gradle | ||
log4j-systemd-journal | ||
systemd-journal | ||
.gitignore | ||
build.gradle | ||
gradle.properties | ||
gradlew | ||
gradlew.bat | ||
LICENSE.txt | ||
README.md | ||
settings.gradle |
Systemd journal for Java
Reading systemd-journal from Java
Please see the junit test file to find out how to consume systemd journal from Java.
The implementation use bridj.
Log4j2 systemd-journal appender
This [Log4j][log4j] appender logs event meta data such as timestamp, logger name, exception stacktrace, [ThreadContext (MDC)][thread-context] or the thread name to [fields][systemd-journal-fields] in [systemd journal][systemd-journal].
Learn more about systemd-journal at Lennart Poettering's site [systemd for Developers III][systemd-for-developers] or at the manual page [systemd journal][systemd-journal].
Usage
Add the following Maven dependency to your project:
Gradle
dependency {
runtime "org.xbib:log4j-systemd-journal:2.13.3.0"
}
Runtime dependencies
- Java 11+
- Linux with systemd library installed (/usr/lib64/libsystemd.so)
- Log4j 2.12.0+
Note:
JNA requires execute permissions in java.io.tmpdir
(which defaults to /tmp
).
For example, if the folder is mounted with "noexec
" for security reasons, you need to define a different temporary directory for JNA:
-Djna.tmpdir=/tmp-folder/with/exec/permissions
Configuration
The appender can be configured with the following properties
Property name | Default | Type | Description |
---|---|---|---|
logSource |
false | boolean | Determines whether the log locations are logged. Note that there is a performance overhead when switched on. The data is logged in standard systemd journal fields CODE_FILE , CODE_LINE and CODE_FUNC . |
logStacktrace |
true | boolean | Determines whether the full exception stack trace is logged. This data is logged in the user field STACKTRACE . |
logThreadName |
true | boolean | Determines whether the thread name is logged. This data is logged in the user field THREAD_NAME . |
logLoggerName |
true | boolean | Determines whether the logger name is logged. This data is logged in the user field LOG4J_LOGGER . |
logAppenderName |
true | boolean | Determines whether the appender name is logged. This data is logged in the user field LOG4J_APPENDER . |
logThreadContext |
true | boolean | Determines whether the [thread context][thread-context] is logged. Each key/value pair is logged as user field with the threadContextPrefix prefix. |
threadContextPrefix |
THREAD_CONTEXT_ |
String | Determines how [thread context][thread-context] keys should be prefixed when logThreadContext is set to true. Note that keys need to match the regex pattern [A-Z0-9_]+ and are normalized otherwise. |
syslogIdentifier |
null | String | This data is logged in the user field SYSLOG_IDENTIFIER . If this is not set, the underlying system will use the command name (usually java ) instead. |
Example
log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO" packages="org.xbib.log4j.systemd">
<Appenders>
<Console name="console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
</Console>
<SystemdJournalAppender name="journal" logStacktrace="true" logSource="false" />
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="console" />
<AppenderRef ref="journal" />
</Root>
</Loggers>
</Configuration>
This will tell Log4j to log to [systemd journal][systemd-journal] as well as to stdout (console).
Note that a layout is optional for SystemdJournal
.
This is because meta data of a log event such as the timestamp, the logger name or the Java thread name are mapped to [systemd-journal fields][systemd-journal-fields] and need not be rendered into a string that loses all the semantic information.
YourExample.java
import org.apache.logging.log4j.*;
class YourExample {
private static Logger logger = LogManager.getLogger(YourExample.class);
public static void main(String[] args) {
ThreadContext.put("MY_KEY", "some value");
logger.info("this is an example");
}
}
Running this sample class will log a message to journald:
Systemd Journal
# journalctl -n
Okt 13 21:26:00 myhost java[2370]: this is an example
Use journalctl -o verbose
to show all fields:
# journalctl -o verbose -n
Di 2015-09-29 21:07:05.850017 CEST [s=45e0…;i=984;b=c257…;m=1833…;t=520e…;x=3e1e…]
PRIORITY=6
_TRANSPORT=journal
_UID=1000
_GID=1000
_CAP_EFFECTIVE=0
_SYSTEMD_OWNER_UID=1000
_SYSTEMD_SLICE=user-1000.slice
_MACHINE_ID=4abc6d…
_HOSTNAME=myhost
_SYSTEMD_CGROUP=/user.slice/user-1000.slice/session-2.scope
_SYSTEMD_SESSION=2
_SYSTEMD_UNIT=session-2.scope
_BOOT_ID=c257f8…
THREAD_NAME=main
LOG4J_LOGGER=org.xbib.log4j.systemd.SystemdJournalAppenderIntegrationTest
_COMM=java
_EXE=/usr/bin/java
MESSAGE=this is a test message with a MDC
CODE_FILE=SystemdJournalAppenderIntegrationTest.java
CODE_FUNC=testMessageWithMDC
CODE_LINE=36
THREAD_CONTEXT_MY_KEY=some value
SYSLOG_IDENTIFIER=log4j2-test
LOG4J_APPENDER=Journal
_PID=8224
_CMDLINE=/usr/bin/java …
_SOURCE_REALTIME_TIMESTAMP=1443553625850017
Note that the [ThreadContext][thread-context] key-value pair {"MY_KEY": "some value"}
is automatically added as field with prefix THREAD_CONTEXT
.
You can use the power of [systemd journal][systemd-journal] to filter for interesting messages. Example:
journalctl CODE_FUNC=testMessageWithMDC THREAD_NAME=main
will only show messages that are logged from the Java main thread in a method called testMessageWithMDC
.
Bridj or JNA
As you noted, I use both bridj and JNA. But only one is necessary. The only reason for this is that it works.
bridj looks easier and more powerful, but is getting old. I am considering a fork of bridj and implement a log4j2 appender for bridj, or porting the API methods sd_journal_open
, sd_journal_add_match
, etc. to JNA.
Feel free to submit patches.