Skip to content

Repository files navigation

Java Logger Benchmark

GitHub Action

JMH Benchmark for different synchronous Java Logger implementations.

Idea of this benchmark is to put all loggers in the same conditions and measure how they all handle the most common scenarios. Compare their implementation in such scenarios, some loggers have more flexible configurations, different APIs, some have more features, different implementations.

At the end it's your choice, do you want flexibility some loggers provide and what are trade-offs of each implementation.

Loggers

Benchmark features these loggers:

Benchmark

Benchmark consists of different common logging scenarios developers typically use in their applications, by the name of the test you can understand what this situation try to emulate, here is full list of tests:

  • messageAndStacktrace
  • messageWithoutArguments
  • messageOneArgumentInTheEnd
  • messageOneArgumentInTheMiddle
  • messageOneArgumentInTheStart
  • messageTwoArgumentInTheEnd
  • messageTwoArgumentInTheMiddle
  • messageTwoArgumentInTheStart
  • messageThreeArgumentInTheEnd
  • messageThreeArgumentInTheMiddle
  • messageThreeArgumentInTheStart

Here are corresponding examples of resulted log messages (excluding messageAndStacktrace due to big stacktrace):

2022-03-22T15:33:48.723 [INFO] io.goodforgod.benchmark.LoggerBenchmark - Message is printed for this logger without arguments
2022-03-22T15:33:48.723 [INFO] io.goodforgod.benchmark.LoggerBenchmark - Message is printed for this logger and with the argument: FirstArgument
2022-03-22T15:33:48.723 [INFO] io.goodforgod.benchmark.LoggerBenchmark - Message is printed for FirstArgument argument for this logger
2022-03-22T15:33:48.723 [INFO] io.goodforgod.benchmark.LoggerBenchmark - FirstArgument argument and message is printed for this logger
2022-03-22T15:33:48.723 [INFO] io.goodforgod.benchmark.LoggerBenchmark - Message is printed for this logger and with arguments FirstArgument and SecondArgument
2022-03-22T15:33:48.723 [INFO] io.goodforgod.benchmark.LoggerBenchmark - Message is printed for FirstArgument and SecondArgument argument for this logger
2022-03-22T15:33:48.723 [INFO] io.goodforgod.benchmark.LoggerBenchmark - FirstArgument and SecondArgument arguments and message is printed for this logger
2022-03-22T15:33:48.723 [INFO] io.goodforgod.benchmark.LoggerBenchmark - Message is printed for this logger and with arguments FirstArgument and SecondArgument and ThirdArgument
2022-03-22T15:33:48.723 [INFO] io.goodforgod.benchmark.LoggerBenchmark - Message is printed for FirstArgument and SecondArgument and ThirdArgument argument for this logger
2022-03-22T15:33:48.723 [INFO] io.goodforgod.benchmark.LoggerBenchmark - FirstArgument and SecondArgument and ThirdArgument argument and message is printed for this logger

If you want to look at benchmark details, you can check it here.

Layout

All loggers participants are configured to the same layout, so the all participants will be in equal conditions.

Pseudo layout for all loggers:

{date} [{level}] {logger} - {message}{separator}{throwable with stacktrace}

Description of layout:

  • date - uses formatter yyyy-MM-dd'T'HH:mm:ss.SSS
  • level - logging level
  • logger - logger full class name
  • message - logging message
  • separator - new line to separate logging messages
  • stacktrace - exception stacktrace

Configuration

All loggers use synchronous output, without any async appending mechanism.

All loggers are configured to output to STDERR.

Benchmark emulates real world usage of loggers, same way logger will be used in real running application. To achieve this, benchmark uses real IO output for loggers, but to mitigate IO of the specific machine and console output, all loggers output is redirected to /dev/null. This is done to benchmark how loggers are working in real environment including IO interactions and avoid benchmarking how machine prints data to STDOUT where benchmark is running at.

Loggers have different implementations and such huge performance gaps (as seen by results) occur mostly due to some loggers accessing IO more frequently than others. So measuring IO interactions is indented and critical to receive real world insights.

Setups

JMH precaution:

REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.

Setup 1

This benchmark results are based on run inside GitHub CI and have forwarded stderr to /dev/null.

Benchmark setup configuration:

  • OS: Ubuntu (Github CI)
  • Processor: Unknown
  • Java: JDK 17.0.2, OpenJDK 64-Bit Server VM, 17.0.2+8-LTS
  • Execution: java -jar benchmark-name.jar 2>/dev/null

