# 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