Alternative Slf4j bobbin logger

Greetings, dear friends!

I want to share my thoughts on the topic of logging and what they led to.

Perhaps due to some lack of theoretical research, logging has always been a zone of turbulence in the Java world. Over time, this has led to the emergence of several libraries for logging, such as:

  • Log4j
  • Java Util Logging
  • Commons logging
  • Logback
  • Log4j2

Trying to narrow the rest of the rest, unfortunately each of them introduced its own shortcomings.

And if from the point of view of code standardization, the situation improved after the appearance of Slf4j - as an abstraction layer for logging, unresolved problems are still present 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 the settings


This leads to declarative programming in the logger configuration files (XML, JSON, YAML), although it would be much simpler to dynamically interpret configuration values ​​at runtime using imperative scripting.

Let's take an example of a filter configuration in Logback, for logging only messages with the logging level INFO:

INFOACCEPTDENY

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 there is no scripting support for formatting a string completely.

- Complicated and overlong configuration


Take Logback and Log4j2:

There is no way to configure the logging level for a particular appender.

Appenders are configured separately from loggers and loggers refer to appenders using the “AppenderRef” attribute - while only loggers support setting the level of logging 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 many appenders, the size of the configuration grows exponentially.

- To each level of logging - 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.) The

existing capabilities require duplication of appenders for each level of logging.

- Setting 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 disconnection between how the log data is generated 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 if they were file-centric.

This contradicts human perception, which more logically perceives expectations around the final contents of the log files, rather than worrying about setting up each individual class.

In practice, this paradox is the reason for the functional limitations of existing implementations:

  • Complicated file name configuration
  • Irrational configuration of the logger, for example:

Logback supports a maximum of 1 “discriminator” in “SiftingAppender”.
SiftingAppender has limitations in policy settings for archiving.
Re-configured RoutingAppender in Log4j2

Decision


- Full scripting support in configuration


Bobbin uses the configuration as a locator for Groovy scripts that determine the behavior of the logger in application runtime.

This is what the “filter” example looks like:

{
  "levels": "['info'].contains(level)"
}

Each aspect of the logger supports customization using scripts:

  • Logging levels
  • Class names
  • Message format
  • File names

- Simple and concise setup


Bobbin does not require encoders, patterns, filters, discriminators and many other unnecessary things.

It is configured with just a few basic parameters:

  • Levels
  • Classes
  • Files
  • Line format

Separate files for each level of logging: just put "$ {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 licensed under Apache.

Also popular now: