import logadapter.jul.Api.*
object MyObject extends SelfLogging:
def doSomething() : Unit =
INFO.log("Something has been done.")
-
Choose a logging backend.
Currently
jul
(java.util.logging),scribe
,mlog
,slf4j
, andstderr
, a simple standard-error backend, are supported.For
jul
andstderr
, the dependency you'll need is just- sbt:
libraryDependencies += "com.mchange" %% "logadapter-scala" % "<version>"
- mill:
ivy"com.mchange::logadapter-scala:<version>"
For
scribe
,mlog
,log4j2
,slf4j
or other backends, you'll need a library-appropriate dependency, like- sbt:
libraryDependencies += "com.mchange" %% "logadapter-scala-scribe" % "<version>"
- mill:
ivy"com.mchange::logadapter-scala-scribe:<version>"
Look for the latest version (which will be the same across all backends).
- sbt:
Note
For logback, choose the slf4j
backend, but make sure the latest version of logback-classic
is
in your CLASSPATH
.
-
Each backend has its own package, in which there is an object called Api. Import the full API from that object:
import logadapter.jul.Api.* // you might have chosen `scribe`, `mlog`, `log4j2`, `slf4j`, or `stderr` instead of `jul`
-
Mark your classes and objects with the trait
SelfLogging
(part of what you've imported). That brings in aLogAdapter
as agiven
, enabling you to log inside the class or object at will:import logadapter.jul.Api.* object MyObject extends SelfLogging: def doSomething() : Int = TRACE.log("Entered "doingSomething()") INFO.log("I'm doing something!") WARNING.logDebug("Math is hard!") INFO.logEval("Computation")(3 + 7) // this logs "Computation: 10" and evaluates to 10
-
If you wish to be able to log outside of a
SelfLogging
class or object, you can explicitly bring in aLogAdapter
from your Api as agiven
:import logadapter.jul.Api.* given LogAdapter = logAdapterFor( "com.mchange.my.logger.name" ) def sunset() : Unit = INFO.log("The sun is setting.")
logAdapterFor
accepts a String logger name, or anyobject
or instance, in which case the log name becomes the class name. (If aClass
object is provided, the log name is theclassObject.getName()
)You can also use the source file name as your logger name, via
given LogAdapter = logAdapterByFilename
Note
If you wish to log to the logger of a SelfLogging
object outside of that object's scope,
you can explicitly import MySelfLoggingObject.logAdapter
, and the logging API will become available.
The API is very simple. Supported log levels are
CONFIG
DEBUG
ERROR
FATAL
FINE
FINER
FINEST
INFO
SEVERE
TRACE
WARNING
Note
These levels may not map exactly to the levels of your logging backend. They get remapped to the most appropriate level your backend supports.
Once you've established a context with a logger (see Steps 3 and 4 above), you simply log on levels:
INFO.log("The sun'll come up tomorrow.")
You can also attach a Throwable to your logs (usually resulting in its stack trace getting logged).
try
// do some scary stuff here
catch
case t : Throwable =>
WARNING.log("Something really bad happened!", t)
throw t
If you want more debugging information (like the filename and line number from which
the message was logged). You can use logDebug
instead if log
:
WARNING.logDebug("No throwable.")
WARNING.logDebug("With throwable.", t)
Note
Some backends (e.g. scribe
) bring in filename and line number information by default. logDebug
may be less useful with these backends.
Sometime for debugging purposes, you want to quickly have the value of an expression
logged. For that, there is the logEval
method, or just apply
your level:
val count = INFO(1 + 2 + 3) // 6 will be logged, and will become the value of count
If you want a prefix to help you interpret the printed expression, you can use
val count = INFO.logEval(prefix = "count")(1 + 2 + 3) // 6 will be logged, and will become the value of count
That's it!
logadapter-scala
does nothing to standardize configuration of backend libraries.
Whatever backend you choose, you'll have to supply its library-specific config files
or use its configuration API directly.
However, logadapter-scala
Api objects are ordinary objects. If you are using
programmatic configuration, one way to ensure your Api is configured before you
begin to use it is define your own alias for it, and import that:
val LoggingApi =
scribe.Logger
.root
.clearHandlers()
.withHandler(minimumLevel = Some(scribe.Level.Info), formatter = scribe.format.Formatter.compact)
.replace()
logadapter.scribe.Api
Now, your configuration is centralized, easy to update or switch out. Elsewhere in your application, you just import from LoggingApi
:
import LoggingApi.*
and you can be sure your logging has been configured before the API is accessed.
In addition to your backend appropriate library, if you bring in...
- sbt:
libraryDependencies += "com.mchange" %% "logadapter-scala-zio" % "<version>"
- mill:
ivy"com.mchange::logadapter-scala-zio:<version>"
you can log using your backend of choice to ZIO effects, and have ZIO effects log errors and defects to your backend of choice.
(Yes, ZIO has its own native logging. But some of us don't love it.)
Setting up the API is a bit inelegant, due in part to a compiler bug that will hopefully get fixed soon. For now the setup looks like...
// workaround of nonexport of SelfLogging from logadapter, due to a compiler bug. hopefully unnecessary soon
object LoggingApi:
val raw = logadapter.zio.ZApi( logadapter.jul.Api )
type SelfLogging = raw.inner.SelfLogging
export raw.*
then, as before, you can use in your application...
import LoggingApi.*
Note
As before, to log, you'll need either to be in a type that implements SelfLogging
, or have explicitly brought in a given LogAdapter
.
See Full Start, items 3 and 4 above.
Now in addition to the logging API above, you have the following new methods of Level
:
INFO.zlog("This is a message") // returns a ZIO effect, ZIO[Any,Nothing,Unit], or UIO[Unit]
SEVERE.zlog("This is a message with a Throwable", t) // returns a ZIO effect, ZIO[Any,Nothing,Unit], or UIO[Unit]
You also have available methods you can call directly on ZIO effects, which yield the same effect, but with the side effect of logging errors or defects to your backend of choice:
val someZioEffect = ...
someZioEffect.zlogError( WARNING ) // returns someZioEffect with the side effect of logging any errors
someZioEffect.zlogDefect( SEVERE ) // returns someZioEffect with the side effect of logging any defects
someZioEffect.zlogErrorDefect( SEVERE ) // returns someZioEffect with the side effect of logging both errors and defects
To get clearer information about where something went wrong if errors or defects are logged, you can add a tag to any of these methods:
someZioEffect.zlogError( WARNING, what = "Call to DB server" ) // any logged error will mention it was the Call to DB server what done it
Over the years, JVM applications have adopted a large menagerie of logging libraries. I've been partial to logging "facades", (pioneered by Apache Commons logging), by which you can learn and use a single logging API, then plug-in and configure any of the different logging libraries you might choose as a "back end".
Mostly in support of c3p0, I've
built a very extensive and configurable logging facade. "mlog" (for
mchange logging) lives in com.mchange.v2.log
package of
mchange-commons-java.
See the c3p0's documentation for information
on configuring and using that package.
mlog-scala is a Scala API
and facade to mlog
, which I've used extensively and
successfully.
However, Scala 3 inlines open a path for a much simpler and lighter-weight
sort of facade. mlog-scala
is tried and true, but brings in a large java
dependency for a very simple task, and imposes some overhead to the frequent
operation of logging. (To be fair to my old self, mlog
is built with some
care, and its overhead is surprisingly small.)
This is an experiment in a much thinner, much simpler logging facade,
that largely retains the mlog-scala
API, which I've been happy with.
Note that mlog
is itself supported by this project. It retains the virtue
of letting the backend be chosen and substituted by external configuration,
rather than committing to it in code. (In that sense, mlog
is similar to
slf4j.)
This project has been supported in part by external financial sponsorship. Many thanks to Chris Peel for his support!
© 2025 Machinery For Change LLC