# semantic_logger keeps payloads searchable and the app unblocked

> Semantic Logger is a Ruby and Rails logging framework that carries structured payloads, exceptions, durations and metrics through to the destination instead of flattening them into text, and writes them from a background thread fed by an in-memory queue. It ships appenders for everything from a file to ElasticSearch, Splunk, Sentry and OpenTelemetry.

**reidmorrison/semantic_logger** — Semantic Logger is a high-performance, asynchronous structured logging framework for Ruby and Rails.

- Repository: https://github.com/reidmorrison/semantic_logger
- Website: https://logger.reidmorrison.com/
- Stars: 956 · Forks: 140
- Language: Ruby
- License: Apache-2.0
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/reidmorrison-semantic-logger

## Payloads survive to the destination instead of being flattened

The first thing that separates Semantic Logger from an ordinary logger is what a log call can carry.

Along with the text message, each log entry can carry a payload, which is any Hash, an exception, a duration, metrics, and tags. The README is explicit that this data is preserved all the way to the destination, so it stays searchable instead of being flattened into text.

That distinction is the whole point. A conventional logger call produces a string, and once the values are interpolated into it, a field like a duration or an order id is no longer a field. To find slow requests you grep for a format you agreed on in advance, and to count failures by type you write a parser. With structured logging, the same information arrives as data, which is what makes an ElasticSearch or Splunk index useful rather than a pile of text to search with regular expressions.

The API makes that explicit rather than implicit:

```ruby
require "semantic_logger"

SemanticLogger.default_level = :info
SemanticLogger.add_appender(io: $stdout, formatter: :color)

logger = SemanticLogger["MyApp"]

logger.info("Queried users table", duration: 54, result: :ok, table: "users")
```

The keyword arguments after the message are the payload, and they are typed data rather than text. There is a separate call for exceptions, which carries the exception object with its full backtrace, and a measure call that times a block and logs the result:

```ruby
logger.error("Failed to process order", exception: exception, order_id: 42)
logger.measure_info("Reindexing users") { reindex_users }
```

That last one is a small convenience with a large effect on adoption, because measuring something by hand is where most teams give up on logging durations at all.

## A queue and a thread that never touch the request path

The second difference from an ordinary logger is where the write happens.

Log events are pushed onto an in-memory queue and written to their destinations by a separate background thread, so the application is not blocked while logs are written. The README states that Semantic Logger can log thousands of lines per second without slowing the application down, and explains that traditional logging systems make the application wait while the log information is being saved.

That is a plain architectural statement about where the blocking happens, and it is the reason a background thread exists at all. The cost is that the queue is in memory in the same process, which has two consequences worth stating plainly.

The first is that a process that exits or crashes loses whatever was still queued. There is no durability step in the description, and an in-memory queue cannot survive the thing that would most benefit from the logs. For a web application that is usually acceptable, since a crash is visible and debuggable in other ways, but for a batch job or a data pipeline where the crash is the interesting event, it is a real gap.

The second is that the queue is shared state between your application threads and the logging thread. Whatever the queue's behaviour is under pressure, it is behaviour you do not control from application code.

None of this is hidden in the documentation, and it is the right way to think about the tool. Semantic Logger is not a durable log pipeline with acknowledgements. It is a way to get structured log data out of the request path cheaply, with the usual trade-off that the data in flight is the data at risk.

## Appenders cover the destinations teams actually run

The list of destinations is where this project earns its place in a real infrastructure.

Logging to the following is supported out of the box: File, Screen, ElasticSearch, with the note to use it with Kibana for dashboards and visualizations, Graylog, BugSnag, NewRelic, Splunk, MongoDB, Honeybadger for exceptions and events, Sentry with both the legacy sentry-raven and the modern sentry-ruby gem, HTTP, TCP, UDP, Syslog, CloudWatch Logs and OpenTelemetry.

Two entries on that list are of a different kind. You can add any existing Ruby logger as another destination, which is the escape hatch for a destination the project does not support. And there is a roll-your-own option for anything else.

