# Consumer Guide: Using shared-logging
This guide walks you through consuming the `shared-logging` crate in your Rust application.
## Table of Contents
1. [Installation](#installation)
2. [Basic Setup](#basic-setup)
3. [Basic Logging](#basic-logging)
4. [Structured Logging with Fields](#structured-logging-with-fields)
5. [Context Propagation](#context-propagation)
6. [Error Logging](#error-logging)
7. [HTTP Middleware](#http-middleware)
8. [OpenTelemetry Integration](#opentelemetry-integration)
9. [Configuration](#configuration)
## Installation
### Option 1: Local Path Dependency (Recommended for Development)
For local development when the crate is in a sibling directory or workspace:
```toml
[dependencies]
shared-logging = { path = "../shared-logging" } # Adjust path relative to your project
# With optional features
# shared-logging = { path = "../shared-logging", features = ["http", "otel"] }
```
**When to use:**
- Developing the crate alongside your application
- Testing changes to the logging library
- Local development and testing
### Option 2: Git Dependency
If the crate is in a Git repository:
```toml
[dependencies]
shared-logging = { git = "https://github.com/kelleyblackmore/shared-logging" }
# With optional features
# shared-logging = { git = "https://github.com/kelleyblackmore/shared-logging", features = ["http"] }
```
**When to use:**
- Using a specific version from a Git repository
- The crate isn't published to crates.io yet
### Option 3: Published to crates.io (Future)
Once published, you can use it like any other crate:
```toml
[dependencies]
shared-logging = "0.1.0" # Use the version number
# With optional features
# shared-logging = { version = "0.1.0", features = ["http", "otel"] }
```
**When to use:**
- The crate is published to crates.io
- You want version management via semver
### Option 4: Workspace Setup (Multiple Projects)
If you have multiple projects that use this crate, set up a Cargo workspace:
**In your workspace root `Cargo.toml`:**
```toml
[workspace]
members = [
"shared-logging",
"my-service-1",
"my-service-2",
]
```
**In your service `Cargo.toml`:**
```toml
[dependencies]
shared-logging = { path = "../shared-logging" }
```
**When to use:**
- Managing multiple projects that share the logging library
- Coordinated development across services
### Step 2: Import the Library
```rust
use shared_logging::{init_logger, Logger, ContextBuilder};
```
## Basic Setup
### Initialize the Logger (Once at Startup)
The logger must be initialized once at the start of your application. This sets up:
- JSON formatter
- Log level filtering
- Output destination (stdout by default)
```rust
use shared_logging::init_logger;
fn main() {
// Initialize with service name and default log level
init_logger("my-service", "info")
.expect("Failed to initialize logger");
// Your application code here
}
```
**Parameters:**
- `service_name`: Name of your service (appears in all log events)
- `default_level`: Default log level (`"trace"`, `"debug"`, `"info"`, `"warn"`, `"error"`)
**Note:** The log level can be overridden with the `RUST_LOG` environment variable:
```bash
RUST_LOG=debug cargo run
RUST_LOG=my_service=debug,other_crate=info cargo run
```
## Basic Logging
### Create a Logger Instance
Create logger instances for different modules/components:
```rust
use shared_logging::Logger;
// Create logger for a specific module
let logger = Logger::new("auth");
let api_logger = Logger::new("api");
let db_logger = Logger::new("database");
```
### Simple Logging
```rust
let logger = Logger::new("my-module");
logger.trace("Very detailed information");
logger.debug("Debug information");
logger.info("Informational message");
logger.warn("Warning message");
logger.error("Error message");
```
**Output:**
```json
{
"timestamp": "2024-01-15T10:30:45.123Z",
"level": "info",
"message": "Informational message",
"service": "my-service",
"module": "my-module"
}
```
## Structured Logging with Fields
Add structured fields to your logs:
```rust
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}"
}
```
### Automatic Redaction
Sensitive fields are automatically redacted:
```rust
logger.info_with("User registration", |e| {
e.field("email", "user@example.com"); // Partially redacted: ****.com
e.field("password", "secret123"); // Fully redacted: [REDACTED]
e.field("api_key", "sk_live_abc123xyz"); // Fully redacted: [REDACTED]
e.field("phone", "555-123-4567"); // Partially redacted: ****4567
});
```
## Context Propagation
Context fields (`trace_id`, `span_id`, `request_id`, `user_id`, `tenant_id`) are automatically included in all logs.
### Creating Context
```rust
use shared_logging::{ContextBuilder, Logger};
// Build context
let context = ContextBuilder::new()
.trace_id("abc123def456") // OpenTelemetry trace ID
.span_id("span789") // OpenTelemetry span ID
.generate_request_id() // Auto-generate request ID
.user_id("user123") // User identifier
.tenant_id("tenant001") // Tenant identifier
.build();
// Create logger with context
let logger = Logger::with_context("api", context);
logger.info("Processing request"); // All logs include context fields
```
### Updating Context
```rust
let mut logger = Logger::new("api");
// Set context
let context = ContextBuilder::new()
.generate_request_id()
.user_id("user123")
.build();
logger.set_context(context);
// Or merge additional context
let additional_context = ContextBuilder::new()
.tenant_id("tenant001")
.build();
logger.merge_context(additional_context);
```
## Error Logging
### Simple Error Logging
```rust
let result: Result<(), String> = Err("Something went wrong".to_string());
if let Err(e) = result {
logger.log_error("Operation failed", &e);
}
```
### Error with Context
```rust
use std::error::Error;
fn process_user(user_id: &str) -> Result<(), Box<dyn Error>> {
// ... your code ...
Err("Database error".into())
}
// Log error with context
match process_user("user123") {
Ok(_) => logger.info("User processed successfully"),
Err(e) => {
logger.error_with("Failed to process user", |e| {
e.field("user_id", "user123");
e.field("operation", "user_processing");
e.error(e.as_ref());
});
}
}
```
**Output:**
```json
{
"timestamp": "2024-01-15T10:30:45.123Z",
"level": "error",
"message": "Failed to process user",
"service": "my-service",
"module": "api",
"fields": "{\"user_id\":\"user123\",\"operation\":\"user_processing\",\"error\":{\"error_type\":\"...\",\"error_message\":\"Database error\",\"error_stack\":[...]}}"
}
```
## HTTP Middleware
### Using with Axum
```rust
use axum::{Router, routing::get};
use shared_logging::init_logger;
use tower::ServiceBuilder;
use tower_http::trace::TraceLayer;
#[tokio::main]
async fn main() {
init_logger("http-server", "info").unwrap();
let app = Router::new()
.route("/", get(handler))
.layer(
ServiceBuilder::new()
.layer(TraceLayer::new_for_http())
);
// ... start server
}
```
### Manual Request ID Handling
```rust
use axum::extract::Request;
use shared_logging::{Logger, ContextBuilder};
async fn handler(request: Request) -> Response {
// 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 with request ID
let context = ContextBuilder::new()
.request_id(&request_id)
.build();
let logger = Logger::with_context("handler", context);
logger.info("Processing request");
// Your handler logic
}
```
## OpenTelemetry Integration
### Setup
```rust
use shared_logging::{init_logger, otel};
#[cfg(feature = "otel")]
fn setup_otel() -> Result<(), Box<dyn std::error::Error>> {
// Initialize OpenTelemetry
otel::init_otel_tracing("my-service")?;
// Initialize logger
init_logger("my-service", "info")?;
Ok(())
}
```
### Extract Context from OTel Span
```rust
use shared_logging::otel;
#[cfg(feature = "otel")]
fn log_with_otel_context() {
// Extract context from current OTel span
let context = otel::extract_context_from_otel();
let logger = Logger::with_context("api", context);
logger.info("Logging with OTel context");
}
```
## Configuration
### Environment Variables
```bash
# Set log level
export RUST_LOG=info
# Set log level for specific modules
export RUST_LOG=my_service=debug,shared_logging=info
# Set log level for all dependencies
export RUST_LOG=debug
```
### Programmatic Configuration
The logger is configured at initialization:
```rust
// Set default level to "debug"
init_logger("my-service", "debug")?;
// Can be overridden by RUST_LOG environment variable
```
## Complete Example
```rust
use shared_logging::{init_logger, Logger, ContextBuilder};
fn main() {
// 1. Initialize logger once at startup
init_logger("my-service", "info").unwrap();
// 2. Create logger instances
let logger = Logger::new("main");
logger.info("Application starting");
// 3. Process a request with context
let context = ContextBuilder::new()
.generate_request_id()
.user_id("user123")
.build();
let api_logger = Logger::with_context("api", context);
// 4. Log with fields
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.log_error("Request failed", &e),
}
}
fn process_request() -> Result<(), String> {
// Your logic here
Ok(())
}
```
## Running Examples
The crate includes example code you can run:
```bash
# Basic usage
cargo run --example basic_usage
# HTTP server example
cargo run --example http_server
# Error handling example
cargo run --example error_handling
```
## Best Practices
1. **Initialize once**: Call `init_logger()` only once at application startup
2. **Use module names**: Create logger instances with descriptive module names
3. **Propagate context**: Use context for request-scoped logging
4. **Structured fields**: Use `*_with()` methods to add structured fields
5. **Error logging**: Use `log_error()` for proper error formatting
6. **Redaction**: Trust the library to redact sensitive data automatically
## Troubleshooting
### Logs not appearing
- Check `RUST_LOG` environment variable
- Ensure `init_logger()` was called
- Verify log level is appropriate
### Context not propagating
- Ensure context is set on the logger instance
- Check that you're using the same logger instance within a request
### Redaction not working
- Verify field names match redaction patterns (password, token, etc.)
- Check that values match PII/secret patterns