Crate outlet_postgres

Crate outlet_postgres 

Source
Expand description

§outlet-postgres

PostgreSQL logging handler for the outlet HTTP request/response middleware. This crate implements the RequestHandler trait from outlet to log HTTP requests and responses to PostgreSQL with JSONB serialization for bodies.

§Features

  • PostgreSQL Integration: Uses sqlx for async PostgreSQL operations
  • JSONB Bodies: Serializes request/response bodies to JSONB fields
  • Type-safe Querying: Query logged data with typed request/response bodies
  • Correlation: Links requests and responses via correlation IDs
  • Error Handling: Graceful error handling with logging

§Quick Start

use outlet::{RequestLoggerLayer, RequestLoggerConfig};
use outlet_postgres::{PostgresHandler, repository::RequestFilter};
use axum::{routing::get, Router};
use tower::ServiceBuilder;
use serde::{Deserialize, Serialize};

// Define your custom request and response body types
#[derive(Clone, Debug, Deserialize, Serialize)]
struct ApiRequest {
    user_id: u64,
    action: String,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
struct ApiResponse {
    success: bool,
    message: String,
}

async fn hello() -> &'static str {
    "Hello, World!"
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let database_url = "postgresql://user:password@localhost/dbname";
     
    // Create handler with separate request and response types
    let handler = PostgresHandler::<ApiRequest, ApiResponse>::new(database_url).await?;
     
    // Or use default Value types for flexible JSON storage
    // let handler = PostgresHandler::new(database_url).await?;
     
    let layer = RequestLoggerLayer::new(RequestLoggerConfig::default(), handler.clone());

    // Get a repository for querying logged requests and responses
    let repository = handler.repository();
     
    // Query logged data with filters
    let filter = RequestFilter {
        method: Some("POST".to_string()),
        status_code_min: Some(400),
        limit: Some(10),
        ..Default::default()
    };
    let results = repository.query(filter).await?;
     
    for pair in results {
        println!("Request: {} {}", pair.request.method, pair.request.uri);
        match pair.request.body {
            Some(Ok(request_body)) => println!("  Parsed request: {:?}", request_body),
            Some(Err(raw_bytes)) => println!("  Raw request bytes: {} bytes", raw_bytes.len()),
            None => println!("  No request body"),
        }
         
        if let Some(response) = pair.response {
            println!("Response: {} ({}ms)", response.status_code, response.duration_ms);
            match response.body {
                Some(Ok(response_body)) => println!("  Parsed response: {:?}", response_body),
                Some(Err(raw_bytes)) => println!("  Raw response bytes: {} bytes", raw_bytes.len()),
                None => println!("  No response body"),
            }
        }
    }

    let app = Router::new()
        .route("/hello", get(hello))
        .layer(ServiceBuilder::new().layer(layer));

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
    axum::serve(listener, app).await?;
    Ok(())
}

Re-exports§

pub use error::PostgresHandlerError;
pub use repository::HttpRequest;
pub use repository::HttpResponse;
pub use repository::RequestFilter;
pub use repository::RequestRepository;
pub use repository::RequestResponsePair;

Modules§

error
Error types for outlet-postgres.
repository

Structs§

PathFilter
Filter configuration for determining which requests to log.
PostgresHandler
PostgreSQL handler for outlet middleware.

Functions§

migrator
Get the migrator for running outlet-postgres database migrations.