https://github.com/studistcorporation/studist-rack-logger
https://github.com/studistcorporation/studist-rack-logger
rack rack-middleware ruby terraform-managed
Last synced: 8 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/studistcorporation/studist-rack-logger
- Owner: StudistCorporation
- Created: 2024-10-09T11:38:12.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2025-06-05T09:11:18.000Z (about 1 year ago)
- Last Synced: 2025-06-05T10:24:47.980Z (about 1 year ago)
- Topics: rack, rack-middleware, ruby, terraform-managed
- Homepage:
- Size: 20.5 KB
- Stars: 0
- Watchers: 9
- Forks: 0
- Open Issues: 4
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# 🪵 Studist Rack Logger
> Unified structured logging middleware for Rack applications
[](https://badge.fury.io/rb/studist-rack-logger)
[](https://www.ruby-lang.org/en/)
## 🚀 Installation
```ruby
gem 'studist-rack-logger'
```
## ⚡ Quick Start
### Simple Usage
```ruby
# Rack app
use Studist::Rack::Logger, app_id: 'my-service'
# Rails
config.middleware.use Studist::Rack::Logger, app_id: 'my-rails-app'
```
### Configuration DSL (Recommended)
```ruby
# Configure once, use everywhere
Studist::Rack::Logger.configure do |config|
config.app_id = 'my-service'
config.format = :json
config.sampling_rate = 0.1 # Log 10% of requests
config.skip_paths = %w[/health /metrics]
config.extractor(:user_id) { |env, req| env['user.id'] }
end
# Then use without options
use Studist::Rack::Logger
```
## 🔧 Configuration
### Configuration DSL (Recommended)
```ruby
Studist::Rack::Logger.configure do |config|
# Basic settings
config.app_id = 'my-service'
config.format = :json # or :ltsv
config.logger = Rails.logger
config.log_version = '2.0.0'
# Performance options
config.sampling_rate = 0.1 # Log 10% of requests
config.error_sampling_rate = 1.0 # Always log errors
# Filtering
config.skip_paths = %w[/health /metrics /ping]
config.skip_if { |context| context[:status] == 200 && context[:request_path].start_with?('/assets') }
# Custom extractors
config.extractor(:user_id) { |env, req| env['user.id'] }
config.extractor(:user_group_id) { |env, req| env['user.group'] }
config.extractor(:user_authority) { |env, req| env['user.role'] }
config.extractor(:normalized_uri) { |env, req| normalize_path(req.path) }
# Custom filters
config.filter { |context| context[:status] != 404 }
end
use Studist::Rack::Logger
```
### Hash-based Configuration (Legacy)
```ruby
use Studist::Rack::Logger,
app_id: 'my-service',
format: :json,
logger: Rails.logger,
sampling_rate: 0.1,
user_id_extractor: ->(env, req) { env['user.id'] },
normalized_uri_extractor: ->(env, req) { normalize_path(req.path) }
```
### Configuration Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `app_id` | String | `"unknown"` | Service identifier |
| `format` | Symbol | `:json` | Log format (`:json` or `:ltsv`) |
| `logger` | Logger | `Logger.new($stdout)` | Logger instance |
| `log_version` | String | `"1.0.0"` | Log schema version |
| `sampling_rate` | Float | `1.0` | Request sampling rate (0.0-1.0) |
| `error_sampling_rate` | Float | `1.0` | Error sampling rate (0.0-1.0) |
| `skip_paths` | Array | `[]` | Paths to skip logging |
| `user_id_extractor` | Proc | `nil` | Extract user ID from request |
| `user_group_id_extractor` | Proc | `nil` | Extract user group ID |
| `user_authority_extractor` | Proc | `nil` | Extract user authority |
| `normalized_uri_extractor` | Proc | `nil` | Extract normalized URI pattern |
| `trusted_proxies` | Array | RFC 1918 ranges | Trusted proxy IP ranges for secure IP extraction |
## 📊 Log Fields
Outputs **18 standardized fields** including:
```json
{
"timestamp": "2024-01-15T10:30:45.123Z",
"app_id": "my-service",
"trace_id": "Root=1-5e1b4151-...",
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"status_code": 200,
"request_method": "GET",
"request_url": "https://api.example.com/users/123",
"response_time_ms": 42,
"user_id": "user123",
"remote_addr": "203.0.113.195"
}
```
View all fields
- `timestamp` - ISO8601 timestamp with milliseconds
- `log_version` - Log schema version
- `app_id` - Application identifier
- `trace_id` - Distributed tracing ID
- `request_id` - Unique request ID (auto-generated)
- `server_name` - Hostname
- `status_code` - HTTP response status
- `request_method` - HTTP method
- `request_url` - Full request URL
- `request_body_size` - Request payload size in bytes
- `query_string` - URL query parameters
- `host` - Request host header
- `user_agent` - User agent string
- `referer` - HTTP referer header
- `remote_addr` - Client IP address
- `x_forwarded_for` - X-Forwarded-For header
- `normalized_uri` - URI pattern (via extractor)
- `response_time_ms` - Response time in milliseconds
- `response_body_size` - Response payload size
- `user_id` - User identifier (via extractor)
- `user_group_id` - User group (via extractor)
- `user_authority` - User role/authority (via extractor)
## 🎯 Features
- **Zero-config** - Works out of the box
- **Configuration DSL** - Powerful block-based configuration
- **Structured logs** - JSON/LTSV formats
- **Distributed tracing** - AWS X-Ray compatible
- **Custom extractors** - Flexible user/URI extraction
- **Advanced filtering** - Skip paths, conditions, and custom filters
- **Sampling support** - Separate rates for requests and errors
- **Trusted proxy filtering** - Secure IP extraction with Rails-compatible proxy handling
- **Error handling** - Logs exceptions with 500 status and backtrace
- **Performance optimized** - Hostname caching, efficient sampling
- **Production ready** - Safe fallbacks, never fails your app
## 🚀 Advanced Usage
### Sampling for High-Traffic Services
```ruby
Studist::Rack::Logger.configure do |config|
config.app_id = 'high-traffic-api'
config.sampling_rate = 0.01 # Log 1% of successful requests
config.error_sampling_rate = 1.0 # Always log errors
end
```
### Conditional Logging
```ruby
Studist::Rack::Logger.configure do |config|
config.app_id = 'my-service'
# Skip health checks and assets
config.skip_paths = %w[/health /ping /assets]
# Skip successful admin requests
config.skip_if do |context|
context[:request_path].start_with?('/admin') &&
context[:status] == 200
end
# Trusted proxy configuration for secure IP extraction
config.trusted_proxies = ['10.0.0.0/8', '172.16.0.0/12']
# Only log errors and slow requests
config.filter do |context|
context[:error] || context[:response_time_ms] > 1000
end
end
```
### Custom Data Extraction
```ruby
Studist::Rack::Logger.configure do |config|
# Extract user information
config.extractor(:user_id) do |env, request|
# From JWT token
token = env['HTTP_AUTHORIZATION']&.sub(/^Bearer /, '')
JWT.decode(token)&.dig(0, 'user_id') if token
end
# Extract tenant ID
config.extractor(:tenant_id) do |env, request|
request.headers['X-Tenant-ID'] ||
request.subdomain
end
# Normalize API routes
config.extractor(:normalized_uri) do |env, request|
path = request.path
path.gsub(/\/\d+/, '/:id') # /users/123 → /users/:id
.gsub(/\/[a-f0-9-]{36}/, '/:uuid') # UUIDs → :uuid
end
end
```
### Rails Integration
```ruby
# config/application.rb
class Application < Rails::Application
# Configure logger
Studist::Rack::Logger.configure do |config|
config.app_id = Rails.application.class.module_parent_name.downcase
config.logger = Rails.logger
config.sampling_rate = Rails.env.production? ? 0.1 : 1.0
config.skip_paths = %w[/health /assets]
# Configure trusted proxies for production
config.trusted_proxies = Rails.env.production? ?
['10.0.0.0/8', '172.16.0.0/12'] : nil
# Extract user from Devise/session
config.extractor(:user_id) do |env, request|
env['warden']&.user&.id
end
end
# Add middleware
config.middleware.use Studist::Rack::Logger
end
```
## 🔄 Migration from Hash Configuration
### Before (Hash-based)
```ruby
use Studist::Rack::Logger,
app_id: 'my-service',
user_id_extractor: ->(env, req) { env['user.id'] },
normalized_uri_extractor: ->(env, req) { normalize_path(req.path) }
```
### After (DSL)
```ruby
Studist::Rack::Logger.configure do |config|
config.app_id = 'my-service'
config.extractor(:user_id) { |env, req| env['user.id'] }
config.extractor(:normalized_uri) { |env, req| normalize_path(req.path) }
end
use Studist::Rack::Logger
```
**Benefits of DSL:**
- Global configuration reuse
- Advanced filtering capabilities
- Better validation and error messages
- More readable and maintainable
- Supports complex conditional logic
---