{"id":28528358,"url":"https://github.com/studistcorporation/studist-rack-logger","last_synced_at":"2025-10-04T14:56:35.242Z","repository":{"id":297406677,"uuid":"870066201","full_name":"StudistCorporation/studist-rack-logger","owner":"StudistCorporation","description":null,"archived":false,"fork":false,"pushed_at":"2025-06-05T09:11:18.000Z","size":21,"stargazers_count":0,"open_issues_count":4,"forks_count":0,"subscribers_count":9,"default_branch":"main","last_synced_at":"2025-06-05T10:24:47.980Z","etag":null,"topics":["rack","rack-middleware","ruby","terraform-managed"],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/StudistCorporation.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-10-09T11:38:12.000Z","updated_at":"2025-06-04T08:53:41.000Z","dependencies_parsed_at":"2025-06-05T10:36:01.341Z","dependency_job_id":null,"html_url":"https://github.com/StudistCorporation/studist-rack-logger","commit_stats":null,"previous_names":["studistcorporation/studist-rack-logger"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/StudistCorporation/studist-rack-logger","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/StudistCorporation%2Fstudist-rack-logger","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/StudistCorporation%2Fstudist-rack-logger/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/StudistCorporation%2Fstudist-rack-logger/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/StudistCorporation%2Fstudist-rack-logger/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/StudistCorporation","download_url":"https://codeload.github.com/StudistCorporation/studist-rack-logger/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/StudistCorporation%2Fstudist-rack-logger/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278328167,"owners_count":25968900,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-04T02:00:05.491Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["rack","rack-middleware","ruby","terraform-managed"],"created_at":"2025-06-09T12:41:07.636Z","updated_at":"2025-10-04T14:56:35.237Z","avatar_url":"https://github.com/StudistCorporation.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🪵 Studist Rack Logger\n\n\u003e Unified structured logging middleware for Rack applications\n\n[![Gem Version](https://badge.fury.io/rb/studist-rack-logger.svg)](https://badge.fury.io/rb/studist-rack-logger)\n[![Ruby](https://img.shields.io/badge/ruby-%3E%3D%203.0.0-ruby.svg)](https://www.ruby-lang.org/en/)\n\n## 🚀 Installation\n\n```ruby\ngem 'studist-rack-logger'\n```\n\n## ⚡ Quick Start\n\n### Simple Usage\n```ruby\n# Rack app\nuse Studist::Rack::Logger, app_id: 'my-service'\n\n# Rails\nconfig.middleware.use Studist::Rack::Logger, app_id: 'my-rails-app'\n```\n\n### Configuration DSL (Recommended)\n```ruby\n# Configure once, use everywhere\nStudist::Rack::Logger.configure do |config|\n  config.app_id = 'my-service'\n  config.format = :json\n  config.sampling_rate = 0.1  # Log 10% of requests\n  config.skip_paths = %w[/health /metrics]\n  config.extractor(:user_id) { |env, req| env['user.id'] }\nend\n\n# Then use without options\nuse Studist::Rack::Logger\n```\n\n## 🔧 Configuration\n\n### Configuration DSL (Recommended)\n\n```ruby\nStudist::Rack::Logger.configure do |config|\n  # Basic settings\n  config.app_id = 'my-service'\n  config.format = :json  # or :ltsv\n  config.logger = Rails.logger\n  config.log_version = '2.0.0'\n  \n  # Performance options\n  config.sampling_rate = 0.1          # Log 10% of requests\n  config.error_sampling_rate = 1.0     # Always log errors\n  \n  # Filtering\n  config.skip_paths = %w[/health /metrics /ping]\n  config.skip_if { |context| context[:status] == 200 \u0026\u0026 context[:request_path].start_with?('/assets') }\n  \n  # Custom extractors\n  config.extractor(:user_id) { |env, req| env['user.id'] }\n  config.extractor(:user_group_id) { |env, req| env['user.group'] }\n  config.extractor(:user_authority) { |env, req| env['user.role'] }\n  config.extractor(:normalized_uri) { |env, req| normalize_path(req.path) }\n  \n  # Custom filters\n  config.filter { |context| context[:status] != 404 }\nend\n\nuse Studist::Rack::Logger\n```\n\n### Hash-based Configuration (Legacy)\n\n```ruby\nuse Studist::Rack::Logger,\n  app_id: 'my-service',\n  format: :json,\n  logger: Rails.logger,\n  sampling_rate: 0.1,\n  user_id_extractor: -\u003e(env, req) { env['user.id'] },\n  normalized_uri_extractor: -\u003e(env, req) { normalize_path(req.path) }\n```\n\n### Configuration Options\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `app_id` | String | `\"unknown\"` | Service identifier |\n| `format` | Symbol | `:json` | Log format (`:json` or `:ltsv`) |\n| `logger` | Logger | `Logger.new($stdout)` | Logger instance |\n| `log_version` | String | `\"1.0.0\"` | Log schema version |\n| `sampling_rate` | Float | `1.0` | Request sampling rate (0.0-1.0) |\n| `error_sampling_rate` | Float | `1.0` | Error sampling rate (0.0-1.0) |\n| `skip_paths` | Array | `[]` | Paths to skip logging |\n| `user_id_extractor` | Proc | `nil` | Extract user ID from request |\n| `user_group_id_extractor` | Proc | `nil` | Extract user group ID |\n| `user_authority_extractor` | Proc | `nil` | Extract user authority |\n| `normalized_uri_extractor` | Proc | `nil` | Extract normalized URI pattern |\n| `trusted_proxies` | Array | RFC 1918 ranges | Trusted proxy IP ranges for secure IP extraction |\n\n## 📊 Log Fields\n\nOutputs **18 standardized fields** including:\n\n```json\n{\n  \"timestamp\": \"2024-01-15T10:30:45.123Z\",\n  \"app_id\": \"my-service\",\n  \"trace_id\": \"Root=1-5e1b4151-...\",\n  \"request_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n  \"status_code\": 200,\n  \"request_method\": \"GET\",\n  \"request_url\": \"https://api.example.com/users/123\",\n  \"response_time_ms\": 42,\n  \"user_id\": \"user123\",\n  \"remote_addr\": \"203.0.113.195\"\n}\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eView all fields\u003c/summary\u003e\n\n- `timestamp` - ISO8601 timestamp with milliseconds\n- `log_version` - Log schema version  \n- `app_id` - Application identifier\n- `trace_id` - Distributed tracing ID\n- `request_id` - Unique request ID (auto-generated)\n- `server_name` - Hostname\n- `status_code` - HTTP response status\n- `request_method` - HTTP method\n- `request_url` - Full request URL\n- `request_body_size` - Request payload size in bytes\n- `query_string` - URL query parameters\n- `host` - Request host header\n- `user_agent` - User agent string\n- `referer` - HTTP referer header\n- `remote_addr` - Client IP address\n- `x_forwarded_for` - X-Forwarded-For header\n- `normalized_uri` - URI pattern (via extractor)\n- `response_time_ms` - Response time in milliseconds\n- `response_body_size` - Response payload size\n- `user_id` - User identifier (via extractor)\n- `user_group_id` - User group (via extractor)\n- `user_authority` - User role/authority (via extractor)\n\n\u003c/details\u003e\n\n## 🎯 Features\n\n- **Zero-config** - Works out of the box\n- **Configuration DSL** - Powerful block-based configuration\n- **Structured logs** - JSON/LTSV formats\n- **Distributed tracing** - AWS X-Ray compatible\n- **Custom extractors** - Flexible user/URI extraction\n- **Advanced filtering** - Skip paths, conditions, and custom filters\n- **Sampling support** - Separate rates for requests and errors\n- **Trusted proxy filtering** - Secure IP extraction with Rails-compatible proxy handling\n- **Error handling** - Logs exceptions with 500 status and backtrace\n- **Performance optimized** - Hostname caching, efficient sampling\n- **Production ready** - Safe fallbacks, never fails your app\n\n## 🚀 Advanced Usage\n\n### Sampling for High-Traffic Services\n\n```ruby\nStudist::Rack::Logger.configure do |config|\n  config.app_id = 'high-traffic-api'\n  config.sampling_rate = 0.01         # Log 1% of successful requests\n  config.error_sampling_rate = 1.0     # Always log errors\nend\n```\n\n### Conditional Logging\n\n```ruby\nStudist::Rack::Logger.configure do |config|\n  config.app_id = 'my-service'\n  \n  # Skip health checks and assets\n  config.skip_paths = %w[/health /ping /assets]\n  \n  # Skip successful admin requests\n  config.skip_if do |context|\n    context[:request_path].start_with?('/admin') \u0026\u0026 \n    context[:status] == 200\n  end\n  \n  # Trusted proxy configuration for secure IP extraction\n  config.trusted_proxies = ['10.0.0.0/8', '172.16.0.0/12']\n  \n  # Only log errors and slow requests\n  config.filter do |context|\n    context[:error] || context[:response_time_ms] \u003e 1000\n  end\nend\n```\n\n### Custom Data Extraction\n\n```ruby\nStudist::Rack::Logger.configure do |config|\n  # Extract user information\n  config.extractor(:user_id) do |env, request|\n    # From JWT token\n    token = env['HTTP_AUTHORIZATION']\u0026.sub(/^Bearer /, '')\n    JWT.decode(token)\u0026.dig(0, 'user_id') if token\n  end\n  \n  # Extract tenant ID\n  config.extractor(:tenant_id) do |env, request|\n    request.headers['X-Tenant-ID'] || \n    request.subdomain\n  end\n  \n  # Normalize API routes\n  config.extractor(:normalized_uri) do |env, request|\n    path = request.path\n    path.gsub(/\\/\\d+/, '/:id')          # /users/123 → /users/:id\n        .gsub(/\\/[a-f0-9-]{36}/, '/:uuid') # UUIDs → :uuid\n  end\nend\n```\n\n### Rails Integration\n\n```ruby\n# config/application.rb\nclass Application \u003c Rails::Application\n  # Configure logger\n  Studist::Rack::Logger.configure do |config|\n    config.app_id = Rails.application.class.module_parent_name.downcase\n    config.logger = Rails.logger\n    config.sampling_rate = Rails.env.production? ? 0.1 : 1.0\n    config.skip_paths = %w[/health /assets]\n    \n    # Configure trusted proxies for production\n    config.trusted_proxies = Rails.env.production? ? \n      ['10.0.0.0/8', '172.16.0.0/12'] : nil\n    \n    # Extract user from Devise/session\n    config.extractor(:user_id) do |env, request|\n      env['warden']\u0026.user\u0026.id\n    end\n  end\n  \n  # Add middleware\n  config.middleware.use Studist::Rack::Logger\nend\n```\n\n## 🔄 Migration from Hash Configuration\n\n### Before (Hash-based)\n```ruby\nuse Studist::Rack::Logger,\n  app_id: 'my-service',\n  user_id_extractor: -\u003e(env, req) { env['user.id'] },\n  normalized_uri_extractor: -\u003e(env, req) { normalize_path(req.path) }\n```\n\n### After (DSL)\n```ruby\nStudist::Rack::Logger.configure do |config|\n  config.app_id = 'my-service'\n  config.extractor(:user_id) { |env, req| env['user.id'] }\n  config.extractor(:normalized_uri) { |env, req| normalize_path(req.path) }\nend\n\nuse Studist::Rack::Logger\n```\n\n**Benefits of DSL:**\n- Global configuration reuse\n- Advanced filtering capabilities  \n- Better validation and error messages\n- More readable and maintainable\n- Supports complex conditional logic\n\n---\n\n\u003cdiv align=\"center\"\u003e\nMade with ❤️ by \u003ca href=\"https://studist.jp\"\u003eStudist\u003c/a\u003e\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstudistcorporation%2Fstudist-rack-logger","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstudistcorporation%2Fstudist-rack-logger","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstudistcorporation%2Fstudist-rack-logger/lists"}