# lograge: Replacing Rails' Multi-Line Request Logs with a Single Parseable Line

> lograge replaces Rails' verbose per-request logging with a single key-value line per request, making production logs from multi-process deployments parseable without a log aggregator. It supports Rails 5.2 through 8.1 and MRI, JRuby, and TruffleRuby.

**roidrage/lograge** — An attempt to tame Rails' default policy to log everything.

- Repository: https://github.com/roidrage/lograge
- Website: http://www.paperplanes.de/2012/3/14/on-notifications-logsubscribers-and-bringing-sanity-to-rails-logging.html
- Stars: 3,573 · Forks: 298
- Language: Ruby
- License: MIT
- Published: 2026-09-23 · Updated: 2026-09-23 · Language: en
- Canonical page: https://hysenlabs.com/projects/roidrage-lograge

## Why Rails' Default Logging Breaks in Multi-Process Deployments

Rails logs each request as a sequence of lines: a Started line, a Processing line, one line per rendered partial, and a Completed line. In development, that sequence is easy to read. In a production environment running ten or twenty Puma workers writing to a shared log file, lines from different requests interleave. The result is a log that cannot be reliably parsed by a log shipper, and where tracing a single request requires manually correlating timestamps across dozens of interleaved lines.

lograge replaces that multi-line sequence with a single line per request. The README shows the contrast directly. Instead of six or more lines, you get:

```
method=GET path=/ format=json controller=HomeController action=index status=200 duration=79.0 view=78.8 db=0.0
```

Each field is a key=value pair. The format is explicitly modeled on the Heroku router log format. No timestamp is added by default, on the assumption that the log formatter or log shipper handles timestamps.

## How lograge Replaces the Rails Request Subscriber

Rails uses ActiveSupport::Notifications and log subscribers to produce its request logs. lograge hooks into the same notification system and replaces the default subscriber. It subscribes to action_controller events and compresses the full event payload into a single hash, then passes that hash to a formatter.

The default formatter produces the key=value output. The raw formatter returns the Ruby hash itself, which allows downstream code to serialize it differently. Other formatters (JSON, Logstash, LTSV, Graylog2, CEE, Lines, KeyValueDeep) redirect the same data to structured output formats.

The mechanism means lograge does not replace Rails' logger; it replaces what Rails logs at the request subscriber level. The underlying logger (ActiveSupport::Logger, or whatever adapter you have configured) still handles output. This is relevant because you can run lograge alongside the original Rails logger on a separate log file if needed.

## Installing lograge and Enabling It in Your Rails Application

Add the gem to your Gemfile:

```ruby
gem "lograge"
```

Enable it in an initializer or in the environment-specific configuration:

```ruby
# config/initializers/lograge.rb
# OR
# config/environments/production.rb
Rails.application.configure do
  config.lograge.enabled = true
end
```

For Rails API-only applications that inherit from ActionController::API, you must also set the base controller class:

```ruby
# config/initializers/lograge.rb
Rails.application.configure do
  config.lograge.base_controller_class = 'ActionController::API'
end
```

If your application uses multiple base controller classes, pass an array instead:

```ruby
Rails.application.configure do
  config.lograge.base_controller_class = ['ActionController::API', 'ActionController::Base']
end
```

After enabling lograge, restart the Rails server. Each incoming request will produce one log line instead of the previous multi-line sequence.

## Adding Custom Fields and Skipping Specific Requests

The default log line includes method, path, format, controller, action, status, and timing fields. To add application-specific fields, use custom_options with a lambda that returns a hash. The lambda receives the full ActiveSupport::Notifications event, so you can read any field from event.payload:

```ruby
# config/environments/staging.rb
Rails.application.configure do
  config.lograge.enabled = true

  config.lograge.custom_options = lambda do |event|
    {:name => "value", :timing => some_float.round(2), :host => event.payload[:host]}
  end
end
```

To add a timestamp to each line:

```ruby
Rails.application.configure do
  config.lograge.enabled = true

  config.lograge.custom_options = lambda do |event|
    { time: Time.now }
  end
end
```

To make additional data available in event.payload without writing it directly in the lograge configuration, override append_info_to_payload in your ApplicationController:

```ruby
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  def append_info_to_payload(payload)
    super
    payload[:host] = request.host
  end
end
```

That payload[:host] value is then readable in custom_options via event.payload[:host].

For fields that require access to controller methods such as current_user or request, use custom_payload instead:

```ruby
Rails.application.configure do
  config.lograge.enabled = true

  config.lograge.custom_payload do |controller|
    {
      host: controller.request.host,
      user_id: controller.current_user.try(:id)
    }
  end
end
```

To skip logging for specific controller actions or based on arbitrary event data:

```ruby
Rails.application.configure do
  config.lograge.enabled = true

  config.lograge.ignore_actions = ['HomeController#index', 'AController#an_action']
  config.lograge.ignore_custom = lambda do |event|
    # return true here if you want to ignore based on the event
  end
end
```

