Library / SDK
outr/scribe avatar
outr/scribe

Scribe: a Scala logging library built from the ground up for speed

The fastest logging library in the world. Built from scratch in Scala and programmatically configurable.

553 stars43 forksScalaMIT

At a glance

What is it?
Scribe is a logging library for Scala, Scala.js and Scala Native that is not a wrapper around SLF4J, Log4j or Logback, and its selling point is that macros do the work at compile time so that logging a disabled level costs nothing and the class, method and line number are known without walking the stack at runtime. It is MIT licensed, published to Maven Central, and has a companion module for SLF4J interoperability.
Who is it for?
Adopt Scribe if you are writing Scala and want a logger that is fast enough to leave everywhere, that costs nothing for a disabled level, that knows the class, method and line of a call site without walking the stack, and that you can reconfigure at runtime without a restart.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 5 days ago.
What is it written in?
Mainly Scala, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The premise: not a wrapper

Scribe is described as a completely different way of thinking about logging. Instead of wrapping around existing logging frameworks and bearing their performance and design flaws, it is built from the ground up to provide fast and effective logging in Scala, Scala.js and Scala Native without the need of configuration files or additional dependencies.

That last clause is the design position, and it is unusual for a logging library. Most loggers are configured through a file, an XML or properties document, or a builder that the application calls once at startup. Scribe's claim is that all management of logging can be handled programmatically, and that classic configuration can be used if you want it, but in Scala itself, giving the developer the freedom to use whatever configuration framework, if any, they choose.

The word without is doing real work in the original sentence. A logger that needs no configuration file and no extra dependencies is one you can drop into a library and have it work, rather than one that obliges you to reconcile its configuration with the rest of your application's.

The primary language is Scala, the licence is MIT, the default branch is master, and the recent releases are 3.18.0 from 2026-03-19, 3.17.0 from 2025-07-19 and 3.16.1 from 2025-04-04. The last push was on 2026-10-18 with the project not archived.

There is a version discrepancy worth knowing about. The README's dependency snippets show 3.19.0, the badges point at the 2.13 line for Maven Central and Javadoc, and the recent release list ends at 3.18.0. So the README documents a version ahead of the last tagged release, which is normal for a library whose README is updated between releases, but it means the badge links are not the version you would install from the snippet.

Availability is stated separately and is worth noting: Scribe is available on the JVM, Scala.js and Scala Native, with cross-compiling for Scala 2.12, 2.13 and 3.

Getting set up, and the one-line usage

The quick start is short enough to read in one sitting, and there are three dependency configurations depending on what you need.

For a JVM-only project:

scala
libraryDependencies += "com.outr" %% "scribe" % "3.19.0"

For a cross-platform project covering the JVM, JS, and/or Native:

scala
libraryDependencies += "com.outr" %%% "scribe" % "3.19.0"

The difference is the sbt operator. The double percent is the Scala.js and Native operator that selects the right artefact per platform, and the triple percent is the cross-project operator that works across JVM, JS and Native at once. That is the whole cross-compilation story, and it is a build-level concern rather than a source-level one.

And for interoperability with SLF4J, so that libraries using other loggers can talk to yours:

scala
libraryDependencies += "com.outr" %% "scribe-slf4j" % "3.19.0"

That third module is the pragmatic part of the story. Scribe is built from the ground up rather than wrapping an existing framework, which is a purity argument, but in a real application most of your dependencies log through SLF4J. The scribe-slf4j module bridges that, so a Scribe-configured application still receives the log events from libraries that do not know about Scribe.

Usage is one line:

scala
scribe.info("Yes, it's that simple!")

The API is a global object with a method per level, so there is no logger instance to pass around and no factory to wire. That is the ergonomic trade that comes with the programmatic configuration model: because there is no configuration file to reconcile, the logger can be reachable from anywhere.

Then there is an SBT tip that only matters if you develop with SBT, and it is a good example of a small library caring about how it looks in a developer's terminal. The default logger supports auto-line wrapping, but in SBT the [info] prefixes cause that to get messed up, and the recommended setting is:

sbt
outputStrategy := Some(StdoutOutput)