Raw Results

BenchmarkWarmupRunsUnitsgoodforgod-simplelogbacklog4jslf4j-simplejava-system
messageAndStacktrace26ops/s118216±813115822±428104783±50113338±22340445±203
messageWithoutArguments26ops/s499217±1199473321±5493417106±6782175836±183543540±467
messageOneArgumentInTheEnd26ops/s458897±4559443582±2258400907±5836169457±319240692±811
messageOneArgumentInTheMiddle26ops/s473144±13985451131±15834422485±5795173946±180340464±552
messageOneArgumentInTheStart26ops/s460671±4028432312±2706406973±6916173542±209541138±587
messageTwoArgumentInTheEnd26ops/s452458±6389444213±3966397294±7014164550±337940661±531
messageTwoArgumentInTheMiddle26ops/s432949±5454442847±5500390417±3958168683±186241380±403
messageTwoArgumentInTheStart26ops/s448363±4246440998±4881392745±10002167334±269939528±231
messageThreeArgumentInTheEnd26ops/s423541±3972426526±5837396242±8698169260±184240128±464
messageThreeArgumentInTheMiddle26ops/s430087±2801409115±3378392355±5064167569±993640184±244
messageThreeArgumentInTheStart26ops/s425695±2946422243±7029373625±6822168490±84839986±586

You can validate results yourself.

Processed Results

If we take goodforgod-simple-logger as baseline and compute other loggers performance based on numbers above:

Benchmarkgoodforgod-simplelogbacklog4jslf4j-simplejava-system
messageAndStacktrace10098.088.611.334.2
messageWithoutArguments10094.883.635.28.7
messageOneArgumentInTheEnd10096.787.436.98.9
messageOneArgumentInTheMiddle10095.389.336.88.6
messageOneArgumentInTheStart10093.888.337.78.9
messageTwoArgumentInTheEnd10098.287.836.49.0
messageTwoArgumentInTheMiddle100102.390.239.09.6
messageTwoArgumentInTheStart10098.487.637.38.8
messageThreeArgumentInTheEnd100100.793.640.09.5
messageThreeArgumentInTheMiddle10095.191.239.09.3
messageThreeArgumentInTheStart10099.287.839.69.4

If we shrink results even more and compute average for all messages with arguments as single result then:

Benchmarkgoodforgod-simplelogbacklog4jslf4j-simplejava-system
message and stacktrace10098.088.611.334.2
message with arguments10097.588.737.89.1

Setup 1

Setup 2

This benchmark have forwarded stderr to NUL(/dev/null analog in windows)

Benchmark setup configuration:

  • OS: Windows 10
  • Processor: AMD Ryzen 2600X
  • Java: OpenJDK 64-Bit Server VM (build 17+35-2724, mixed mode, sharing)
  • Execution: java -jar benchmark-name.jar 2>NUL

Raw Results

BenchmarkWarmupRunsUnitsgoodforgod-simplelogbacklog4jslf4j-simplejava-system
messageAndStacktrace26ops/s58718±66954617±24043765±6052684±14222721±262
messageWithoutArguments26ops/s120257±34208101818±598589485±1245732231±452831956±747
messageOneArgumentInTheEnd26ops/s116935±3201593380±1319187549±183543576±441629963±355
messageOneArgumentInTheMiddle26ops/s137995±3642082164±327390059±1740840252±962630299±167
messageOneArgumentInTheStart26ops/s100351±1941488131±561392676±1873641611±942330424±353
messageTwoArgumentInTheEnd26ops/s95318±456785102±303587795±709444082±432429248±548
messageTwoArgumentInTheMiddle26ops/s101764±1360486166±98796163±2833041920±762330086±642
messageTwoArgumentInTheStart26ops/s97099±1419191736±1019185855±426048236±836429137±518
messageThreeArgumentInTheEnd26ops/s99141±1151491344±874489784±1449346913±313629543±371
messageThreeArgumentInTheMiddle26ops/s96524±1099790234±123189083±1126438981±372430155±409
messageThreeArgumentInTheStart26ops/s125277±1088883704±142886095±245440526±1395329521±311

Processed Results

If we take goodforgod-simple-logger as baseline and compute other loggers performance based on numbers above:

