mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-05-23 16:30:39 +00:00
feat(http): improve error logging with client IP (#7503)
* feat(http): improve error logging with client IP - Add logging to ErrorResponse::from_error_message() - Add middleware to log HTTP errors with client IP Closes #7328 Signed-off-by: maximk777 <maximkirienkov777@gmail.com> * fix(http): address review comments for error logging Restore rich Debug logging in from_error(), add URI/method/matched path to client IP middleware, and only log when client address is available. Signed-off-by: evenyag <realevenyag@gmail.com> --------- Signed-off-by: maximk777 <maximkirienkov777@gmail.com> Signed-off-by: evenyag <realevenyag@gmail.com> Co-authored-by: evenyag <realevenyag@gmail.com>
This commit is contained in:
@@ -112,8 +112,8 @@ pub mod utils;
|
||||
use result::HttpOutputWriter;
|
||||
pub(crate) use timeout::DynamicTimeoutLayer;
|
||||
|
||||
mod client_ip;
|
||||
use crate::prom_remote_write::validation::PromValidationMode;
|
||||
|
||||
mod hints;
|
||||
mod read_preference;
|
||||
#[cfg(any(test, feature = "testing"))]
|
||||
@@ -883,6 +883,7 @@ impl HttpServer {
|
||||
authorize::check_http_auth,
|
||||
))
|
||||
.layer(middleware::from_fn(hints::extract_hints))
|
||||
.layer(middleware::from_fn(client_ip::log_error_with_client_ip))
|
||||
.layer(middleware::from_fn(
|
||||
read_preference::extract_read_preference,
|
||||
)),
|
||||
@@ -1247,7 +1248,10 @@ impl Server for HttpServer {
|
||||
error!(e; "Failed to set TCP_NODELAY on incoming connection");
|
||||
}
|
||||
});
|
||||
let serve = axum::serve(listener, app.into_make_service());
|
||||
let serve = axum::serve(
|
||||
listener,
|
||||
app.into_make_service_with_connect_info::<SocketAddr>(),
|
||||
);
|
||||
|
||||
// FIXME(yingwen): Support keepalive.
|
||||
// See:
|
||||
|
||||
109
src/servers/src/http/client_ip.rs
Normal file
109
src/servers/src/http/client_ip.rs
Normal file
@@ -0,0 +1,109 @@
|
||||
// Copyright 2023 Greptime Team
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use axum::body::Body;
|
||||
use axum::extract::{ConnectInfo, MatchedPath};
|
||||
use axum::http::Request;
|
||||
use axum::middleware::Next;
|
||||
use axum::response::Response;
|
||||
use common_telemetry::warn;
|
||||
|
||||
/// Middleware that logs HTTP error responses (4xx/5xx) with client IP address.
|
||||
///
|
||||
/// Extracts client address from [`ConnectInfo`] if available.
|
||||
pub async fn log_error_with_client_ip(req: Request<Body>, next: Next) -> Response {
|
||||
let request_info = req
|
||||
.extensions()
|
||||
.get::<ConnectInfo<SocketAddr>>()
|
||||
.map(|c| c.0)
|
||||
.map(|addr| {
|
||||
let method = req.method().clone();
|
||||
let uri = req.uri().clone();
|
||||
let matched_path = req.extensions().get::<MatchedPath>().cloned();
|
||||
(addr, method, uri, matched_path)
|
||||
});
|
||||
|
||||
let response = next.run(req).await;
|
||||
|
||||
if (response.status().is_client_error() || response.status().is_server_error())
|
||||
&& let Some((addr, method, uri, matched_path)) = request_info
|
||||
{
|
||||
warn!(
|
||||
"HTTP error response {} for {} {} (matched: {}) from client {}",
|
||||
response.status(),
|
||||
method,
|
||||
uri,
|
||||
matched_path
|
||||
.as_ref()
|
||||
.map(|p| p.as_str())
|
||||
.unwrap_or("<unknown>"),
|
||||
addr
|
||||
);
|
||||
}
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use axum::Router;
|
||||
use axum::routing::get;
|
||||
use http::StatusCode;
|
||||
use tower::ServiceExt;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_middleware_passes_error_response() {
|
||||
async fn not_found_handler() -> StatusCode {
|
||||
StatusCode::NOT_FOUND
|
||||
}
|
||||
|
||||
let app = Router::new()
|
||||
.route("/not-found", get(not_found_handler))
|
||||
.layer(axum::middleware::from_fn(log_error_with_client_ip));
|
||||
|
||||
let response = app
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.uri("/not-found")
|
||||
.body(Body::empty())
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(response.status(), StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_middleware_passes_success_response() {
|
||||
async fn ok_handler() -> StatusCode {
|
||||
StatusCode::OK
|
||||
}
|
||||
|
||||
let app = Router::new()
|
||||
.route("/ok", get(ok_handler))
|
||||
.layer(axum::middleware::from_fn(log_error_with_client_ip));
|
||||
|
||||
let response = app
|
||||
.oneshot(Request::builder().uri("/ok").body(Body::empty()).unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
}
|
||||
}
|
||||
@@ -32,17 +32,24 @@ pub struct ErrorResponse {
|
||||
impl ErrorResponse {
|
||||
pub fn from_error(error: impl ErrorExt) -> Self {
|
||||
let code = error.status_code();
|
||||
|
||||
if code.should_log_error() {
|
||||
error!(error; "Failed to handle HTTP request");
|
||||
} else {
|
||||
debug!("Failed to handle HTTP request, err: {:?}", error);
|
||||
}
|
||||
|
||||
Self::from_error_message(code, error.output_msg())
|
||||
ErrorResponse {
|
||||
code: code as u32,
|
||||
error: error.output_msg(),
|
||||
execution_time_ms: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_error_message(code: StatusCode, msg: String) -> Self {
|
||||
if code.should_log_error() {
|
||||
error!("Failed to handle HTTP request: {}", msg);
|
||||
} else {
|
||||
debug!("Failed to handle HTTP request: {}", msg);
|
||||
}
|
||||
ErrorResponse {
|
||||
code: code as u32,
|
||||
error: msg,
|
||||
|
||||
Reference in New Issue
Block a user