Skip to content

Трассировка и логирование

Трассировка и логирование - важные компоненты для разработки и поддержки веб-приложений на Axum. Эффективное логирование позволяет отслеживать работу приложения, выявлять ошибки и анализировать производительность.

Содержание

Настройка трассировки

Для логирования в Axum используются библиотеки tracing и tracing-subscriber. Добавьте их в зависимости проекта:

toml
[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"] }

Базовая настройка логирования в приложении:

rust
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. Вот как интегрировать трассировку в приложение:

rust
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();
}

Стратегии логирования

Существует несколько стратегий логирования, которые можно использовать в зависимости от окружения и требований:

Текстовое логирование для разработки

rust
pub fn setup_development_logging() {
    tracing_subscriber::registry()
        .with(EnvFilter::new("app=debug,tower_http=debug,axum=debug"))
        .with(fmt::layer().pretty())  // Красивое форматирование для консоли
        .init();
}

JSON-логирование для продакшена

rust
pub fn setup_production_logging() {
    tracing_subscriber::registry()
        .with(EnvFilter::new("app=info,tower_http=info,axum=info"))
        .with(fmt::layer().json())  // JSON формат для анализа логов
        .init();
}

Условная настройка логирования

rust
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" (областей), которые позволяют группировать логи и добавлять к ним метаданные:

rust
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 с пользовательскими обработчиками:

rust
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);

Визуализация логов

Визуализация логов помогает анализировать работу приложения. Существует несколько подходов:

Экспорт в систему мониторинга

rust
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();
}

Логирование в файл

rust
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. Структурируйте логи

Используйте структурированное логирование вместо обычных текстовых сообщений:

rust
// Плохо
tracing::info!("Пользователь с id={} не найден", user_id);

// Хорошо
tracing::info!(user_id = %user_id, "Пользователь не найден");

2. Используйте правильные уровни логирования

rust
// 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. Трассировка ошибок

rust
use tracing::instrument;

#[instrument(err)]
async fn process_data(data: Vec<u8>) -> Result<(), Error> {
    // Автоматически логируется ошибка, если она возникнет
    process_step_1(&data)?;
    process_step_2(&data)?;
    Ok(())
}

4. Фильтрация логов по окружению

rust
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. Включение контекстной информации

rust
// 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. Использование типов для логирования бизнес-событий

rust
// Определение бизнес-события
#[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-приложений. Используя структурированные логи и контекстную информацию, вы получаете важные данные о работе вашего сервиса, что помогает быстро выявлять и исправлять проблемы.