Setting outputStrategy to StdoutOutput disables the [info] and [error] prefixes so logging looks correct when running your application within SBT.

The topic list mentions logback alongside slf4j, which reflects the comparison the README makes: Scribe is positioned against the JVM logging frameworks rather than as one more option among them.

Why another logging framework, and the five numbered problems

The README anticipates the objection directly, and lists six problems with the wrapper approach.

The framing is that nearly every Scala logging framework is mostly just a wrapper around Java logging frameworks, usually SLF4J, Log4J or Logback, and that this comes with a few problems:

scala
 1. No support for Scala.js
 2. No support for Scala Native
 3. Performance cost (Blog Post: https://matthicks.com/2018/02/06/scribe-2-0-fastest-jvm-logger-in-the-world/)
 4. Additional dependencies
 5. Substantial cost logging method and line numbers
 6. Lack of programmatic configuration support

Each of the six is a different kind of objection, and the list is more interesting than it first appears because two of the items are not really about performance.

The first two are platform gaps. No support for Scala.js, and no support for Scala Native. A wrapper around a Java library cannot run on Scala.js, because Scala.js has no JVM and no Java bytecode, and it cannot run on Native for the same reason. So for a Scala project that targets the browser or compiles to native code, a wrapper is not a preference but a blocker.

The third is performance, and it comes with a link to a blog post from 2018 announcing Scribe 2.0 as the fastest JVM logger in the world. The fourth is additional dependencies, which is the argument that a wrapper drags in a tree of artefacts.

The fifth is the one that is the most specific: substantial cost logging method and line numbers. A wrapper library cannot know where it was called from without the logging framework walking the stack at runtime, and that walk is not free. This is the problem Scribe's macros solve, and it is the reason the feature list leads with performance rather than with the platform support.

The sixth is a lack of programmatic configuration support, which is the complaint the whole library is built to address.

So the argument is a mixture of the practical and the architectural, and it is worth noticing that a reader who only cares about Scala on the JVM might reasonably weigh items one, two and four as irrelevant and treat the rest as a performance pitch. The response to that is the interoperability module, which gives up the purity on the integration boundary while keeping it everywhere else.

The five features, and what the macros actually do

The main features are five, and the first one is where the others come from.

Performance is a critical consideration, and the approach is to use macros to handle optimisation of everything possible at compile time, to avoid logging slowing down the production application. The claim is that Scribe is the fastest logging framework on the JVM, as far as the authors are aware.

The mechanism is what makes this different from a fast logger. A conventional logger receives a level and a message, checks the level against its configuration, and if the level is disabled, returns. That check is a branch, a comparison and a method call, on every log statement, forever. With a macro, the level check can happen at compile time, so a call to a disabled level can be compiled to nothing at all.

Programmatic configuration is the second feature, and the reasoning is practical rather than ideological. No need to be bound to configuration files means you can rely on any configuration framework, or configure real-time changes to your logging in your production environment. The README gives the case that particularly comes in handy: if you need to enable debug logging on something going wrong in production, you need no restart of the server, simply a mechanism to modify the logging configuration in real-time.

That is a real operational capability and it is the thing wrapper libraries make awkward, because the configuration has to be parsed at startup by whatever mechanism the wrapper uses.

Clean logging is the third feature. Macros allow logging to be introduced into a class via an import instead of a mix-in or unnecessary setup code.

Zero cost class, method and line number logging is the fourth, and it is the one that connects back to problem five in the list of objections. The idea is never to worry about your logger working up the stack to figure out the position of the logging statement at runtime. With macros, that information is determined at compile time, which avoids any runtime cost.

So the location of a log call is a compile-time constant, baked into the call site rather than discovered by walking frames. That is the specific saving that problem five refers to, and it is why the macro approach is not merely an optimisation of the conventional path but a different mechanism.

Asynchronous logging support is the fifth. Scribe's logger is very fast, but if real-time performance is critical, the asynchronous support completely removes logging impact from the application's thread impact.

The complete list is in the project wiki, and the documentation is there too, with a Gitter channel for immediate feedback on questions.

The repository, and the modules around the core

The top-level listing is a build of many small modules, and it explains the library's scope better than the README does.

There is core/ for the main implementation, and then a series of sibling modules named after what they integrate with. There is slf4j/ and slf4j2/ for the two generations of the SLF4J API, log4j/ for Log4j, jpl/ for the java-platform-logging bridge, logstash/ for structured output, slack/ for Slack integration, json/ and jsonCirce/ and jsonFabric/ for three different JSON backends, and overlay/ for a separate artifact.

The three JSON modules are the interesting set. Circe and Fabric are two of the better known Scala JSON libraries, so scribe-jsonCirce and scribe-jsonFabric are formatters that render log events through them, and scribe-json is a third implementation that does not depend on either. Offering three ways to do the same thing is unusual, and it suggests the author wanted users coming from different JSON libraries to be able to keep the one they already have.

There is also cats/, which in the Scala ecosystem means the cats library for functional programming abstractions, so there is a module that emits log events as cats types, which is how a log stream would be typed for a functional consumer.

Then there is fileModule/, which is separate from core/, and a migration/ directory, which is where the code for upgrading from earlier versions lives.

The build tooling is worth noting. There is build.sbt at the root, along with project/ for the sbt build definitions, a scalastyle-config.xml for the style checker, a .mergify.yml for the merge bot, and a .jvmopts file for JVM options during the build. There are benchmark.sh, local.sh, publish.sh and test.sh scripts at the root, plus benchmarks/ and work/ directories, so performance work is part of the repository rather than a separate effort.

There is a config/ directory, and given the programmatic configuration story, that is probably the default or example configuration, and a docs/ directory alongside a jitpack.yml for publishing to JitPack.

The acknowledgements name YourKit, which supports open source projects with its full-featured Java Profiler, and the README credits both the Java and .NET profilers as innovative and intelligent tools for profiling Java and .NET applications. For a library whose central claim is speed, being profiled with a commercial profiler is the right arrangement.

Editorial conclusion

Adopt Scribe if you are writing Scala and want a logger that is fast enough to leave everywhere, that costs nothing for a disabled level, that knows the class, method and line of a call site without walking the stack, and that you can reconfigure at runtime without a restart. Do not adopt it expecting it to drop into an existing SLF4J-based application without thought, because it is not a wrapper, so add the scribe-slf4j module to bridge the libraries that already log that way, and note that the value drops considerably for a project that only targets the JVM and has no performance problem, since two of the six stated problems do not apply to you. Before you commit, check which version the README snippet names against the last tagged release, since they differ, and add the outputStrategy setting if you develop with SBT, or the log prefixes will break the auto-line wrapping.

Frequently asked questions

What is Scribe?

It is a logging library for Scala, Scala.js and Scala Native, built from the ground up rather than wrapping an existing framework such as SLF4J, Log4j or Logback. It needs no configuration files or additional dependencies, and all logging management can be handled programmatically in Scala. It is MIT licensed.

How do I add Scribe to a Scala project?

For a JVM project add libraryDependencies += "com.outr" %% "scribe" % "3.19.0". For a cross-platform project covering the JVM, JS and/or Native use the triple-percent operator instead, and if you need interoperability with libraries that log through SLF4J add the scribe-slf4j module.

Why is Scribe not a wrapper around SLF4J?

The README lists six problems with the wrapper approach: no support for Scala.js, no support for Scala Native, a performance cost, additional dependencies, substantial cost logging method and line numbers, and a lack of programmatic configuration support. The first two are hard blockers for cross-platform Scala projects, since a Java library cannot run on Scala.js or Native.

How do macros make Scribe fast?

Macros handle optimisation of everything possible at compile time, so a call to a disabled log level can be compiled away entirely, and the class, method and line number of a logging statement are determined at compile time rather than by walking the stack at runtime. The authors describe Scribe as the fastest logging framework on the JVM.

Can I change Scribe's log level while the application is running?

Yes. Programmatic configuration means you are not bound to configuration files, so you can configure real-time changes to your logging in your production environment, for example enabling debug logging on something going wrong in production without restarting the server.

Official sources

  1. Issues
  2. License: MIT
  3. outr/scribe on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/outr-scribe.svg)](https://hysenlabs.com/projects/outr-scribe)

Was this useful?