Трассировка и логирование
Трассировка и логирование - важные компоненты для разработки и поддержки веб-приложений на Axum. Эффективное логирование позволяет отслеживать работу приложения, выявлять ошибки и анализировать производительность.
Содержание
- Настройка трассировки
- Интеграция с Axum
- Стратегии логирования
- Контекстное логирование
- Трассировка запросов
- Визуализация логов
- Лучшие практики
Настройка трассировки
Для логирования в Axum используются библиотеки tracing
и tracing-subscriber
. Добавьте их в зависимости проекта:
[dependencies]
axum = "0.7.2"
tokio = { version = "1.32.0", features = ["full"] }
tracing = "0.1.40"
tracing-subscriber = { version = "0.3.17", features = ["env-filter", "json"] }
tower-http = { version = "0.4.4", features = ["trace"] }
Базовая настройка логирования в приложении:
use tracing_subscriber::{
fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter
};
pub fn setup_logging() {
// Настройка фильтра логов на основе переменной окружения RUST_LOG
// По умолчанию включаем логи уровня INFO для нашего приложения
let env_filter = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new("app=info,tower_http=info,axum=info"));
// Инициализация подписчика с форматированием и фильтром
tracing_subscriber::registry()
.with(env_filter)
.with(fmt::layer())
.init();
// Логируем запуск приложения
tracing::info!("Логирование инициализировано");
}
Интеграция с Axum
Axum работает с трассировкой через middleware из пакета tower-http
. Вот как интегрировать трассировку в приложение:
use axum::{Router, routing::get};
use tower_http::trace::{self, TraceLayer};
use tracing::Level;
use std::time::Duration;
async fn hello() -> &'static str {
"Hello, World!"
}
#[tokio::main]
async fn main() {
// Настройка логирования
setup_logging();
// Создание маршрутизатора с трассировкой
let app = Router::new()
.route("/", get(hello))
.layer(
TraceLayer::new_for_http()
// Настройка уровней логирования для разных событий
.make_span_with(trace::DefaultMakeSpan::new().level(Level::INFO))
.on_request(trace::DefaultOnRequest::new().level(Level::INFO))
.on_response(trace::DefaultOnResponse::new().level(Level::INFO))
// Включение логирования для медленных запросов
.on_failure(trace::DefaultOnFailure::new().level(Level::WARN))
);
// Запуск сервера
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
.await
.unwrap();
tracing::info!("Сервер запущен на http://127.0.0.1:3000");
axum::serve(listener, app).await.unwrap();
}
Стратегии логирования
Существует несколько стратегий логирования, которые можно использовать в зависимости от окружения и требований:
Текстовое логирование для разработки
pub fn setup_development_logging() {
tracing_subscriber::registry()
.with(EnvFilter::new("app=debug,tower_http=debug,axum=debug"))
.with(fmt::layer().pretty()) // Красивое форматирование для консоли
.init();
}
JSON-логирование для продакшена
pub fn setup_production_logging() {
tracing_subscriber::registry()
.with(EnvFilter::new("app=info,tower_http=info,axum=info"))
.with(fmt::layer().json()) // JSON формат для анализа логов
.init();
}
Условная настройка логирования
pub fn setup_logging(is_production: bool) {
let env_filter = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| {
if is_production {
EnvFilter::new("app=info,tower_http=info,axum=warn")
} else {
EnvFilter::new("app=debug,tower_http=debug,axum=debug")
}
});
let formatting_layer = if is_production {
fmt::layer().json()
} else {
fmt::layer().pretty()
};
tracing_subscriber::registry()
.with(env_filter)
.with(formatting_layer)
.init();
}
Контекстное логирование
Tracing поддерживает контекстное логирование с помощью "spans" (областей), которые позволяют группировать логи и добавлять к ним метаданные:
use axum::{extract::{State, Path}, Json};
use tracing::{info, error, warn, instrument};
use uuid::Uuid;
#[derive(Clone)]
struct AppState {
db_pool: PgPool,
}
// Инструментирование функции для автоматического логирования
#[instrument(skip(state), fields(user_id = %id))]
async fn get_user(
Path(id): Path<i32>,
State(state): State<AppState>,
) -> Json<User> {
info!("Получение данных пользователя");
// Трассировка запроса к БД
let span = tracing::info_span!("database_query", query = "get_user_by_id");
let user = state.db_pool.get_user(id).instrument(span).await;
match user {
Ok(user) => {
info!(name = %user.name, "Пользователь найден");
Json(user)
},
Err(e) => {
error!(error = %e, "Ошибка получения пользователя");
// Обработка ошибки...
}
}
}
// Добавление идентификатора запроса для трассировки через middleware
async fn request_id_middleware<B>(
mut req: Request<B>,
next: Next<B>,
) -> impl IntoResponse {
let request_id = Uuid::new_v4();
// Создаем новый span с идентификатором запроса
let span = tracing::info_span!("request", %request_id);
// Выполняем запрос в контексте span
async move {
// Добавляем request_id в расширения запроса
req.extensions_mut().insert(request_id);
// Выполняем следующий middleware
next.run(req).await
}.instrument(span).await
}
Трассировка запросов
Для более детальной трассировки HTTP-запросов можно настроить TraceLayer
с пользовательскими обработчиками:
use tower_http::trace::{self, TraceLayer};
use tracing::{Span, Level};
use axum::http::{Request, Response};
use std::time::Duration;
// Пользовательская функция для создания span'а
fn make_span<B>(request: &Request<B>) -> Span {
let method = request.method();
let uri = request.uri();
let path = uri.path();
tracing::info_span!(
"request",
method = %method,
path = %path,
query = %uri.query().unwrap_or(""),
version = ?request.version(),
remote_addr = %request.extensions()
.get::<axum::extract::ConnectInfo<std::net::SocketAddr>>()
.map(|ci| ci.0.to_string())
.unwrap_or_else(|| "unknown".into()),
)
}
// Пользовательская функция для обработки ответа
fn on_response<B>(response: &Response<B>, latency: Duration, span: &Span) {
let status = response.status();
let status_code = status.as_u16();
// Выбираем уровень логирования в зависимости от статуса
let level = if status_code >= 500 {
Level::ERROR
} else if status_code >= 400 {
Level::WARN
} else {
Level::INFO
};
// Логируем информацию о запросе
tracing::event!(
target: "http_response",
level,
latency = %format!("{:.3}ms", latency.as_secs_f64() * 1000.0),
status = %status_code,
status_text = %status.canonical_reason().unwrap_or("Unknown"),
);
}
// Создание трассировочного слоя
let trace_layer = TraceLayer::new_for_http()
.make_span_with(make_span)
.on_response(on_response)
.on_request(|request: &Request<_>, _span: &Span| {
tracing::info!(
target: "http_request",
method = %request.method(),
path = %request.uri().path(),
"Начало обработки запроса"
);
});
// Добавление к маршрутизатору
let app = Router::new()
.route("/", get(hello))
.layer(trace_layer);
Визуализация логов
Визуализация логов помогает анализировать работу приложения. Существует несколько подходов:
Экспорт в систему мониторинга
use tracing_subscriber::{
fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter,
fmt::format::FmtSpan,
};
use opentelemetry::{global, sdk::trace as sdktrace};
use tracing_opentelemetry::OpenTelemetryLayer;
pub fn setup_opentelemetry_logging() {
// Настраиваем экспортер OpenTelemetry
let tracer = opentelemetry_jaeger::new_pipeline()
.with_service_name("axum-app")
.install_simple()
.expect("Ошибка настройки трассировки Jaeger");
// Создаем слой OpenTelemetry
let telemetry = tracing_opentelemetry::layer().with_tracer(tracer);
// Настраиваем подписчика
tracing_subscriber::registry()
.with(EnvFilter::from_default_env())
.with(fmt::layer())
.with(telemetry)
.init();
}
Логирование в файл
use std::fs::File;
pub fn setup_file_logging() {
let file = File::create("app.log").expect("Не удалось создать файл логов");
tracing_subscriber::registry()
.with(EnvFilter::from_default_env())
.with(fmt::layer().with_writer(file))
.init();
}
Лучшие практики
1. Структурируйте логи
Используйте структурированное логирование вместо обычных текстовых сообщений:
// Плохо
tracing::info!("Пользователь с id={} не найден", user_id);
// Хорошо
tracing::info!(user_id = %user_id, "Пользователь не найден");
2. Используйте правильные уровни логирования
// ERROR - для ошибок, требующих немедленного внимания
tracing::error!(error = %e, "Соединение с базой данных потеряно");
// WARN - для некритичных, но важных проблем
tracing::warn!(user_id = %id, "Попытка доступа к удаленной записи");
// INFO - для отслеживания нормальной работы системы
tracing::info!(request_count = %count, "Количество обработанных запросов");
// DEBUG - для подробной информации, полезной при отладке
tracing::debug!(query = %sql, "Выполнение SQL-запроса");
// TRACE - для очень детальной информации
tracing::trace!(headers = ?req.headers(), "Заголовки входящего запроса");
3. Трассировка ошибок
use tracing::instrument;
#[instrument(err)]
async fn process_data(data: Vec<u8>) -> Result<(), Error> {
// Автоматически логируется ошибка, если она возникнет
process_step_1(&data)?;
process_step_2(&data)?;
Ok(())
}
4. Фильтрация логов по окружению
use tracing_subscriber::filter::EnvFilter;
pub fn configure_logging() {
let filter = match std::env::var("APP_ENV").as_deref() {
Ok("production") => EnvFilter::new("app=info,warp=warn,tower_http=warn"),
Ok("staging") => EnvFilter::new("app=debug,warp=info,tower_http=info"),
_ => EnvFilter::new("app=trace,warp=debug,tower_http=debug"),
};
tracing_subscriber::registry()
.with(filter)
.with(fmt::layer())
.init();
}
5. Включение контекстной информации
// Middleware для добавления контекстной информации к логам
async fn context_middleware<B>(
mut req: Request<B>,
next: Next<B>,
) -> impl IntoResponse {
let user_agent = req
.headers()
.get(header::USER_AGENT)
.and_then(|h| h.to_str().ok())
.unwrap_or("unknown");
let method = req.method().clone();
let uri = req.uri().clone();
// Создаем span с контекстной информацией
let span = tracing::info_span!(
"request",
method = %method,
uri = %uri,
user_agent = %user_agent,
);
// Выполняем запрос в контексте span'а
span.in_scope(|| next.run(req)).await
}
6. Использование типов для логирования бизнес-событий
// Определение бизнес-события
#[derive(Debug)]
enum BusinessEvent {
UserRegistered { id: i32, email: String },
OrderPlaced { order_id: String, amount: f64 },
PaymentFailed { order_id: String, reason: String },
}
// Логирование бизнес-события
fn log_business_event(event: BusinessEvent) {
match event {
BusinessEvent::UserRegistered { id, email } => {
tracing::info!(user_id = %id, %email, "Пользователь зарегистрирован");
}
BusinessEvent::OrderPlaced { order_id, amount } => {
tracing::info!(%order_id, %amount, "Заказ размещен");
}
BusinessEvent::PaymentFailed { order_id, reason } => {
tracing::warn!(%order_id, %reason, "Ошибка оплаты");
}
}
}
Правильная настройка трассировки и логирования значительно упрощает разработку, отладку и эксплуатацию Axum-приложений. Используя структурированные логи и контекстную информацию, вы получаете важные данные о работе вашего сервиса, что помогает быстро выявлять и исправлять проблемы.