blinq-common

Crates.ioblinq-common
lib.rsblinq-common
version0.1.0
created_at2025-06-11 08:59:29.678194+00
updated_at2025-06-11 08:59:29.678194+00
descriptionCommon utilities for Blinq Rust microservices: error handling, logging, and HTTP middleware
homepagehttps://github.com/blinq-dev/blinq-common
repositoryhttps://github.com/blinq-dev/blinq-common
max_upload_size
id1708338
size94,011
(18thdecimal)

documentation

https://docs.rs/blinq-common

README

blinq-common

Common utilities for Blinq Rust microservices: error handling, advanced logging with request ID tracking, and HTTP middleware with extensive customization and override capabilities.

Features

  • Modular Design: Use only what you need with feature flags
  • Error Handling: Extensible AppError type with HTTP status mapping
  • Advanced Logging: Multiple formats (Pretty, Compact, JSON, Full) with request ID injection
  • Request ID Tracking: Automatic request ID generation and injection for distributed tracing
  • Middleware: HTTP middleware (CORS, tracing) with custom configuration
  • Override Support: Easy customization and extension points for microservices

Feature Flags

  • default: Enables logging and error-handling
  • axum: Axum web framework integration
  • logging: Advanced logging utilities with tracing-subscriber and request ID support
  • error-handling: Common error types and handling
  • middleware: HTTP middleware utilities
  • cors: CORS middleware (requires middleware)
  • tracing: HTTP tracing middleware (requires middleware)

Installation

Add to your microservice's Cargo.toml:

[dependencies]
blinq-common = { version = "0.1.0", features = ["axum", "middleware", "cors", "tracing"] }

Or for minimal usage:

[dependencies]
blinq-common = { version = "0.1.0", features = ["error-handling"] }

Quick Start

Basic Usage

use blinq_common::prelude::*;

#[tokio::main]
async fn main() {
    // Initialize logging
    blinq_common::logging::init();

    // Use common error types
    let result: Result<String, AppError> = Err(AppError::NotFound("User not found".into()));
}

Advanced Usage with Request ID Tracking

use blinq_common::prelude::*;
use blinq_common::config::CommonConfig;
use blinq_common::logging::{self, RequestIdStrategy};
use blinq_common::{middleware, named_request_span};
use axum::{routing::get, Router};
use tracing::Instrument;

#[tokio::main]
async fn main() {
    // Create advanced logging configuration with request ID support
    let logging_config = LoggingConfig::new()
        .with_level("debug")
        .with_format(LogFormat::Pretty)
        .with_service_name("my-service")
        .with_auto_request_id(true)
        .with_request_id_strategy(RequestIdStrategy::PrefixedUuid("srv".to_string()))
        .show_target(true)
        .show_thread_ids(true)
        .show_file_line(true);

    logging::init_advanced(logging_config);

    // Create service configuration
    let config = CommonConfig::new("my-service")
        .with_log_level("debug")
        .with_cors_origins(vec!["https://myapp.com".to_string()])
        .with_custom("database_url", "postgres://...");

    // Use configured middleware
    let app = Router::new()
        .route("/", get(handler))
        .layer(middleware::trace())
        .layer(middleware::cors_with_config(&config));

    // ... rest of your app
}

async fn handler() -> Result<String, AppError> {
    // Create a request span with automatic request ID
    let span = named_request_span!("api_request", RequestIdStrategy::Short);

    async {
        tracing::info!("Processing request");
        tracing::debug!("Validating input");

        // Your business logic here
        tracing::info!("Request processed successfully");
        Ok("Hello, World!".to_string())
    }
    .instrument(span)
    .await
}

Request ID Tracking

Automatic request ID injection for distributed tracing across your microservices:

Request ID Strategies

use blinq_common::logging::RequestIdStrategy;

// UUID-like format: "a1b2c3d4-5678-9abc"
RequestIdStrategy::Uuid

// Short alphanumeric: "a1b2c3d4"
RequestIdStrategy::Short

// Timestamp-based: "req_1640995200000"
RequestIdStrategy::Timestamp

// Custom prefix: "pay_a1b2c3d4-5678-9abc"
RequestIdStrategy::PrefixedUuid("pay".to_string())

Using Request Spans

use blinq_common::{named_request_span, request_span};
use tracing::Instrument;

// Simple request span
let span = request_span!();

// Named request span with custom strategy
let span = named_request_span!("payment_processing", RequestIdStrategy::PrefixedUuid("pay".to_string()));

// Use the span
async {
    tracing::info!("Processing payment");
    // Your business logic
}
.instrument(span)
.await;

Logging Formats

Choose from multiple logging formats based on your environment:

Pretty Format (Development)

2025-01-15T10:30:45.123456Z  INFO payment_service: Payment processed successfully, payment_id: "pay_abc123", amount: 100
    at src/payment.rs:45 on ThreadId(2)
    in payment_processing with request_id: "pay_def456"

Compact Format (Production)

2025-01-15T10:30:45Z INFO payment_processing: Payment processed successfully payment_id="pay_abc123" amount=100 request_id="pay_def456"

JSON Format (Log Aggregation)

{
  "timestamp": "2025-01-15T10:30:45Z",
  "level": "INFO",
  "fields": {
    "message": "Payment processed successfully",
    "payment_id": "pay_abc123",
    "amount": 100
  },
  "span": { "request_id": "pay_def456", "name": "payment_processing" },
  "threadId": "ThreadId(2)"
}

Full Format (Maximum Detail)

2025-01-15T10:30:45.123456Z INFO ThreadId(2) payment_processing{request_id="pay_def456"}: payment_service: src/payment.rs:45: Payment processed successfully payment_id="pay_abc123" amount=100

Configuring Logging Formats

use blinq_common::logging::{LoggingConfig, LogFormat, RequestIdStrategy};

// Development configuration
let dev_config = LoggingConfig::new()
    .with_level("debug")
    .with_format(LogFormat::Pretty)
    .with_auto_request_id(true)
    .show_target(true)
    .show_thread_ids(true)
    .show_file_line(true);

// Production configuration
let prod_config = LoggingConfig::new()
    .with_level("info")
    .with_format(LogFormat::Json)
    .with_auto_request_id(true)
    .with_request_id_strategy(RequestIdStrategy::PrefixedUuid("prod".to_string()))
    .show_target(false)
    .show_thread_ids(true)
    .show_file_line(false);

logging::init_advanced(prod_config);

Extending Error Types

Create your own error types that integrate seamlessly:

use blinq_common::AppError;

#[derive(thiserror::Error, Debug)]
pub enum MyServiceError {
    #[error("Custom business logic error: {0}")]
    BusinessLogicError(String),

    #[error("External API error: {0}")]
    ExternalApiError(String),
}

// Convert to common error type
impl From<MyServiceError> for AppError {
    fn from(err: MyServiceError) -> Self {
        match err {
            MyServiceError::BusinessLogicError(msg) => AppError::InvalidInput(msg),
            MyServiceError::ExternalApiError(msg) => AppError::ExternalServiceError(msg),
        }
    }
}

Configuration Override Examples

Custom CORS Configuration

let config = CommonConfig::new("api-gateway")
    .with_cors_origins(vec![
        "https://app.example.com".to_string(),
        "https://admin.example.com".to_string(),
    ])
    .with_cors_headers(vec![
        "content-type".to_string(),
        "authorization".to_string(),
        "x-api-key".to_string(),
    ]);

Advanced Logging Configuration

let config = CommonConfig::new("data-processor")
    .with_log_level("trace")
    .with_custom("log_format", "json")
    .with_custom("log_output", "file")
    .with_custom("log_show_target", "false")
    .with_custom("log_show_threads", "true")
    .with_custom("log_show_location", "true");

// Convert to LoggingConfig
let logging_config = logging::from_common_config(&config);
logging::init_advanced(logging_config);

