scala-logging: SLF4J for Scala, with the check-enabled guard applied for you
Convenient and performant logging library for Scala wrapping SLF4J.
At a glance
- What is it?
- scala-logging wraps SLF4J so Scala code can write logger.debug(s"...") without hand writing the isDebugEnabled guard, because a Scala macro rewrites the call at compile time. It also ships LazyLogging, StrictLogging and AnyLogging traits, guarded log blocks, and a logger that carries context such as a correlation id.
- Who is it for?
- scala-logging suits Scala teams on SLF4J who want the guard applied at compile time rather than at every call site, and who want log context such as a correlation id carried in the type system instead of threaded by hand.
- Can I use it commercially?
- Yes. Apache-2.0 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 99 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 November 7, 2026, and from our analysis. They are not legal advice.
Star history
- scala-garden/scala-logging923 ★ · -1
| Repository | 2026-10-21 | 2026-11-07 |
|---|---|---|
| scala-garden/scala-logging | 924 | 923 |
-1 Stars in the last 17 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
The problem: every log call needs a guard nobody wants to write
Scala Logging is a convenience and performance library that wraps SLF4J, and its argument is short. You call the log method without checking whether the level is enabled:
logger.debug(s"Some $expensive message!")That is convenient because the alternative is two lines at every call site. The performance half of the argument is about what the compiler produces. Thanks to Scala macros the check-enabled idiom is applied and the generated code becomes:
if (logger.isDebugEnabled) logger.debug(s"Some $expensive message!")So the guard is not skipped by the library, it is generated. That distinction is the whole design, and it matters because the guard has to be evaluated before the string interpolation runs, otherwise the expensive message is built and thrown away. Scala Logging sits above SLF4J rather than replacing it, so the backend is still whatever your organisation has standardised on, and the README names Logback as a compatible one.
Macros do the rewriting at compile time
The implementation lives in a directory called scala2macros, alongside core/ for the library itself and integration-test/ for the test suite, with a scalariform.sbt file handling source formatting and a project/ directory for the sbt build. The macro approach has a consequence worth stating plainly. Because the rewriting happens at compile time, it can only see the source code as written, not the values at runtime. That is why the interpolation rewriting described later has a narrow scope, and why the guard is generated rather than evaluated dynamically. It is also why the library carries its own macro module separately from the core artifact, since the macro implementation is needed at compile time on the consumer's classpath while the runtime piece is small. The project is published to Sonatype OSS and Maven Central under the group id com.typesafe.scala-logging and the artifact id scala-logging, with the Scala version suffix applied by sbt.
Three traits, and the argument for picking one deliberately
The library defines the logger member for you in three different ways, and the README gives a clear recommendation for each. The LazyLogging trait defines the logger as a lazy value, StrictLogging defines it as a strict value, and AnyLogging defines an abstract logger that you override. Use LazyLogging when you are creating lots of objects with the trait repetitively, because the logger is not constructed until it is needed. Use StrictLogging pretty much by default, especially for a singleton class or when you know the log methods will always be invoked, since the cost of one logger construction is paid once. Use AnyLogging when writing a trait that needs access to a logger without committing to one implementation, in which case you supply your own. The examples show the shape of each, and in the LazyLogging and StrictLogging cases the underlying SLF4J logger is named after the class into which the trait is mixed, which keeps logger names meaningful in the output.
Guarded blocks, not just guarded messages
Beyond the ordinary call sites, the library offers the when level enabled form, which guards a whole block of code:
class LazyLoggingExample extends LazyLogging {
logger.debug("This is Lazy Logging ;-)")
logger.whenDebugEnabled {
println("This would only execute when the debug level is enabled.")
(1 to 10).foreach(x => println("Scala logging is great!"))
}
}The same pattern appears with StrictLogging and with whenInfoEnabled under AnyLogging. This is the construct that matters for real work, because it covers the case where the expensive part is not the message but the work that produces it. A debug block that builds a report, walks a directory or formats a large structure can be placed inside whenDebugEnabled and will not execute at all when the level is off, which the message-only guard would not achieve on its own.
Carrying a correlation id in the type system
LoggerTakingImplicit provides the same methods as the Logger class but with an additional implicit parameter of type A, and creating one requires a CanLog[A] instance as evidence. The intended use is a contextual parameter that is passed around, such as a correlation id, that you want included in every message. You define the case class, provide an implicit CanLog implementation that rewrites the message, and let the implicit value flow:
case class CorrelationId(value: String)
implicit case object CanLogCorrelationId extends CanLog[CorrelationId] {
override def logMessage(originalMsg: String, a: CorrelationId): String = s"${a.value} $originalMsg"
}
implicit val correlationId = CorrelationId("ID")
val logger = Logger.takingImplicit[CorrelationId]("test")
logger.info("Test") // takes implicit correlationId and logs "ID Test"The context object can be read back with getContext, and the README also shows the same pattern used to populate and clear an MDC entry, so that the correlation id also reaches loggers that are not this one.
String interpolation is rewritten, with two documented limits
The library prefers Scala's string interpolation, logger.error(s"log $value"), over the SLF4J style logger.error("log {}", value). The reason given is that some tools, Sentry is the example, use the log message format as a grouping key and therefore do not work well with Scala's interpolation. Scala Logging replaces simple interpolations with their SLF4J counterparts, so logger.error(s"my log message: $arg1 $arg2 $arg3") becomes logger.error("my log message: {} {} {}", arg1, arg2, arg3), which the README says has no effect on behaviour. The Limitations section is short and specific. The rewrite works only when string interpolation is used directly inside the logging statement, meaning the message must be static and available at compile time. And it works only for the logger.level(message) and logger.level(marker, message) forms, not when you want to log an exception together with interpolation, which the README attributes to a limitation of the SLF4J API.
Versions, backends and the release candidate
The prerequisites are Java 11 or higher, with Scala Logging 3.x supporting Java 8, and Scala 2.12, 2.13 or 3.0, plus a logging backend compatible with SLF4J. The README also points out two version forks: Scala Logging 2.x is the one for Scala 2.10, and Scala Logging 3.x is the one for SLF4J 1.x. For sbt the dependency line is:
libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "4.0.0-RC1"with the backend added alongside it:
libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.4.14"The release history is where a careful adopter needs to look. The newest published version is 4.0.0-RC1 from 2025-11-28, and the last full release is 3.9.6 from 2025-09-16, with 3.9.5 dating back to 2023-08-30. The last push was on 2026-07-31 and the repository is not archived, so work continues, but the artefact you would depend on today is a release candidate. The licence is Apache-2.0, with a LICENSE file and a NOTICE file in the repository root.
Editorial conclusion
scala-logging suits Scala teams on SLF4J who want the guard applied at compile time rather than at every call site, and who want log context such as a correlation id carried in the type system instead of threaded by hand. Skip it if you are on SLF4J 2.x and want the current stable line, since the newest published version is 4.0.0-RC1 while 3.9.6 is the last full release, and skip it if your log messages are built dynamically, because the interpolation rewrite only works when the message is static. Before adopting, check the SLF4J major your backend uses against the Scala Logging line, since the README says 3.x targets SLF4J 1.x, and add Logback with the logback-classic 1.4.14 dependency the README shows.
Frequently asked questions
What does scala-logging add on top of SLF4J?
It adds convenience and speed. You can call log methods without checking whether the level is enabled, and Scala macros apply the check-enabled idiom so the generated code becomes if (logger.isDebugEnabled) logger.debug(...), which keeps the message from being built when the level is off.
Which Scala Logging version should I use?
The README states that Scala Logging 3.x is the version for SLF4J 1.x, and Scala Logging 2.x is the one compatible with Scala 2.10. The current line is 4.x, with 4.0.0-RC1 published on 2025-11-28 and 3.9.6 on 2025-09-16.
How do I add scala-logging to an sbt build?
Add libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "4.0.0-RC1" to build.sbt, along with an SLF4J compatible backend such as libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.4.14". The artefacts are published to Sonatype OSS and Maven Central.
When should I use LazyLogging instead of StrictLogging?
The README recommends LazyLogging when you are creating lots of objects with the trait repetitively, since the logger is a lazy value, and StrictLogging pretty much by default, especially for a singleton class or when the log methods will always be invoked. AnyLogging defines an abstract logger for traits that need a logger without committing to an implementation.
Does scala-logging support logging a correlation id with every message?
Yes. LoggerTakingImplicit takes an additional implicit parameter of type A and requires a CanLog[A] instance, so you can implement logMessage to prefix the message and use afterLog to set and clear an MDC entry. The context object can be read back with getContext.
Official sources
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.
[](https://hysenlabs.com/projects/scala-garden-scala-logging)Star history chart for your README
[](https://hysenlabs.com/projects/scala-garden-scala-logging)Was this useful?