Java Util Logging integration is provided as part of the artifact:

<dependency>
  <groupId>io.yupiik.logging</groupId>
  <artifactId>yupiik-logging-jul</artifactId>
  <version>${yupiik-logging.version}</version>
</dependency>

Use globally

java .... -Djava.util.logging.manager=io.yupiik.logging.jul.YupiikLogManager ...

Configuration

Configuration is close to default JUL one with small differences:

  1. handlers don't need to configure themselves in their constructor or so for common properties (formatter, level, ...), the framework does it automatically

  2. InlineFormatter and JsonFormatter support aliases (reflection free instantiation) with inline and json you can use in the configuration

Standard handler

io.yupiik.logging.jul.handler.StandardHandler enables to log on stdout and stderr. For all level from finest/debug to info it will go on stdout and others (warnings, errors/severes) will go on stderr.

It can be configured with the std or standard alias too.

There is also its companion stdout or io.yupiik.logging.jul.handler.StdoutHandler which only outputs on stdout.

File Handler

A io.yupiik.logging.jul.handler.LocalFileHandler is also provided to be able to log in a file and rotate the log file. Indeed you can use JVM FileHandler but this one is a bit more powerful in practise if you don't run in a container and can't use a docker logging driver or equivalent.

Here is its configuration - all are prefixed with io.yupiik.logging.jul.handler.LocalFileHandler. and the standard configuration (encoding, level, ...) is still supported even if not listed.

Name Default Value Description
filenamePattern

$/logs/logs.%s.%03d.log

where log files are created, it uses String.format() and gives you the date and file number - in this order. If you replace `%s` by `%sH` you will append the hour in the filename. `%sHm` adds the minutes (a more precise pattern is not needed nor recommended).
noRotation false if `true`, once a log file is opened, it is kept for the life of the process.
overwrite false if `true`, existing files are reused.
truncateIfExists false if `true`, opening a file is not in append mode (previous content is erased).
limit 10 Megabytes limit size indicating the file should be rotated - in long format
dateCheckInterval 5 seconds how often the date should be computed to rotate the file (don't do it each time for performances reason, means you can get few records of next day in a file name with current day). In java Duration format (ex: `PT5S`).
bufferSize -1 if positive the in memory buffer used to store data before flushing them to the disk (in bytes)
archiveDirectory

$/logs/archives/

where compressed logs are put.
archiveFormat gzip zip or gzip.
archiveOlderThan -1 how many days files are kept before being compressed (in Duration Format)
purgeOlderThan -1 how many days files are kept before being deleted, note: it applies on archives and not log files so 2 days of archiving and 3 days of purge makes it deleted after 5 days (in Duration Format).
compressionLevel -1 In case of zip archiving the zip compression level (-1 for off or 0-9).
maxArchives -1 Max number of archives (zip/gzip) to keep, ignored if negative (you can review `io.yupiik.logging.jul.handler.LocalFileHandlerTest.purgeMaxArchive` for some sample configuration).

Duration Format

The format for the String to be parsed is PnDTnHnMn.nS where nD means n number of Days, nH means n number of Hours, nM means n number of Minutes, nS means n number of Seconds and T is a prefix that must be used before the part consisting of nHnMn.nS.

Async handler

io.yupiik.logging.jul.handler.AsyncHandler enables to handle asynchronously in a background thread log events (LogRecord).

io.yupiik.logging.jul.handler.AsyncHandler.delegate.class enables to configure the delegate class name.

The properties like io.yupiik.logging.jul.handler.AsyncHandler.formatter etc are forwarded to the delegate if set - and generally override the default ones.

IMPORTANT

logging asynchronously means you are loosing the context related to threads - if you are using a ThreadLocal and not yet a ScopedValue in a 100% virtual thread based application. For that purposes we enable the formatters and delegate handler to implement io.yupiik.logging.jul.api.RecordFreezer which will take the LogRecord when emitted, convert it to a new LogRecord (RecordFreezer.FrozenLogRecord is a default implementation) which is initialized at that moment and just read when emitted to the delegating handler to have the proper informations. This is often used for custom JSON entries mapper (similar to MDC/NDC) when the implementation relies on ThreadLocal.

Pattern formatter

The library also provides a pattern formatter. It does not use String.format as SimpleFormatter does and can be configured either with io.yupiik.logging.jul.formatter.PatternFormatter.pattern property or passing the pattern to the formatter alias (reflection free mode): pattern(<pattern to use>). Its syntax uses % to mark elements of the log record. Here is the list:

Name Description
%% Escapes `%` character.
%n End of line.
%l or %level Log record level.
%m or %message Log record message.
%c or %logger Logger name.
%C or %class Class name if exists or empty.
%M or %method Method name if exists or empty.
%d or %date The instant value. Note it can be followed by a date time formatter pattern between braces.
%x or %exception The exception. It will be preceeded by a new line if existing to ensure it integrates well in log output.
%T or %threadId Thread ID.
%t or %thread or %threadName Thread name - only works in synchronous mode.
%r Duration (in ms) since the startup of the application (creating of the pattern formatter actually).
%uuid Random UUID.

Pattern example value: %d [%l][%c][%C][%M] %m%x%n, it will output lines like 1970-01-01T00:00:00Z [INFO][the.logger][the.source][the.method] test message\n.

JSON formatter

JSON formatter relies on JSON-B until 1.0.2, JSON-P starting with 1.0.3 and no dependency starting with 1.0.8 so ensure to add the related dependencies. It can be done with this list for example:

<dependency>
  <groupId>org.apache.geronimo.specs</groupId>
  <artifactId>geronimo-json_1.1_spec</artifactId>
  <version>1.5</version>
</dependency>
<dependency>
  <groupId>org.apache.johnzon</groupId>
  <artifactId>johnzon-core</artifactId>
  <version>1.2.19</version>
</dependency>
TIP

the JSON formatter can be configured passing json(useUUID=[false|true],formatMessage=[true|false],customEntriesMapper=<fqn of a Function<LogRecord, Map<String, String>>>) value instead of just json. All configuration being optional.formatMessage enables to skip the message formatting when your application does not rely on it - faster and uses less the CPU, useUUID enables to force an unique ID in the record.customEntriesMapper enables to pass a function taking the log record and converting it to a map of data to append to the json object (must be String key/values).

Sample Configuration Files

As with native JUL LogManager, you can configure the runtime logging with the following system property: -Djava.util.logging.config.file=<path to config file>.

NOTE

don't forget -Djava.util.logging.manager=io.yupiik.logging.jul.YupiikLogManager too.

Here is a sample configuration switching to JSON logging:

.handlers = io.yupiik.logging.jul.handler.StandardHandler
io.yupiik.logging.jul.handler.StandardHandler.formatter = json

The same configuration for a standard inline logging (text style) but tuning the log level:

.handlers = io.yupiik.logging.jul.handler.StandardHandler
io.yupiik.logging.jul.handler.StandardHandler.level = FINEST
com.app.level = FINEST

Here is a configuration using a pattern:

.handlers = standard
standard.formatter = pattern(%d [%l][%c][%C][%M] %m%x%n)

And finally a configuration using file output instead of standard one:

.handlers = file
file.formatter = inline
TIP
you can set all properties as system properties and also environment variables (in uppercase and dots replaced by underscores).