shared-logging 0.1.0

Structured logging library with context propagation, redaction, and HTTP middleware
Documentation
# Step-by-Step Walkthrough: Consuming shared-logging

This guide walks you through consuming the `shared-logging` crate in a real application.

## Step 1: Add the Dependency

Create a new Rust project or add to your existing `Cargo.toml`:

### Option A: Local Path (Recommended for Development)

If `shared-logging` is in a sibling directory:
```toml
[dependencies]
shared-logging = { path = "../shared-logging" }  # Adjust path as needed
```

### Option B: Git Repository

If the crate is in a Git repository:
```toml
[dependencies]
shared-logging = { git = "https://github.com/kelleyblackmore/shared-logging" }
```

### Optional Features

Enable HTTP middleware or OpenTelemetry support:
```toml
shared-logging = { 
    path = "../shared-logging",
    features = ["http"]  # or ["otel"] or ["http", "otel"]
}
```

**Note:** For local development, path dependencies are the easiest. See [INSTALLATION.md](INSTALLATION.md) for more options.

## Step 2: Initialize the Logger

In your `main.rs` or application entry point, initialize the logger **once** at startup:

```rust
use shared_logging::init_logger;

fn main() {
    // Initialize logger with service name and default log level
    init_logger("my-service", "info")
        .expect("Failed to initialize logger");
    
    println!("Logger initialized!");
}
```

**Run it:**
```bash
cargo run
```

**Output:** You'll see JSON logs in stdout. The logger is now ready!

## Step 3: Create Logger Instances

Create logger instances for different parts of your application:

```rust
use shared_logging::Logger;

fn main() {
    init_logger("my-service", "info").unwrap();
    
    // Create loggers for different modules
    let auth_logger = Logger::new(Some("auth".to_string()));
    let api_logger = Logger::new(Some("api".to_string()));
    let db_logger = Logger::new(Some("database".to_string()));
    
    auth_logger.info("Auth module initialized");
    api_logger.info("API module initialized");
    db_logger.info("Database module initialized");
}
```

**Output:**
```json
{"timestamp":"2024-01-15T10:30:45.123Z","level":"info","message":"Auth module initialized","service":"my-service","module":"auth","fields":"{}"}
{"timestamp":"2024-01-15T10:30:45.124Z","level":"info","message":"API module initialized","service":"my-service","module":"api","fields":"{}"}
{"timestamp":"2024-01-15T10:30:45.125Z","level":"info","message":"Database module initialized","service":"my-service","module":"database","fields":"{}"}
```

## Step 4: Add Structured Fields

Add structured data to your logs:

```rust
use shared_logging::Logger;

fn main() {
    init_logger("my-service", "info").unwrap();
    let logger = Logger::new(Some("auth".to_string()));
    
    logger.info_with("User logged in", |e| {
        e.field("user_id", "user123");
        e.field("ip_address", "192.168.1.100");
        e.field("login_method", "oauth");
        e.field("success", true);
    });
}
```

**Output:**
```json
{
  "timestamp": "2024-01-15T10:30:45.123Z",
  "level": "info",
  "message": "User logged in",
  "service": "my-service",
  "module": "auth",
  "fields": "{\"user_id\":\"user123\",\"ip_address\":\"192.168.1.100\",\"login_method\":\"oauth\",\"success\":true}"
}
```

## Step 5: Use Context Propagation

Add correlation IDs that propagate through all logs:

```rust
use shared_logging::{Logger, ContextBuilder};

fn main() {
    init_logger("my-service", "info").unwrap();
    
    // Create context with correlation IDs
    let context = ContextBuilder::new()
        .trace_id("abc123def456")
        .generate_request_id()  // Auto-generates a UUID
        .user_id("user123")
        .tenant_id("tenant001")
        .build();
    
    // Create logger with context
    let logger = Logger::with_context(Some("api".to_string()), context);
    
    // All logs will include the context fields
    logger.info("Processing request");
    logger.info_with("User action", |e| {
        e.field("action", "create_post");
    });
}
```

**Output:** All logs include `trace_id`, `request_id`, `user_id`, and `tenant_id` fields.