The range tells you what this is for. A file appender and a screen appender are the local development cases. ElasticSearch, Graylog, Splunk, MongoDB and CloudWatch Logs are the hosted aggregation cases. BugSnag, Honeybadger, NewRelic and Sentry are the error tracking cases. HTTP, TCP and UDP are the cases where you need to ship to something the project has never heard of, and Syslog is the case where your infrastructure already has a syslog pipeline.

OpenTelemetry is the newest-looking entry on the list and it matters, because it is the direction the industry is moving for log export. The repository topics name the same set, with elasticsearch, splunk, syslog and bugsnag among them.

The practical value of the list is not that every entry is equally good, it is that you can pick one and get structured data into the system you already run, instead of shipping JSON strings to a place that expects plain text.

## Appender gems stay out of the gem until you enable them

The optional dependency section is short and useful, because it tells you exactly what each appender costs you.

The README states that the listed gems are only required when their corresponding appenders are being used, and are therefore not automatically included by the gem. The mapping runs from BugSnag to the bugsnag gem, MongoDB to mongo of version 1.9.2 or above, Honeybadger to honeybadger, NewRelic to newrelic_rpm, Syslog to syslog_protocol of version 0.9.2 or above with net_tcp_client for a remote syslogng server over TCP or UDP, Splunk to splunk-sdk-ruby, ElasticSearch to elasticsearch, OpenSearch to opensearch-ruby, Kafka to ruby-kafka, the legacy Sentry appender to sentry-raven, the Sentry appender to sentry-ruby, and OpenTelemetry to opentelemetry-logs-sdk plus an exporter such as opentelemetry-exporter-otlp-logs.

Two details are worth noting. The version floors are stated for the appenders with known compatibility constraints, mongo at 1.9.2 and syslog_protocol at 0.9.2, so an older environment is a problem to solve before you enable those. And the legacy Sentry appender is marked deprecated, which tells you the project treats sentry-raven as a compatibility path rather than a supported target.

The Kafka entry is also worth calling out, because it is not in the destination list in the earlier section. A Kafka appender means Semantic Logger can feed a streaming pipeline rather than only a queryable store, which changes what you can build on the log stream.

The dependency model is what keeps the base gem usable. You install one thing, and only the appenders you switch on bring their own dependency tree.

## Rails users should install a different gem

If the application is a Rails app, the README gives a direct instruction that is easy to miss.

When running Rails, use rails_semantic_logger instead, since it replaces the Rails default logger with Semantic Logger automatically. The Rails page in the documentation is where the configuration instructions live, and the base gem is meant for standalone applications.

The difference matters in practice. Wiring Semantic Logger into a Rails application by hand means intercepting the framework's own logging calls, which is exactly what the sibling gem does for you. Using the base gem in a Rails app means either running two logging systems side by side or doing work the other gem already handles.

The project has a wider orbit than the two gems. The README points at Rocket Job as the sister project, Ruby's missing batch system, which fully supports Semantic Logger when running jobs in the background, with complete support for job metrics sent via Semantic Logger to your favourite dashboards.

That combination covers the two places where log data usually goes missing in a Rails application, the request cycle and the background job. If your application has a job queue, that is a reason to look at both gems together rather than only at the logging one.

## Installing the gem and reading the documentation

Installation is a single gem command:

```bash
gem install semantic_logger
```

For a standalone application, the README shows the shape of the configuration, setting the global default log level and adding an appender:

```ruby
require 'semantic_logger'

# Set the global default log level
SemanticLogger.default_level = :trace

# Log to a file, and use the colorized formatter
SemanticLogger.add_appender(file_name: 'development.log', formatter: :color)
```

The global default level is set once for the process, and appenders are added explicitly, so nothing is written anywhere until you say so. That is a deliberate design and it means the configuration is visible in one place rather than inherited from a framework default.

The documentation is unusually well organised for a gem this size. There is an Introduction and a Programmer's Guide as separate pages, with the full guide at logger.reidmorrison.com. The README also links a documentation index for AI assistants at llms.txt and the complete documentation in a single file at llms-full.txt, and notes that the same pages are included as markdown inside the installed gem under docs/. Having the documentation ship with the gem means it is available offline and in the exact version you installed.

The repository is consistent with that. Beyond the usual lib/, spec/ and test/ directories there is a docs/ directory, an AGENTS.md and a CLAUDE.md for tooling that reads a repository, a .claude/ directory, and a docker-compose.yaml that starts a mongodb service for the appender tests:

