一键导入
salvo-logging
Implement request logging, tracing, and observability. Use for debugging, monitoring, and production observability.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Implement request logging, tracing, and observability. Use for debugging, monitoring, and production observability.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Implement authentication and authorization using JWT, Basic Auth, or custom schemes. Use for securing API endpoints and user management.
Create basic Salvo web applications with handlers, routers, and server setup. Use when starting a new Salvo project or adding basic HTTP endpoints.
Implement caching strategies for improved performance. Use for reducing database load and speeding up responses.
Compress HTTP responses using gzip, brotli, zstd, or deflate. Use for reducing bandwidth and improving load times.
Limit concurrent requests to protect resources. Use for file uploads, expensive operations, and preventing resource exhaustion.
Configure Cross-Origin Resource Sharing (CORS) and security headers. Use for APIs accessed from browsers on different domains.
| name | salvo-logging |
| description | Implement request logging, tracing, and observability. Use for debugging, monitoring, and production observability. |
| version | 0.94.0 |
| tags | ["operations","logging","tracing","observability"] |
[dependencies]
salvo = { version = "0.94.0", features = ["logging"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
salvo::logging::Logger emits a tracing span per request with remote_addr, version, method, path, and response status + duration. It must be installed on a Service (not a Router) so it wraps the catcher and sees the final status:
use salvo::logging::Logger;
use salvo::prelude::*;
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let router = Router::new().get(hello);
let service = Service::new(router).hoop(Logger::new());
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(service).await;
}
Logger::new().log_status_error(false) disables auto-logging of StatusError bodies (on by default).
When Logger is not enough, write a handler that measures time around call_next:
use salvo::prelude::*;
use std::time::Instant;
use tracing::info;
#[handler]
async fn request_logger(
req: &mut Request, depot: &mut Depot, res: &mut Response, ctrl: &mut FlowCtrl,
) {
let start = Instant::now();
let method = req.method().clone();
let path = req.uri().path().to_string();
ctrl.call_next(req, depot, res).await;
info!(
%method,
%path,
status = res.status_code.map(|s| s.as_u16()).unwrap_or(0),
duration_ms = start.elapsed().as_millis() as u64,
"request",
);
}
Gotcha: inside a handler res.status_code is a Option<StatusCode> field (not a method). If the handler hasn't set it, it's None until the catcher runs.
use tracing::{debug, info, warn, instrument};
#[handler]
#[instrument(skip_all, fields(user_id))]
async fn get_user(req: &mut Request, res: &mut Response) {
let user_id: u32 = req.param("id").unwrap_or(0);
tracing::Span::current().record("user_id", user_id);
debug!("fetching user");
match fetch_user(user_id).await {
Ok(user) => { info!("found"); res.render(Json(user)); }
Err(e) => { warn!(error = %e, "not found"); res.status_code(StatusCode::NOT_FOUND); }
}
}
Configure levels via RUST_LOG (e.g. RUST_LOG=info,salvo=debug,hyper=warn):
use tracing_subscriber::{EnvFilter, fmt, layer::SubscriberExt, util::SubscriberInitExt};
fn init_logging() {
let filter = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new("info,salvo=debug,hyper=warn"));
tracing_subscriber::registry()
.with(filter)
.with(fmt::layer())
.init();
}
tracing_subscriber::registry()
.with(EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")))
.with(fmt::layer().json().with_current_span(true).with_span_list(true))
.init();
tracing-appender = "0.2"
use tracing_appender::{rolling, non_blocking};
let file_appender = rolling::daily("logs", "app.log");
let (writer, _guard) = non_blocking(file_appender);
tracing_subscriber::registry()
.with(EnvFilter::new("info"))
.with(fmt::layer().with_writer(writer).with_ansi(false))
.init();
// _guard MUST be held for the process lifetime or buffered logs are dropped.
use uuid::Uuid;
#[handler]
async fn add_request_id(
req: &mut Request, depot: &mut Depot, res: &mut Response, ctrl: &mut FlowCtrl,
) {
let id = req.header::<String>("x-request-id")
.unwrap_or_else(|| Uuid::new_v4().to_string());
depot.insert("request_id", id.clone());
res.headers_mut().insert("x-request-id", id.parse().unwrap());
let span = tracing::info_span!("request", request_id = %id, method = %req.method(), path = %req.uri().path());
let _enter = span.enter();
ctrl.call_next(req, depot, res).await;
}
Salvo also ships salvo::request_id::RequestId middleware (feature request-id) that does this automatically.
#[handler]
async fn timing(req: &mut Request, depot: &mut Depot, res: &mut Response, ctrl: &mut FlowCtrl) {
let start = Instant::now();
ctrl.call_next(req, depot, res).await;
let ms = start.elapsed().as_millis();
if ms > 1000 {
tracing::warn!(path = %req.uri().path(), duration_ms = ms as u64, "slow request");
}
}
Salvo provides salvo_otel (feature otel) for trace propagation. For raw tracing-otel:
opentelemetry = "0.22"
opentelemetry-otlp = "0.15"
tracing-opentelemetry = "0.23"
let tracer = opentelemetry_otlp::new_pipeline()
.tracing()
.with_exporter(opentelemetry_otlp::new_exporter().tonic().with_endpoint("http://localhost:4317"))
.install_batch(opentelemetry_sdk::runtime::Tokio)
.unwrap();
tracing_subscriber::registry()
.with(tracing_subscriber::fmt::layer())
.with(tracing_opentelemetry::layer().with_tracer(tracer))
.init();
// On shutdown:
opentelemetry::global::shutdown_tracer_provider();
Logger on Service, not Router — otherwise it logs before the catcher rewrites the status and body.Logger requires a tracing subscriber; without one, nothing is emitted.tracing-appender writers must keep their WorkerGuard alive or pending log lines are discarded at shutdown.Authorization / Cookie unredacted.Logger auto-logs StatusError bodies.tracing-test crate verifies log output.