Service-Specific Configuration

let config = CommonConfig::new("payment-service")
    .with_custom("stripe_api_key", env::var("STRIPE_API_KEY").unwrap())
    .with_custom("webhook_secret", env::var("STRIPE_WEBHOOK_SECRET").unwrap())
    .with_custom("max_retry_attempts", "3")
    .with_custom("enable_request_ids", "true");

Available Modules

Error Handling (error module)

  • AppError: Common error enum with HTTP status mapping
  • Conversion traits for easy integration
  • Axum IntoResponse implementation
  • Helper methods: service_error(), is_client_error(), is_server_error()

Logging (logging module)

  • init(): Basic logging initialization
  • init_with_config(): Logging with CommonConfig
  • init_advanced(): Advanced logging with LoggingConfig
  • init_json_production(): Production JSON logging
  • init_development(): Development pretty logging
  • init_test(): Minimal test logging
  • LoggingConfig: Advanced configuration builder
  • RequestIdStrategy: Request ID generation strategies
  • generate_request_id(): Manual request ID generation
  • create_request_span(): Request span creation utilities

Middleware (middleware module)

  • cors(): Permissive CORS middleware
  • cors_with_config(): Configurable CORS middleware
  • trace(): HTTP request/response tracing

Configuration (config module)

  • CommonConfig: Centralized configuration with builder pattern
  • Override points for all common functionality
  • Custom key-value storage for service-specific config

Examples

Run the basic example:

cargo run --example basic --features="axum,logging,middleware,cors,tracing"

Run the advanced example with request ID tracking:

cargo run --example advanced --features="axum,logging,middleware,cors,tracing"

Test the request ID functionality:

# In one terminal, start the server
cargo run --example advanced --features="axum,logging,middleware,cors,tracing"

# In another terminal, make requests and see unique request IDs in logs
curl http://127.0.0.1:3000/health
curl http://127.0.0.1:3000/payment/100
curl http://127.0.0.1:3000/user/123

Each curl request will generate a unique request ID that appears in all related log entries, making it easy to trace requests through your system.

Real-World Usage Example

Here's how to integrate blinq-common into your referral service:

use blinq_common::prelude::*;
use blinq_common::logging::{LoggingConfig, LogFormat, RequestIdStrategy};
use blinq_common::{middleware, named_request_span};

#[tokio::main]
async fn main() {
    // Configure logging with request IDs
    let logging_config = LoggingConfig::new()
        .with_level("info")
        .with_format(LogFormat::Json) // For production log aggregation
        .with_service_name("referral-service")
        .with_auto_request_id(true)
        .with_request_id_strategy(RequestIdStrategy::PrefixedUuid("ref".to_string()));

    logging::init_advanced(logging_config);

    // Service configuration
    let config = CommonConfig::new("referral-service")
        .with_log_level("info")
        .with_cors_origins(vec!["https://app.blinq.com".to_string()])
        .with_custom("database_url", "postgres://postgres:postgres@localhost:5432/referral_db");

    let app = routes::router()
        .layer(Extension(repo))
        .layer(middleware::trace())
        .layer(middleware::cors_with_config(&config));

    // Now every request will have a unique request ID like "ref_a1b2c3d4-5678-9abc"
    // that appears in all log entries for that request
}

Publishing Your Microservice

When using this crate in your microservice:

  1. Choose appropriate features based on your needs
  2. Configure request ID tracking for distributed tracing
  3. Select logging format based on environment (Pretty for dev, JSON for prod)
  4. Create service-specific configuration using CommonConfig
  5. Extend error types for your business logic
  6. Override middleware settings as needed
  7. Add custom configuration for service-specific settings

Testing

# Run all tests
cargo test

# Run tests for specific features
cargo test --features="error-handling,logging"

# Test logging formats
cargo run --example logging --features="logging"

Contributing

See CONTRIBUTING.md for guidelines.

License

Licensed under the MIT license. See LICENSE for details.

Commit count: 0

cargo fmt