```yaml
name: 'semanticlogger'
services:
  mongodb:
    image: mongo
    ports:
      - "27017:27017"
```

## Versioning, licences and the absence of GitHub releases

The maintenance picture is good, with one structural gap.

The last push to the repository was on 2026-10-07, the repository is not archived, and the work is visible in the repository files. There is a CHANGELOG.md, a CONTRIBUTING.md that explains how to set up the project, run the tests and gives an overview of the architecture including a class diagram, a Rakefile, a Gemfile, a semantic_logger.gemspec, and both .rubocop.yml and .rubocop_todo.yml for style enforcement. The README badge describes the status as Production Ready.

The project uses Semantic Versioning, and there is an Upgrading Guide for instructions on upgrading between major versions. That is the version history you will actually use.

The gap is that the repository has no GitHub releases at all. Everything ships through RubyGems, where the gem version is published, and the version badge in the README points at the gem page on rubygems.org rather than at a tag. That is normal for a gem, and it means you install a version and read the changelog rather than picking a commit.

The licence is Apache-2.0, which includes an explicit patent grant and requires that modifications carry the notice. For a logging framework you are likely to vendor or depend on inside a larger application, that is a comfortable choice, and unlike a permissive MIT licence it also gives you an explicit patent position to point at in a legal review.

One last thing to check before you commit. The appender list is where the risk sits for most adopters, since each one talks to an external service with its own client library and its own version compatibility. The optional dependency section tells you the floors, and the upgrading guide tells you what moved between major versions.

## Conclusion

semantic_logger suits Ruby and Rails teams whose log data is currently trapped inside interpolated strings and who need to search on fields rather than grep text, and it suits services where blocking on log writes is a measurable cost. It is the wrong choice for a small script or a library that should not impose a logging framework, and the wrong choice if you need per-appender delivery guarantees, since the queue model means an application crash can lose what is still in memory. Before adopting it, read the optional dependency list so you only pull in the gems for the appenders you actually enable, install rails_semantic_logger rather than the base gem when the application is a Rails app, and check the Upgrading Guide if you are moving between major versions, because this project follows Semantic Versioning and its release history lives in the changelog rather than in tagged GitHub releases.

## FAQ

### How do I install semantic_logger and add an appender?

Run gem install semantic_logger, then require 'semantic_logger' in your application, set SemanticLogger.default_level and add an appender with SemanticLogger.add_appender, for example SemanticLogger.add_appender(file_name: 'development.log', formatter: :color).

### What is the difference between Semantic Logger and a standard Ruby logger?

It logs structured data rather than only strings, so a log entry can carry a payload Hash, an exception, a duration, metrics and tags that stay searchable all the way to the destination, and it logs asynchronously through an in-memory queue serviced by a background thread.

### Which destinations does semantic_logger support?

File, Screen, ElasticSearch, Graylog, BugSnag, NewRelic, Splunk, MongoDB, Honeybadger, Sentry with both sentry-raven and sentry-ruby, HTTP, TCP, UDP, Syslog, CloudWatch Logs and OpenTelemetry are supported out of the box, and you can add any existing Ruby logger or roll your own.

### Should I use rails_semantic_logger instead in a Rails app?

Yes. The README says to use rails_semantic_logger when running Rails, since it replaces the Rails default logger with Semantic Logger automatically. The base gem is for standalone applications.

### Do I need to install extra gems for the appenders I use?

Yes, each appender has its own gem, and the README states they are only required when the corresponding appender is used. For example the Splunk appender needs splunk-sdk-ruby, the MongoDB appender needs mongo 1.9.2 or above, and the OpenTelemetry appender needs opentelemetry-logs-sdk plus an exporter.

## Sources

- [Issues](https://github.com/reidmorrison/semantic_logger/issues)
- [License: Apache-2.0](https://github.com/reidmorrison/semantic_logger/blob/main/LICENSE)
- [Project website](https://logger.reidmorrison.com/)
- [README](https://github.com/reidmorrison/semantic_logger/blob/main/README.md)
- [reidmorrison/semantic_logger on GitHub](https://github.com/reidmorrison/semantic_logger)

---

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