[ en | ru ]
Nanolog — is a simple but powerful logger for Java applications, compatible with SLF4J, that removes the need for static logger instances in every class requiring logging.
Nanolog provides an advanced API for application logging, offering additional features not found in other popular logger implementations:
- Automatic caller class detection
- Lazy message initialization via Supplier
- Parameterized message support
- Safe object-to-string conversion
- Easy creation of custom logger implementations
Nanolog is an extremely lightweight library and can be used as a standalone logger. However, it also supports integration with SLF4J, enabling compatibility with other logging libraries that act as its bridges — such as Logback, Log4j, JBoss Logging, and others.
Nanolog compatible with Java 8+.
The latest stable version of the library is 1.0.
The latest development version is 1.1-SNAPSHOT.
package com.nanolaba.logging.examples;
import com.nanolaba.logging.*;
public class QuickStart {
public static void main(String[] args) {
try {
// This is the logger used by default
LOG.init(new ConsoleLogger());
// If you want to use SLF4J
LOG.init(new Slf4jLogger());
// If you want to write your own logger, use a lambda function
// or implement the ILogger interface.
LOG.init(entry -> System.err.println(entry.getLevel() + " - " +
entry.getSourceClass() + " - " +
entry.getFormattedMessage()));
LOG.debug("A static logger variable is not needed");
LOG.info(String.class, "But you can explicitly specify which class the logging should belong to");
LOG.warn("This is a parameterized message: {}, {}, {} ",
100, "foo", new Object[]{"foo", "bar"});
if (LOG.isDebugEnabled()) {
LOG.debug("You can check if a log level is enabled in the standard way: " +
hugeComputations());
}
LOG.debug(() -> "It's also possible to pass a lambda expression: " +
hugeComputations());
} catch (Exception e) {
LOG.error(e);
}
}
private static String hugeComputations() {
return "OK";
}
}Maven (pom.xml)
<dependency>
<groupId>com.nanolaba</groupId>
<artifactId>nanolog</artifactId>
<version>1.0</version>
</dependency> Gradle (build.gradle)
dependencies {
implementation 'com.nanolaba:nanolog:1.0'
}Manual download
Get the JAR from Maven Central. Add it to your project's classpath
To use the latest development version in your project, you need to specify the snapshot repository URL and then add a dependency with the -SNAPSHOT suffix:
Maven (pom.xml)
<repositories>
<repository>
<id>central.sonatype.com-snapshot</id>
<url>https://central.sonatype.com/repository/maven-snapshots</url>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
</snapshots>
</repository>
</repositories>
<dependency>
<groupId>com.nanolaba</groupId>
<artifactId>nanolog</artifactId>
<version>1.1-SNAPSHOT</version>
</dependency> Gradle (build.gradle)
repositories {
maven {
url 'https://oss.sonatype.org/content/repositories/snapshots'
}
}
dependencies {
implementation 'com.nanolaba:nanolog:1.1-SNAPSHOT'
}Nanolog does not require any mandatory setup — once the library is added to the project, logging works out of the box and ConsoleLogger is used as the default implementation.
You can pick a different logger in one of three ways:
-
Programmatically
Pass any
ILoggerimplementation toLOG.init(...)at application startup:LOG.init(new Slf4jLogger());
-
Via a system property
Set the
nanolog.loggerproperty to the fully qualified class name of anILoggerimplementation (the class must have a public no-arg constructor):-Dnanolog.logger=com.nanolaba.logging.Slf4jLogger -
Via a configuration file
Place a
nanolog.propertiesfile on the classpath. On the first call toLOGits entries are copied into system properties (without overwriting values already set via-D):nanolog.logger=com.nanolaba.logging.Slf4jLoggerThe file path can be overridden with the
nanolog.configsystem property.
To override the choice made via property or file, call LOG.init(...) explicitly — each call replaces the current logger.
If instantiation of the class specified in nanolog.logger fails, Nanolog
writes a [NANOLOG]-prefixed diagnostic to System.err and silently falls back to ConsoleLogger.
ConsoleLogger — the default implementation. Writes
ERROR entries to System.err, and all other levels to System.out.
Format of a log line (components are separated by spaces):
LEVEL dd.MM.yyyy HH:mm:ss [SourceClass] Message
Each component can be toggled or tuned via fluent setters:
LOG.init(new ConsoleLogger()
.setShowLevel(true)
.setShowDate(true)
.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"))
.setShowSource(true)
.setShowSourceFullName(false)
.setTraceEnabled(false)
.setDebugEnabled(false));Available options:
showLevel— include the log level name (TRACE,DEBUG, …).showDate+dateFormat— include the timestamp formatted with the givenSimpleDateFormat.showSource— include the source class name in square brackets.showSourceFullName— use the fully qualified class name instead of the simple name.traceEnabled,debugEnabled,infoEnabled,warnEnabled,errorEnabled— per-level switches (all enabled by default). Levels that are disabled are short-circuited before theLogEntryis built, soSupplier-based lazy messages are not evaluated.
For custom rendering (coloured output, JSON, a different layout) extend
ConsoleLogger and override getOutputStream(LogEntry); for fine-grained formatting tweak writeLevel, writeDate, writeSource, writeMessage,
writeThrowable or addDelimiter.
Slf4jLogger is a bridge to SLF4J. Each LogEntry is forwarded to the
org.slf4j.Logger returned by LoggerFactory.getLogger(sourceClass). Loggers are cached per source class in a ConcurrentHashMap and the cache is never evicted, so in applications with dynamic class reloading it will retain Class
references.
Level filtering is delegated to SLF4J (isTraceEnabled(), isDebugEnabled() and so on), so the actual log configuration — levels, patterns, appenders, files — is taken from whatever SLF4J backend you plug in (Logback, Log4j 2, JBoss Logging, slf4j-simple, etc.).
The library declares slf4j-api with provided scope, so if you use
Slf4jLogger, you must add slf4j-api and a concrete binding to your project yourself. Example (Maven, Logback):
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.12</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.6</version>
</dependency>Initialisation:
LOG.init(new Slf4jLogger());After that, writing through LOG.info(...) / LOG.debug(...) ends up in the SLF4J pipeline, and — because caller class detection is already done by Nanolog
— SLF4J receives the real source class rather than Slf4jLogger or LOG itself.
A custom logger is any implementation of
ILogger. The interface has a single abstract method, so the simplest implementation is a lambda:
LOG.init(entry -> System.out.println(
entry.getLevel() + " [" + entry.getSourceClass().getSimpleName() + "] "
+ entry.getFormattedMessage()));A full implementation looks like this:
public class MyLogger implements ILogger {
@Override
public void log(LogEntry entry) {
// write the entry wherever you need — file, socket, queue, ...
}
@Override
public boolean isEnabled(LogEntry.LogEntryLevel level, Class sourceClass) {
// return false to suppress entries of the given level for the given class
return level != LogEntry.LogEntryLevel.TRACE;
}
}Useful things to know about LogEntry:
getLevel()— one ofTRACE,DEBUG,INFO,WARN,ERROR.getSourceClass()— the detected (or explicitly passed) source class.getThrowable()— the exception, if one was passed; otherwisenull.getMessage()— the raw message object (already resolved from theSupplier, if a lambda was used).getArgs()— substitution arguments for the{}-placeholders.getFormattedMessage()— the final string with{}arguments substituted; arrays are rendered viaArrays.toString/Arrays.deepToString, and iftoString()throws, the result is"[FAILED toString()]", so a brokentoString()never breaks logging.
Two points worth keeping in mind:
isEnabled(...)is called fromLOG.log(...)before theLogEntryis built and before anySupplieris resolved — returnfalseto skip expensive message computation.- A custom logger class should not itself use
LOGfor logging without passing an explicit source class — any class that implementsILoggeris skipped by the automatic caller-detection, which would otherwise misattribute the log entry.
Questions, bug reports and feature requests are welcome on the issue tracker: github.com/nanolaba/nanolog/issues.
Pull requests are also welcome. Note that the build enforces 100% pitest mutation coverage, so any change to production code must come with tests that catch the corresponding mutants.
Generated with nanolaba/readme-generator — 23.04.2026