## Available Formatters from Key-Value to Logstash

lograge ships with nine formatter classes. The default is KeyValue, which produces the key=value pairs shown in the README. JSON output uses Lograge::Formatters::Json. For Logstash-compatible output, use Lograge::Formatters::Logstash, which requires adding the logstash-event gem:

```ruby
gem "logstash-event"
```

The full formatter list from the README:

```ruby
Lograge::Formatters::Lines.new
Lograge::Formatters::Cee.new
Lograge::Formatters::Graylog2.new
Lograge::Formatters::KeyValue.new  # default lograge format
Lograge::Formatters::KeyValueDeep.new
Lograge::Formatters::Json.new
Lograge::Formatters::Logstash.new
Lograge::Formatters::LTSV.new
Lograge::Formatters::Raw.new       # Returns a ruby hash object
```

Set the formatter in your environment configuration:

```ruby
Rails.application.configure do
  config.lograge.formatter = Lograge::Formatters::Logstash.new
end
```

## Keeping the Original Rails Log Alongside lograge

lograge replaces the request log subscriber by default. If you need to preserve the original verbose Rails output for a specific environment (for example, to keep a debug log while shipping structured logs separately), the README documents this:

```ruby
Rails.application.configure do
  config.lograge.keep_original_rails_log = true

  config.lograge.logger = ActiveSupport::Logger.new "#{Rails.root}/log/lograge_#{Rails.env}.log"
end
```

With this configuration, the original Rails multi-line output continues to the default log destination, while lograge writes its single-line output to a separate file. This is useful during a migration period where both formats need to coexist.

## Where lograge Falls Short and How Semantic Logger Differs

lograge replaces request subscriber output. It does not touch framework-generated logs outside the request cycle: startup messages, cache store logs, mailer deliveries, and similar output continue unmodified. The README does not document support for background job logging (Sidekiq, Resque, or similar).

lograge also does not add a request token or request ID by default. In a multi-process environment where you need to correlate a request log line with an error or a background job triggered by that request, you must add a correlation token yourself through custom_options or custom_payload. Rails provides ActionDispatch::RequestId middleware that sets a request_id, which you can pull from the controller or event payload.

Semantic Logger (from the rails_semantic_logger gem) takes a different approach. It replaces Rails' logger entirely rather than replacing a single log subscriber, and it wraps every log call in the application, not just controller requests. This means Semantic Logger captures ActiveRecord queries, cache operations, and mailer events with structured output by default. The trade-off is that Semantic Logger requires replacing the underlying logger adapter, which is a larger change to the application than adding lograge.

## Supported Versions, Maintenance, and License

The last push to the repository was on 2026-09-21. The repository is not archived. A CHANGELOG.md is included and the repository uses a CI workflow file.

Supported Rails versions are 5.2, 6.0, 6.1, 7.0, 7.1, 7.2, 8.0, and 8.1. Supported Ruby versions cover MRI 2.6 through 4.0, JRuby 9.4 and 10.0, and TruffleRuby 34.0. Rails main (edge) and Ruby head are tested in CI on a best-effort basis and are explicitly noted as not yet officially supported and allowed to fail.

The project is licensed under the MIT license. There are no GitHub releases; the gem is published to RubyGems and consumed through a Gemfile entry.

## Conclusion

lograge suits production Rails applications where multi-process log output is unparseable or too noisy for monitoring pipelines. Engineers who need background job logs or database query details within each request line will find lograge intentionally omits those; the README does not document Sidekiq or background job logging. Before enabling it, verify that config.lograge.custom_options captures the request fields your monitoring pipeline expects.

## FAQ

### Does lograge work with Rails API-only applications?

Yes, but you must set config.lograge.base_controller_class to 'ActionController::API' in an initializer. If your application uses multiple base classes, pass them as an array.

### How do I add a user ID or request host to lograge output?

Use config.lograge.custom_payload with a block that receives the controller object. The block can access controller.request and controller.current_user. The hash returned by the block is merged into the log line automatically.

### Can lograge output JSON for use with a log aggregator?

Yes. Set config.lograge.formatter = Lograge::Formatters::Json.new for standard JSON output. For Logstash-compatible JSON, use Lograge::Formatters::Logstash.new and add the logstash-event gem to your Gemfile.

## Sources

- [Issues](https://github.com/roidrage/lograge/issues)
- [License: MIT](https://github.com/roidrage/lograge/blob/master/LICENSE)
- [Project website](http://www.paperplanes.de/2012/3/14/on-notifications-logsubscribers-and-bringing-sanity-to-rails-logging.html)
- [README](https://github.com/roidrage/lograge/blob/master/README.md)
- [roidrage/lograge on GitHub](https://github.com/roidrage/lograge)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/roidrage-lograge
