Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/lightbend-labs/scala-logging

Convenient and performant logging library for Scala wrapping SLF4J.
https://github.com/lightbend-labs/scala-logging

jvm logback logging macros sbt scala slf4j

Last synced: 5 days ago
JSON representation

Convenient and performant logging library for Scala wrapping SLF4J.

Awesome Lists containing this project

README

        

Scala Logging is a **convenient** and **fast** logging library wrapping [SLF4J](http://www.slf4j.org).

It's convenient, because you can simply call log methods, *without* checking whether the respective log level is enabled:

```scala
logger.debug(s"Some $expensive message!")
```

It's fast, because thanks to Scala macros the *check-enabled-idiom* is applied and the following code is generated:

```scala
if (logger.isDebugEnabled) logger.debug(s"Some $expensive message!")
```

## Prerequisites

* Java 8 or higher
* Scala 2.11, 2.12, 2.13 or 3.0
* Logging backend compatible with SLF4J

A compatible logging backend is [Logback](http://logback.qos.ch), add it to your sbt build definition:

```scala
libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.10"
```

If you are looking for a version compatible with Scala 2.10, check out Scala Logging 2.x.

## Getting Scala Logging

Scala Logging is published to Sonatype OSS and Maven Central:

- Group id / organization: *com.typesafe.scala-logging*
- Artifact id / name: *scala-logging*

sbt users may add this to their `build.sbt`:

```scala
libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "3.9.4"
```

## Using Scala Logging

The `Logger` class from the `com.typesafe.scalalogging` package wraps an underlying SLF4J logger.
In order to create a `Logger`, you pass a name to the `apply` factory method defined in the `Logger` companion object:

```scala
val logger = Logger("name")
```

Or, you pass in a SLF4J logger instance:

```scala
val logger = Logger(LoggerFactory.getLogger("name"))
```

Or, you pass in the name of the class into which it is defined:

```scala
val logger = Logger(getClass.getName)
```

Or, you pass in a class:

```scala
val logger = Logger(classOf[MyClass])
```

Or, using the runtime class wrapped by the implicit class tag parameter:

```scala
val logger = Logger[MyClass]
```

The `LazyLogging` and `StrictLogging` traits from the `com.typesafe.scalalogging` package define the `logger` member as
a lazy or strict value respectively, whereas the `AnyLogging` trait defines an abstract `logger`.

It depends on the individual use case which trait to use. However, we have defined some scenarios where you can use these traits:

- Use `LazyLogging` if you are creating lots of objects with this trait repetitively.
- Use `StrictLogging` pretty much by default, especially if the class is a singleton, or you know the log methods will always be invoked.
- Use `AnyLogging` when writing some trait which needs access to any logger without deciding on a specific implementation.

In case of `LazyLogging` and `StrictLogging`, the underlying SLF4J logger is named according to the class into which
these traits are mixed:

```scala
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!"))
}
}
```

```scala
class StrictLoggingExample extends StrictLogging {
logger.debug("This is Strict Logging ;-)")

logger.whenDebugEnabled {
println("This would only execute when the debug level is enabled.")
(1 to 10).foreach(x => println("Scala logging is great!"))
}
}
```

```scala
class AnyLoggingExample extends AnyLogging {
override protected val logger: Logger = Logger("name")

logger.info("This is Any Logging ;-)")

logger.whenInfoEnabled {
println("This would only execute when the info level is enabled.")
(1 to 10).foreach(x => println("Scala logging is great!"))
}
}
```

`LoggerTakingImplicit` provides the same methods as `Logger` class, but with additional implicit parameter `A`.
During creation of the `LoggerTakingImplicit` evidence `CanLog[A]` is required.
It may be useful when contextual parameter (e.g. _Correlation ID_) is being passed around and you would like to include it in the log messages:

```scala
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"
```

If you want to extract the context object associated with your logger i.e. `correlationId` here, use `getContext`.
```scala
val context = logger.canLogEv.getContext()
```

It's also possible to use `MDC` through `CanLog` without any troubles with execution context.

```scala
case class CorrelationId(value: String)
implicit case object CanLogCorrelationId extends CanLog[CorrelationId] {
override def logMessage(originalMsg: String, a: CorrelationId): String = {
MDC.put("correlationId", a.value)
originalMsg
}

override def afterLog(a: CorrelationId): Unit = {
MDC.remove("correlationId")
}
}

implicit val correlationId = CorrelationId("ID")

val logger = Logger.takingImplicit[CorrelationId]("test")

def serviceMethod(implicit correlationId: CorrelationId): Future[Result] = {
dbCall.map { value =>
logger.trace(s"Received value $value from db") // takes implicit correlationId
toResult(value)
}
}
```

## String Interpolation

It is idiomatic to use Scala's string interpolation `logger.error(s"log $value")` instead of SLF4J string interpolation `logger.error("log {}", value)`.
However there are some tools (such as [Sentry](https://sentry.io)) that use the log message format as grouping key. Therefore they do not work well with
Scala's string interpolation.

Scala Logging replaces simple string interpolations with their SLF4J counterparts like this:

```scala
logger.error(s"my log message: $arg1 $arg2 $arg3")
```

```scala
logger.error("my log message: {} {} {}", arg1, arg2, arg3)
```

This has no effect on behavior and performace should be comparable (depends on the underlying logging library).

### Limitations

- Works only when string interpolation is directly used inside the logging statement. That is when the log message is static (available at compile time).
- Works only for the `logger.(message)` and `logger.(marker, message)` logging methods. It does not work if you want to log an exception and
use string interpolation too (this is a limitation of the SLF4J API).

## Line numbers in log message?

Using the [sourcecode](https://github.com/lihaoyi/sourcecode#logging) library, it's possible to add line number
information (especially useful for debugging):

```scala
def foo(arg: String)(implicit line: sourcecode.Line, file: sourcecode.File) = {
... do something with arg ...
... do something with file.value ...
}

foo("hello") // the implicit sourcecode.File is filled in automatically
```

## Maintenance status

This library is community-maintained. It is not supported under the Lightbend subscription.

## Contribution policy

Contributions via GitHub pull requests are gladly accepted from their original author. Before we can accept pull requests, you will need to agree to the [Lightbend Contributor License Agreement](https://www.lightbend.com/contribute/cla) online, using your GitHub account.