## Step 6: Log Errors

Log errors with proper formatting:

```rust
use shared_logging::Logger;
use std::error::Error;

#[derive(Debug)]
struct DatabaseError {
    message: String,
    code: u32,
}

impl std::fmt::Display for DatabaseError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "Database error {}: {}", self.code, self.message)
    }
}

impl Error for DatabaseError {}

fn main() {
    init_logger("my-service", "info").unwrap();
    let logger = Logger::new(Some("database".to_string()));
    
    let error = DatabaseError {
        message: "Connection timeout".to_string(),
        code: 1001,
    };
    
    // Simple error logging
    logger.log_error("Failed to connect", &error);
    
    // Error with additional context
    logger.error_with("Database operation failed", |e| {
        e.field("operation", "user_lookup");
        e.field("retry_count", 3);
        e.error(&error);
    });
}
```

## Step 7: Handle HTTP Requests (Optional)

If you're building an HTTP server, use context for request-scoped logging:

```rust
use axum::{extract::Request, Router, routing::get};
use shared_logging::{Logger, ContextBuilder};

async fn handler(request: Request) -> String {
    // Extract or generate request ID
    let request_id = request.headers()
        .get("x-request-id")
        .and_then(|v| v.to_str().ok())
        .map(|s| s.to_string())
        .unwrap_or_else(|| {
            ContextBuilder::new()
                .generate_request_id()
                .build()
                .request_id
                .unwrap()
        });
    
    // Create context
    let context = ContextBuilder::new()
        .request_id(&request_id)
        .build();
    
    // Create logger with context
    let logger = Logger::with_context(Some("handler".to_string()), context);
    
    logger.info("Request received");
    
    // Your handler logic here
    
    "OK".to_string()
}
```

## Step 8: Configure Log Levels

Control log verbosity via environment variable:

```bash
# Set global log level
export RUST_LOG=info
cargo run

# Set level for specific modules
export RUST_LOG=my_service=debug,shared_logging=info
cargo run

# Very verbose (trace level)
export RUST_LOG=trace
cargo run
```

## Complete Example

Here's a complete example putting it all together:

```rust
use shared_logging::{init_logger, Logger, ContextBuilder};
use std::error::Error;

fn main() {
    // 1. Initialize logger once at startup
    init_logger("my-service", "info").unwrap();
    
    // 2. Create module loggers
    let logger = Logger::new(Some("main".to_string()));
    logger.info("Application starting");
    
    // 3. Simulate processing a request with context
    let context = ContextBuilder::new()
        .generate_request_id()
        .user_id("user123")
        .build();
    
    let api_logger = Logger::with_context(Some("api".to_string()), context);
    
    // 4. Log request processing
    api_logger.info_with("Processing request", |e| {
        e.field("endpoint", "/api/users");
        e.field("method", "GET");
    });
    
    // 5. Handle errors
    match process_request() {
        Ok(_) => api_logger.info("Request processed successfully"),
        Err(e) => {
            api_logger.error_with("Request failed", |e| {
                e.field("retry_count", 3);
                e.error(&e);
            });
        }
    }
}

fn process_request() -> Result<(), Box<dyn Error>> {
    // Simulate some work
    Ok(())
}
```

## Running the Examples

The crate includes runnable examples:

```bash
# Basic usage
cargo run --example basic_usage

# HTTP server example (requires tokio)
cargo run --example http_server

# Error handling example
cargo run --example error_handling
```

## Key Takeaways

1. **Initialize once**: Call `init_logger()` only once at application startup
2. **Module names**: Use descriptive module names when creating loggers
3. **Context propagation**: Use `ContextBuilder` to add correlation IDs
4. **Structured fields**: Use `*_with()` methods to add structured data
5. **Error logging**: Use `log_error()` for proper error formatting
6. **Automatic redaction**: Sensitive data is automatically redacted
7. **Environment control**: Use `RUST_LOG` to control log levels

## Next Steps

- Read the [CONSUMER_GUIDE.md]CONSUMER_GUIDE.md for detailed API documentation
- Check the [README.md]README.md for feature overview
- Explore the examples in the `examples/` directory