Greetings, dear friends!
I want to share my reasoning on the topic of logging and what they led to.
Perhaps due to some lack of theoretical research, logging has always been a kind of turbulence zone in the Java world. Over time, this caused the emergence of several logging libraries, such as:
')
- Log4j
- Java Util Logging
- Commons logging
- Logback
- Log4j2
Trying to narrow down the restraints, unfortunately each of them introduced its own shortcomings.
And if from the point of view of code standardization, the situation has improved after the appearance of Slf4j - as an abstraction layer for logging, there are still unresolved problems in existing implementations.
As an open source community, we are taking the initiative to come up with a new, revolutionary approach - and create a lightweight (but at the same time functionally rich) logger using the latest developments, such as scripting.
Problems
- Existing implementations provide only partial support for scripts in settings
This leads to declarative programming in the logger configuration files (XML, JSON, YAML), although it would be much easier to dynamically interpret the configuration values ​​at runtime using imperative scripting.
Let's take an example of a filter configuration in Logback, for logging messages only with the INFO logging level:
<filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>INFO</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter>
This is a typical example of declarative XML programming.
(yes, Logback supports a filter using Groovy, but it applies only to specific appenders, not to a logger)
But support for scripting to format the string is completely missing.
- Advanced and over-extended configuration
Take Logback and Log4j2:
There is no possibility to configure the logging level for a specific appender.
Appenders are configured separately from loggers and loggers refer to appenders using the “AppenderRef” attribute — only loggers support customization of logging level and class names.
Suppose we need to exclude Debug messages from one Foo class from a specific log file, without affecting other log files and classes.
In Logback, this is possible using the Groovy Script filter on the appender - but if we have a lot of appenders, the size of the configuration grows exponentially.
- Each logging level is a separate file!
We could not find the possibility of such a setting, in which messages are grouped into files by message level (debug, info, etc.)
Existing features require duplication of appenders for each level of logging.
- Setting up filtering by class name in the Root logger itself
Root logger supports setting only the logging level, but there is no possibility of centralized control of which classes should be logged.
- There is a conceptual separation between how log data is produced in the application and how this data is consumed by the logger.
Historical practice is such that loggers (and their configuration) are more class-centric than file-centric ones.
This is contrary to human perception, which more logically perceives expectations around the final contents of the log files, and does not worry about setting up each individual class.
In practice, this paradox causes the functional limitations of existing implementations:
- Advanced Filename Configuration
- Irrational logger configuration, for example:
The logback supports a maximum of 1 "discriminator" in the "SiftingAppender".
SiftingAppender has limitations in policy settings for archiving
Re-configured “RoutingAppender” setting in Log4j2
Decision
- Full support for scripting in the configuration
Bobbin uses the configuration as a placeholder for Groovy scripts that determine the behavior of the logger during the execution time of the application.
This is what the above “filter” example looks like:
{ "levels": "['info'].contains(level)" }
Every aspect of the logger supports customization using scripts:
- Logging levels
- Class names
- Message format
- File names
- Simple and short setup
Bobbin does not require Encoders, Patterns, Filters, Discriminators and many other extra things.
It is configured with only a few basic parameters:
- Levels
- Classes
- Files
- String format
Separate files for each logging level: just place the "$ {level}" in the file name mask in Bobbin.json (configuration file).
Example configuration file:
{ "levels": "['debug', 'info', 'warn', 'error'].contains(level)", "destinations": [ { "name": "io.infinite.bobbin.destinations.FileDestination", "properties": { "fileName": "\"./LOGS/PLUGINS/INPUT/${className}/${level}/${className}_${level}.log\"" }, "classes": "className.contains('conf.plugins.input')" }, { "name": "io.infinite.bobbin.destinations.FileDestination", "properties": { "fileName": "\"./LOGS/PLUGINS/OUTPUT/${className}/${level}/${threadName}_${level}_${date}.log\"" }, "classes": "className.contains('conf.plugins.output')" }, { "name": "io.infinite.bobbin.destinations.FileDestination", "properties": { "fileName": "\"./LOGS/THREADS/${threadGroupName}/${threadName}/${level}/${threadName}_${level}_${date}.log\"" }, "classes": "className.contains('io.infinite.')" }, { "name": "io.infinite.bobbin.destinations.FileDestination", "properties": { "fileName": "\"./LOGS/ALL/WARNINGS_AND_ERRORS_${date}.log\"" }, "levels": "['warn', 'error'].contains(level)" }, { "name": "io.infinite.bobbin.destinations.ConsoleDestination", "levels": "['warn', 'error'].contains(level)" } ] }
Try Bobbin now:
Gradle: compile "io.infinite:bobbin:2.0.0"
* Bobbin is an open source project under the Apache license.