Benchmarkgoodforgod-simplelogbacklog4jslf4j-simplejava-system
messageAndStacktrace10093.074.54.638.7
messageWithoutArguments10084.774.426.826.6
messageOneArgumentInTheEnd10079.974.937.325.6
messageOneArgumentInTheMiddle10059.565.329.222.0
messageOneArgumentInTheStart10087.892.441.530.3
messageTwoArgumentInTheEnd10089.392.146.230.7
messageTwoArgumentInTheMiddle10084.794.541.229.6
messageTwoArgumentInTheStart10094.588.449.730.0
messageThreeArgumentInTheEnd10092.190.647.329.8
messageThreeArgumentInTheMiddle10093.592.340.431.2
messageThreeArgumentInTheStart10066.868.732.323.6

If we shrink results even more and compute average for all messages with arguments as single result then:

Benchmarkgoodforgod-simplelogbacklog4jslf4j-simplejava-system
message and stacktrace10093.074.54.638.7
message with arguments10083.383.339.227.9

Setup 2

Setup 3

This benchmark have forwarded stderr to NUL(/dev/null analog in windows)

Benchmark setup configuration:

  • OS: Windows 10
  • Processor: Intel i5-6200U
  • Java: OpenJDK 64-Bit Server VM (build 17.0.1+12-39, mixed mode, sharing)
  • Execution: java -jar benchmark-name.jar 2>NUL

Raw Results

BenchmarkWarmupRunsUnitsgoodforgod-simplelogbacklog4jslf4j-simplejava-system
messageAndStacktrace26ops/s44741±122730574±55128409±7182074±16615384±161
messageWithoutArguments26ops/s77648±235772632±210770858±400833142±450220126±5878
messageOneArgumentInTheEnd26ops/s75533±497670459±157667934±365132307±1216520420±693
messageOneArgumentInTheMiddle26ops/s75453±856871517±305465894±638731073±634520141±585
messageOneArgumentInTheStart26ops/s73486±1507966942±206266961±140931229±718620163±276
messageTwoArgumentInTheEnd26ops/s75008±181866768±409665697±104832632±44820421±265
messageTwoArgumentInTheMiddle26ops/s75396±147369392±726568996±411030265±324920178±344
messageTwoArgumentInTheStart26ops/s75785±285168737±456267683±172034428±99620206±239
messageThreeArgumentInTheEnd26ops/s75579±423066103±285866542±214930621±543220232±371
messageThreeArgumentInTheMiddle26ops/s74463±172569847±179766406±147430986±479220311±333
messageThreeArgumentInTheStart26ops/s75444±172768149±356766786±162130203±466020280±315

Processed Results

If we take goodforgod-simple-logger as baseline and compute other loggers performance based on numbers above:

Benchmarkgoodforgod-simplelogbacklog4jslf4j-simplejava-system
messageAndStacktrace10068.363.54.634.4
messageWithoutArguments10093.591.342.725.9
messageOneArgumentInTheEnd10093.389.942.827.0
messageOneArgumentInTheMiddle10094.887.341.226.7
messageOneArgumentInTheStart10091.191.142.527.4
messageTwoArgumentInTheEnd10089.087.643.527.2
messageTwoArgumentInTheMiddle10092.091.540.126.8
messageTwoArgumentInTheStart10090.789.345.426.7
messageThreeArgumentInTheEnd10087.588.040.526.8
messageThreeArgumentInTheMiddle10093.889.241.627.3
messageThreeArgumentInTheStart10090.388.540.026.9

If we shrink results even more and compute average for all messages with arguments as single result then:

Benchmarkgoodforgod-simplelogbacklog4jslf4j-simplejava-system
message and stacktrace10068.363.54.634.4
message with arguments10091.689.442.026.9

Setup 3

Run

In case you want to try benchmark yourself, then you should compile and package all benchmarks first:

./gradlew shadowJar

Then you can run each of them in their proper directory, for example to run goodforgod-simple-logger benchmark:

java -jar goodforgod-simple-logger/build/libs/*all.jar

If you want to suppress logger output to measure raw performance, you should redirect STRERR that logger produce to /dev/null.

java -jar goodforgod-simple-logger/build/libs/*all.jar 2>/dev/null

Configuration

You can configure the number of warmup and iterations with command line arguments, the first argument corresponds to warmups and second is for iterations:

Example below will run 1 warmup and 2 iteration:

java -jar goodforgod-simple-logger/build/libs/*all.jar 1 2

Releases

Packages

Used by

Contributors

Languages