join JNA-based log4j appender into this bridj-based consumer implementation, update to Gradle 6.4.1, Log4j 2.13.3
This commit is contained in:
parent
422641ab1d
commit
6927d31b70
30 changed files with 975 additions and 159 deletions
157
README.md
Normal file
157
README.md
Normal file
|
@ -0,0 +1,157 @@
|
|||
# Systemd journal for Java
|
||||
|
||||
[](https://travis-ci.org/jprante/systemd-journal)
|
||||
[](http://maven-badges.herokuapp.com/maven-central/org.xbib/log4j-systemd-journal)
|
||||
[](https://opensource.org/licenses/Apache-2.0)
|
||||
[](https://www.paypal.me/JoergPrante)
|
||||
|
||||
## 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
|
||||
<?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`
|
||||
```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.
|
||||
|
134
build.gradle
134
build.gradle
|
@ -1,122 +1,34 @@
|
|||
plugins {
|
||||
id "org.sonarqube" version "2.6.1"
|
||||
id "io.codearte.nexus-staging" version "0.11.0"
|
||||
id "de.marcphilipp.nexus-publish" version "0.4.0"
|
||||
id "io.codearte.nexus-staging" version "0.21.1"
|
||||
}
|
||||
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'maven'
|
||||
|
||||
dependencies {
|
||||
implementation "com.nativelibs4java:bridj:${project.property('bridj.version')}"
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-api:${project.property('junit.version')}"
|
||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${project.property('junit.version')}"
|
||||
testImplementation "org.mockito:mockito-junit-jupiter:${project.property('mockito.version')}"
|
||||
}
|
||||
|
||||
compileJava {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
compileTestJava {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
test {
|
||||
enabled = true
|
||||
useJUnitPlatform()
|
||||
systemProperty 'jna.debug', 'true'
|
||||
testLogging {
|
||||
events 'PASSED', 'FAILED', 'SKIPPED'
|
||||
}
|
||||
afterSuite { desc, result ->
|
||||
if (!desc.parent) {
|
||||
println "\nTest result: ${result.resultType}"
|
||||
println "Test summary: ${result.testCount} test, " +
|
||||
"${result.successfulTestCount} succeeded, " +
|
||||
"${result.failedTestCount} failed " +
|
||||
"${result.skippedTestCount} skipped "
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task javadocJar(type: Jar, dependsOn: classes) {
|
||||
from javadoc
|
||||
into "build/tmp"
|
||||
classifier 'javadoc'
|
||||
}
|
||||
|
||||
task sourcesJar(type: Jar, dependsOn: classes) {
|
||||
from sourceSets.main.allSource
|
||||
into "build/tmp"
|
||||
classifier 'sources'
|
||||
}
|
||||
|
||||
artifacts {
|
||||
archives javadocJar, sourcesJar
|
||||
wrapper {
|
||||
gradleVersion = "${project.property('gradle.wrapper.version')}"
|
||||
distributionType = Wrapper.DistributionType.ALL
|
||||
}
|
||||
|
||||
ext {
|
||||
user = 'jprante'
|
||||
projectDescription = 'Systemd journal bindings'
|
||||
scmUrl = 'https://github.com/jprante/systemd-journal'
|
||||
scmConnection = 'scm:git:git://github.com/jprante/systemd-journal.git'
|
||||
scmDeveloperConnection = 'scm:git:git://github.com/jprante/systemd-journal.git'
|
||||
name = 'systemd-journal'
|
||||
description = 'Systemd journal bindings and logging adapters for Java'
|
||||
inceptionYear = '2018'
|
||||
url = 'https://github.com/' + user + '/' + name
|
||||
scmUrl = 'https://github.com/' + user + '/' + name
|
||||
scmConnection = 'scm:git:git://github.com/' + user + '/' + name + '.git'
|
||||
scmDeveloperConnection = 'scm:git:ssh://git@github.com:' + user + '/' + name + '.git'
|
||||
issueManagementSystem = 'Github'
|
||||
issueManagementUrl = ext.scmUrl + '/issues'
|
||||
licenseName = 'The Apache License, Version 2.0'
|
||||
licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
|
||||
}
|
||||
|
||||
task sonaTypeUpload(type: Upload) {
|
||||
group = 'publish'
|
||||
configuration = configurations.archives
|
||||
uploadDescriptor = true
|
||||
repositories {
|
||||
if (project.hasProperty('ossrhUsername')) {
|
||||
mavenDeployer {
|
||||
beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
|
||||
repository(url: 'https://oss.sonatype.org/service/local/staging/deploy/maven2') {
|
||||
authentication(userName: ossrhUsername, password: ossrhPassword)
|
||||
}
|
||||
snapshotRepository(url: 'https://oss.sonatype.org/content/repositories/snapshots') {
|
||||
authentication(userName: ossrhUsername, password: ossrhPassword)
|
||||
}
|
||||
pom.project {
|
||||
groupId project.group
|
||||
artifactId project.name
|
||||
version project.version
|
||||
name project.name
|
||||
description description
|
||||
packaging 'jar'
|
||||
inceptionYear '2018'
|
||||
url scmUrl
|
||||
organization {
|
||||
name 'xbib'
|
||||
url 'http://xbib.org'
|
||||
}
|
||||
developers {
|
||||
developer {
|
||||
id user
|
||||
name 'Jörg Prante'
|
||||
email 'joergprante@gmail.com'
|
||||
url 'https://github.com/jprante'
|
||||
}
|
||||
}
|
||||
scm {
|
||||
url scmUrl
|
||||
connection scmConnection
|
||||
developerConnection scmDeveloperConnection
|
||||
}
|
||||
licenses {
|
||||
license {
|
||||
name 'The Apache License, Version 2.0'
|
||||
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
subprojects {
|
||||
apply plugin: 'java-library'
|
||||
apply from: rootProject.file('gradle/ide/idea.gradle')
|
||||
apply from: rootProject.file('gradle/compile/java.gradle')
|
||||
apply from: rootProject.file('gradle/test/junit5.gradle')
|
||||
apply from: rootProject.file('gradle/publishing/publication.gradle')
|
||||
}
|
||||
|
||||
nexusStaging {
|
||||
packageGroup = "org.xbib"
|
||||
}
|
||||
apply from: rootProject.file('gradle/publishing/sonatype.gradle')
|
||||
|
|
|
@ -2,7 +2,8 @@ group = org.xbib
|
|||
name = systemd-journal
|
||||
version = 1.0.0
|
||||
|
||||
gradle.wrapper.version = 6.4.1
|
||||
bridj.version = 0.7.0
|
||||
|
||||
junit.version = 5.4.2
|
||||
mockito.version = 2.27.0
|
||||
jna.version = 5.5.0
|
||||
log4j.version = 2.13.3
|
||||
mockito.version = 3.3.3
|
||||
|
|
43
gradle/compile/java.gradle
Normal file
43
gradle/compile/java.gradle
Normal file
|
@ -0,0 +1,43 @@
|
|||
|
||||
apply plugin: 'java-library'
|
||||
|
||||
java {
|
||||
modularity.inferModulePath.set(true)
|
||||
}
|
||||
|
||||
compileJava {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
compileTestJava {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes('Implementation-Version': project.version)
|
||||
}
|
||||
}
|
||||
|
||||
task sourcesJar(type: Jar, dependsOn: classes) {
|
||||
classifier 'sources'
|
||||
from sourceSets.main.allSource
|
||||
}
|
||||
|
||||
task javadocJar(type: Jar, dependsOn: javadoc) {
|
||||
classifier 'javadoc'
|
||||
}
|
||||
|
||||
artifacts {
|
||||
archives sourcesJar, javadocJar
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
options.compilerArgs << '-Xlint:all,-fallthrough'
|
||||
}
|
||||
|
||||
javadoc {
|
||||
options.addStringOption('Xdoclint:none', '-quiet')
|
||||
}
|
55
gradle/documentation/asciidoc.gradle
Normal file
55
gradle/documentation/asciidoc.gradle
Normal file
|
@ -0,0 +1,55 @@
|
|||
apply plugin: 'org.xbib.gradle.plugin.asciidoctor'
|
||||
|
||||
configurations {
|
||||
asciidoclet
|
||||
}
|
||||
|
||||
dependencies {
|
||||
asciidoclet "org.asciidoctor:asciidoclet:${project.property('asciidoclet.version')}"
|
||||
}
|
||||
|
||||
|
||||
asciidoctor {
|
||||
backends 'html5'
|
||||
outputDir = file("${rootProject.projectDir}/docs")
|
||||
separateOutputDirs = false
|
||||
attributes 'source-highlighter': 'coderay',
|
||||
idprefix: '',
|
||||
idseparator: '-',
|
||||
toc: 'left',
|
||||
doctype: 'book',
|
||||
icons: 'font',
|
||||
encoding: 'utf-8',
|
||||
sectlink: true,
|
||||
sectanchors: true,
|
||||
linkattrs: true,
|
||||
imagesdir: 'img',
|
||||
stylesheet: "${projectDir}/src/docs/asciidoc/css/foundation.css"
|
||||
}
|
||||
|
||||
|
||||
/*javadoc {
|
||||
options.docletpath = configurations.asciidoclet.files.asType(List)
|
||||
options.doclet = 'org.asciidoctor.Asciidoclet'
|
||||
//options.overview = "src/docs/asciidoclet/overview.adoc"
|
||||
options.addStringOption "-base-dir", "${projectDir}"
|
||||
options.addStringOption "-attribute",
|
||||
"name=${project.name},version=${project.version},title-link=https://github.com/xbib/${project.name}"
|
||||
configure(options) {
|
||||
noTimestamp = true
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
/*javadoc {
|
||||
options.docletpath = configurations.asciidoclet.files.asType(List)
|
||||
options.doclet = 'org.asciidoctor.Asciidoclet'
|
||||
options.overview = "${rootProject.projectDir}/src/docs/asciidoclet/overview.adoc"
|
||||
options.addStringOption "-base-dir", "${projectDir}"
|
||||
options.addStringOption "-attribute",
|
||||
"name=${project.name},version=${project.version},title-link=https://github.com/xbib/${project.name}"
|
||||
options.destinationDirectory(file("${projectDir}/docs/javadoc"))
|
||||
configure(options) {
|
||||
noTimestamp = true
|
||||
}
|
||||
}*/
|
13
gradle/ide/idea.gradle
Normal file
13
gradle/ide/idea.gradle
Normal file
|
@ -0,0 +1,13 @@
|
|||
apply plugin: 'idea'
|
||||
|
||||
idea {
|
||||
module {
|
||||
outputDir file('build/classes/java/main')
|
||||
testOutputDir file('build/classes/java/test')
|
||||
}
|
||||
}
|
||||
|
||||
if (project.convention.findPlugin(JavaPluginConvention)) {
|
||||
//sourceSets.main.output.classesDirs = file("build/classes/java/main")
|
||||
//sourceSets.test.output.classesDirs = file("build/classes/java/test")
|
||||
}
|
64
gradle/publishing/publication.gradle
Normal file
64
gradle/publishing/publication.gradle
Normal file
|
@ -0,0 +1,64 @@
|
|||
|
||||
apply plugin: "de.marcphilipp.nexus-publish"
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
mavenJava(MavenPublication) {
|
||||
from components.java
|
||||
artifact sourcesJar
|
||||
artifact javadocJar
|
||||
pom {
|
||||
name = project.name
|
||||
description = rootProject.ext.description
|
||||
url = rootProject.ext.url
|
||||
inceptionYear = rootProject.ext.inceptionYear
|
||||
packaging = 'jar'
|
||||
organization {
|
||||
name = 'xbib'
|
||||
url = 'https://xbib.org'
|
||||
}
|
||||
developers {
|
||||
developer {
|
||||
id = 'jprante'
|
||||
name = 'Jörg Prante'
|
||||
email = 'joergprante@gmail.com'
|
||||
url = 'https://github.com/jprante'
|
||||
}
|
||||
}
|
||||
scm {
|
||||
url = rootProject.ext.scmUrl
|
||||
connection = rootProject.ext.scmConnection
|
||||
developerConnection = rootProject.ext.scmDeveloperConnection
|
||||
}
|
||||
issueManagement {
|
||||
system = rootProject.ext.issueManagementSystem
|
||||
url = rootProject.ext.issueManagementUrl
|
||||
}
|
||||
licenses {
|
||||
license {
|
||||
name = rootProject.ext.licenseName
|
||||
url = rootProject.ext.licenseUrl
|
||||
distribution = 'repo'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (project.hasProperty("signing.keyId")) {
|
||||
apply plugin: 'signing'
|
||||
signing {
|
||||
sign publishing.publications.mavenJava
|
||||
}
|
||||
}
|
||||
|
||||
nexusPublishing {
|
||||
repositories {
|
||||
sonatype {
|
||||
username = project.property('ossrhUsername')
|
||||
password = project.property('ossrhPassword')
|
||||
packageGroup = "org.xbib"
|
||||
}
|
||||
}
|
||||
}
|
11
gradle/publishing/sonatype.gradle
Normal file
11
gradle/publishing/sonatype.gradle
Normal file
|
@ -0,0 +1,11 @@
|
|||
|
||||
if (project.hasProperty('ossrhUsername') && project.hasProperty('ossrhPassword')) {
|
||||
|
||||
apply plugin: 'io.codearte.nexus-staging'
|
||||
|
||||
nexusStaging {
|
||||
username = project.property('ossrhUsername')
|
||||
password = project.property('ossrhPassword')
|
||||
packageGroup = "org.xbib"
|
||||
}
|
||||
}
|
28
gradle/test/junit5.gradle
Normal file
28
gradle/test/junit5.gradle
Normal file
|
@ -0,0 +1,28 @@
|
|||
|
||||
def junitVersion = project.hasProperty('junit.version')?project.property('junit.version'):'5.6.2'
|
||||
def hamcrestVersion = project.hasProperty('hamcrest.version')?project.property('hamcrest.version'):'2.2'
|
||||
|
||||
dependencies {
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-api:${junitVersion}"
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-params:${junitVersion}"
|
||||
testImplementation "org.hamcrest:hamcrest-library:${hamcrestVersion}"
|
||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${junitVersion}"
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
systemProperty 'jna.debug_load', 'true'
|
||||
failFast = true
|
||||
testLogging {
|
||||
events 'STARTED', 'PASSED', 'FAILED', 'SKIPPED'
|
||||
}
|
||||
afterSuite { desc, result ->
|
||||
if (!desc.parent) {
|
||||
println "\nTest result: ${result.resultType}"
|
||||
println "Test summary: ${result.testCount} tests, " +
|
||||
"${result.successfulTestCount} succeeded, " +
|
||||
"${result.failedTestCount} failed, " +
|
||||
"${result.skippedTestCount} skipped"
|
||||
}
|
||||
}
|
||||
}
|
5
gradle/wrapper/gradle-wrapper.properties
vendored
5
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,6 +1,5 @@
|
|||
#Thu Mar 19 17:07:57 CET 2020
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.4.1-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
31
gradlew
vendored
31
gradlew
vendored
|
@ -82,6 +82,7 @@ esac
|
|||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
|
@ -129,6 +130,7 @@ fi
|
|||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
|
@ -154,19 +156,19 @@ if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
|||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
|
@ -175,14 +177,9 @@ save () {
|
|||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
|
4
gradlew.bat
vendored
4
gradlew.bat
vendored
|
@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=.
|
|||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
|
@ -81,6 +84,7 @@ set CMD_LINE_ARGS=%*
|
|||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
|
|
8
log4j-systemd-journal/build.gradle
Normal file
8
log4j-systemd-journal/build.gradle
Normal file
|
@ -0,0 +1,8 @@
|
|||
|
||||
version = "${project.property('log4j.version')}.0"
|
||||
|
||||
dependencies {
|
||||
implementation "net.java.dev.jna:jna:${project.property('jna.version')}"
|
||||
implementation "org.apache.logging.log4j:log4j-core:${project.property('log4j.version')}"
|
||||
testImplementation "org.mockito:mockito-junit-jupiter:${project.property('mockito.version')}"
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package org.xbib.log4j.systemd;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
|
||||
/**
|
||||
* Format java exception messages and stack traces.
|
||||
*/
|
||||
public final class ExceptionFormatter {
|
||||
|
||||
private ExceptionFormatter() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Format exception with stack trace.
|
||||
*
|
||||
* @param t the thrown object
|
||||
* @return the formatted exception
|
||||
*/
|
||||
public static String format(Throwable t) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
append(sb, t, 0, true);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Append Exception to string builder.
|
||||
* @param sb string builder
|
||||
* @param t the exception
|
||||
* @param level exception nested level
|
||||
* @param details details
|
||||
*
|
||||
*/
|
||||
private static void append(StringBuilder sb, Throwable t, int level, boolean details) {
|
||||
if (((t != null) && (t.getMessage() != null)) && (!t.getMessage().isEmpty())) {
|
||||
if (details && (level > 0)) {
|
||||
sb.append("\n\nCaused by\n");
|
||||
}
|
||||
sb.append(t.getMessage());
|
||||
}
|
||||
if (details) {
|
||||
if (t != null) {
|
||||
if ((t.getMessage() != null) && (t.getMessage().isEmpty())) {
|
||||
sb.append("\n\nCaused by ");
|
||||
} else {
|
||||
sb.append("\n\n");
|
||||
}
|
||||
}
|
||||
StringWriter sw = new StringWriter();
|
||||
if (t != null) {
|
||||
t.printStackTrace(new PrintWriter(sw));
|
||||
}
|
||||
sb.append(sw.toString());
|
||||
}
|
||||
if (t != null && t.getCause() != null) {
|
||||
append(sb, t.getCause(), level + 1, details);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
package org.xbib.log4j.systemd;
|
||||
|
||||
import org.apache.logging.log4j.Level;
|
||||
import org.apache.logging.log4j.core.Appender;
|
||||
import org.apache.logging.log4j.core.Core;
|
||||
import org.apache.logging.log4j.core.Filter;
|
||||
import org.apache.logging.log4j.core.Layout;
|
||||
import org.apache.logging.log4j.core.LogEvent;
|
||||
import org.apache.logging.log4j.core.appender.AbstractAppender;
|
||||
import org.apache.logging.log4j.core.config.Configuration;
|
||||
import org.apache.logging.log4j.core.config.Property;
|
||||
import org.apache.logging.log4j.core.config.plugins.Plugin;
|
||||
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
|
||||
import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
|
||||
import org.apache.logging.log4j.core.config.plugins.PluginElement;
|
||||
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
|
||||
import org.apache.logging.log4j.core.util.Booleans;
|
||||
import org.apache.logging.log4j.util.ReadOnlyStringMap;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* Log4j appender for systemd journal.
|
||||
*/
|
||||
@Plugin(name = "SystemdJournalAppender", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
|
||||
public class SystemdJournalAppender extends AbstractAppender {
|
||||
|
||||
private final SystemdLibraryAPI systemdLibraryAPI;
|
||||
|
||||
private final boolean logSource;
|
||||
|
||||
private final boolean logStacktrace;
|
||||
|
||||
private final boolean logThreadName;
|
||||
|
||||
private final boolean logLoggerName;
|
||||
|
||||
private final boolean logAppenderName;
|
||||
|
||||
private final boolean logThreadContext;
|
||||
|
||||
private final String threadContextPrefix;
|
||||
|
||||
private final String syslogIdentifier;
|
||||
|
||||
public SystemdJournalAppender(String name, Filter filter, Layout<?> layout, boolean ignoreExceptions,
|
||||
SystemdLibraryAPI systemdLibraryAPI,
|
||||
boolean logSource, boolean logStacktrace, boolean logThreadName,
|
||||
boolean logLoggerName, boolean logAppenderName, boolean logThreadContext,
|
||||
String threadContextPrefix, String syslogIdentifier) {
|
||||
super(name, filter, layout, ignoreExceptions, Property.EMPTY_ARRAY);
|
||||
this.systemdLibraryAPI = systemdLibraryAPI != null ? systemdLibraryAPI : SystemdLibraryAPI.getInstance();
|
||||
this.logSource = logSource;
|
||||
this.logStacktrace = logStacktrace;
|
||||
this.logThreadName = logThreadName;
|
||||
this.logLoggerName = logLoggerName;
|
||||
this.logAppenderName = logAppenderName;
|
||||
this.logThreadContext = logThreadContext;
|
||||
if (threadContextPrefix == null) {
|
||||
this.threadContextPrefix = "THREAD_CONTEXT_";
|
||||
} else {
|
||||
this.threadContextPrefix = normalizeKey(threadContextPrefix);
|
||||
}
|
||||
this.syslogIdentifier = syslogIdentifier;
|
||||
}
|
||||
|
||||
@PluginFactory
|
||||
public static SystemdJournalAppender createAppender(@PluginAttribute("name") final String name,
|
||||
@PluginAttribute("ignoreExceptions") final String ignoreExceptionsString,
|
||||
@PluginAttribute("logSource") final String logSourceString,
|
||||
@PluginAttribute("logStacktrace") final String logStacktraceString,
|
||||
@PluginAttribute("logLoggerName") final String logLoggerNameString,
|
||||
@PluginAttribute("logAppenderName") final String logAppenderNameString,
|
||||
@PluginAttribute("logThreadName") final String logThreadNameString,
|
||||
@PluginAttribute("logThreadContext") final String logThreadContextString,
|
||||
@PluginAttribute("threadContextPrefix") final String threadContextPrefix,
|
||||
@PluginAttribute("syslogIdentifier") final String syslogIdentifier,
|
||||
@PluginElement("Layout") final Layout<?> layout,
|
||||
@PluginElement("Filter") final Filter filter,
|
||||
@PluginConfiguration final Configuration config) {
|
||||
boolean ignoreExceptions = Booleans.parseBoolean(ignoreExceptionsString, true);
|
||||
boolean logSource = Booleans.parseBoolean(logSourceString, false);
|
||||
boolean logStacktrace = Booleans.parseBoolean(logStacktraceString, true);
|
||||
boolean logThreadName = Booleans.parseBoolean(logThreadNameString, true);
|
||||
boolean logLoggerName = Booleans.parseBoolean(logLoggerNameString, true);
|
||||
boolean logAppenderName = Booleans.parseBoolean(logAppenderNameString, true);
|
||||
boolean logThreadContext = Booleans.parseBoolean(logThreadContextString, true);
|
||||
if (name == null) {
|
||||
LOGGER.error("No name provided for SystemdJournalAppender");
|
||||
return null;
|
||||
}
|
||||
SystemdLibraryAPI systemdLibraryAPI = SystemdLibraryAPI.getInstance();
|
||||
return new SystemdJournalAppender(name, filter, layout, ignoreExceptions,
|
||||
systemdLibraryAPI,
|
||||
logSource, logStacktrace, logThreadName, logLoggerName, logAppenderName,
|
||||
logThreadContext, threadContextPrefix, syslogIdentifier);
|
||||
}
|
||||
|
||||
private int log4jLevelToJournalPriority(Level level) {
|
||||
switch (level.getStandardLevel()) {
|
||||
case FATAL:
|
||||
return 2;
|
||||
case ERROR:
|
||||
return 3;
|
||||
case WARN:
|
||||
return 4;
|
||||
case INFO:
|
||||
return 6;
|
||||
case DEBUG:
|
||||
case TRACE:
|
||||
return 7;
|
||||
default:
|
||||
throw new IllegalArgumentException("unable to map log level: " + level);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void append(LogEvent event) {
|
||||
List<Object> args = new ArrayList<>();
|
||||
args.add(buildFormattedMessage(event));
|
||||
args.add("PRIORITY=%d");
|
||||
args.add(log4jLevelToJournalPriority(event.getLevel()));
|
||||
if (logThreadName) {
|
||||
args.add("THREAD_NAME=%s");
|
||||
args.add(event.getThreadName());
|
||||
}
|
||||
if (logLoggerName) {
|
||||
args.add("LOG4J_LOGGER=%s");
|
||||
args.add(event.getLoggerName());
|
||||
}
|
||||
if (logAppenderName) {
|
||||
args.add("LOG4J_APPENDER=%s");
|
||||
args.add(getName());
|
||||
}
|
||||
if (logStacktrace && event.getThrown() != null) {
|
||||
args.add("STACKTRACE=%s");
|
||||
args.add(ExceptionFormatter.format(event.getThrown()));
|
||||
}
|
||||
if (logSource && event.getSource() != null) {
|
||||
String fileName = event.getSource().getFileName();
|
||||
args.add("CODE_FILE=%s");
|
||||
args.add(fileName);
|
||||
String methodName = event.getSource().getMethodName();
|
||||
args.add("CODE_FUNC=%s");
|
||||
args.add(methodName);
|
||||
int lineNumber = event.getSource().getLineNumber();
|
||||
args.add("CODE_LINE=%d");
|
||||
args.add(lineNumber);
|
||||
}
|
||||
if (logThreadContext) {
|
||||
ReadOnlyStringMap context = event.getContextData();
|
||||
if (context != null) {
|
||||
for (Entry<String, String> entry : context.toMap().entrySet()) {
|
||||
String key = entry.getKey();
|
||||
args.add(threadContextPrefix + normalizeKey(key) + "=%s");
|
||||
args.add(entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (syslogIdentifier != null && !syslogIdentifier.isEmpty()) {
|
||||
args.add("SYSLOG_IDENTIFIER=%s");
|
||||
args.add(syslogIdentifier);
|
||||
}
|
||||
args.add(null);
|
||||
int rc = systemdLibraryAPI.journal_send("MESSAGE=%s", args);
|
||||
if (rc != 0) {
|
||||
LOGGER.error("sd_journal_send failed: " + rc);
|
||||
}
|
||||
}
|
||||
|
||||
private String buildFormattedMessage(LogEvent event) {
|
||||
if (getLayout() != null) {
|
||||
return new String(getLayout().toByteArray(event), StandardCharsets.UTF_8);
|
||||
}
|
||||
return event.getMessage().getFormattedMessage();
|
||||
}
|
||||
|
||||
private static String normalizeKey(String key) {
|
||||
return key.toUpperCase().replaceAll("[^_A-Z0-9]", "_");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package org.xbib.log4j.systemd;
|
||||
|
||||
import com.sun.jna.Library;
|
||||
|
||||
public interface SystemdLibrary extends Library {
|
||||
|
||||
int sd_journal_send(String format, Object... args);
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package org.xbib.log4j.systemd;
|
||||
|
||||
import com.sun.jna.Native;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The systemd library API, loaded by Java Native Access (JNA).
|
||||
*
|
||||
* The native library is loaded only once, so this class is a singleton.
|
||||
*/
|
||||
public class SystemdLibraryAPI {
|
||||
|
||||
private static final SystemdLibraryAPI instance = new SystemdLibraryAPI();
|
||||
|
||||
private final SystemdLibrary systemdLibrary;
|
||||
|
||||
private SystemdLibraryAPI() {
|
||||
this.systemdLibrary = loadLibrary();
|
||||
}
|
||||
|
||||
public static SystemdLibraryAPI getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public int journal_send(String format, Object... args) {
|
||||
return systemdLibrary.sd_journal_send(format, args);
|
||||
}
|
||||
|
||||
public int journal_send(String format, List<Object> args) {
|
||||
return systemdLibrary.sd_journal_send(format, args.toArray());
|
||||
}
|
||||
|
||||
private static SystemdLibrary loadLibrary() {
|
||||
try {
|
||||
return Native.load("systemd", SystemdLibrary.class);
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
throw new RuntimeException("Failed to load systemd library." +
|
||||
" Please note that JNA requires an executable temporary folder." +
|
||||
" It can be explicitly defined with -Djna.tmpdir", e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package org.xbib.log4j.systemd;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.ThreadContext;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledOnOs;
|
||||
import org.junit.jupiter.api.condition.OS;
|
||||
|
||||
@EnabledOnOs({OS.LINUX})
|
||||
class SystemdJournalAppenderIntegrationTest {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(SystemdJournalAppenderIntegrationTest.class.getName());
|
||||
|
||||
@BeforeEach
|
||||
void clearMdc() {
|
||||
ThreadContext.clearAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMessages() {
|
||||
logger.trace("this is a test message with level TRACE");
|
||||
logger.debug("this is a test message with level DEBUG");
|
||||
logger.info("this is a test message with level INFO");
|
||||
logger.warn("this is a test message with level WARN");
|
||||
logger.error("this is a test message with level ERROR");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMessageWithUnicode() {
|
||||
logger.info("this is a test message with unicode: →←üöß");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMessageWithMDC() {
|
||||
ThreadContext.put("some key1", "some value %d");
|
||||
ThreadContext.put("some key2", "some other value with unicode: →←üöß");
|
||||
logger.info("this is a test message with a MDC");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMessageWithPlaceholders() {
|
||||
ThreadContext.put("some key1%s", "%1$");
|
||||
ThreadContext.put("%1$", "%1$");
|
||||
logger.info("this is a test message with special placeholder characters: %1$");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMessageWithStacktrace() {
|
||||
logger.info("this is a test message with an exception", new RuntimeException("some exception"));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
package org.xbib.log4j.systemd;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.logging.log4j.Level;
|
||||
import org.apache.logging.log4j.ThreadContext;
|
||||
import org.apache.logging.log4j.core.LogEvent;
|
||||
import org.apache.logging.log4j.core.impl.Log4jLogEvent;
|
||||
import org.apache.logging.log4j.message.Message;
|
||||
import org.apache.logging.log4j.spi.DefaultThreadContextMap;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledOnOs;
|
||||
import org.junit.jupiter.api.condition.OS;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@EnabledOnOs({OS.LINUX})
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class SystemdJournalAppenderTest {
|
||||
|
||||
@Mock
|
||||
private Message message;
|
||||
|
||||
@Mock
|
||||
private SystemdLibraryAPI api;
|
||||
|
||||
@BeforeEach
|
||||
void prepare() {
|
||||
ThreadContext.clearAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSimple() {
|
||||
SystemdLibraryAPI api = mock(SystemdLibraryAPI.class);
|
||||
Message message = mock(Message.class);
|
||||
SystemdJournalAppender journalAppender =
|
||||
new SystemdJournalAppender("Journal", null, null, false, api,
|
||||
false, false, false, false, false, false, null, null);
|
||||
when(message.getFormattedMessage()).thenReturn("some message");
|
||||
LogEvent event = new Log4jLogEvent.Builder().setMessage(message).setLevel(Level.INFO).build();
|
||||
journalAppender.append(event);
|
||||
List<Object> expectedArgs = new ArrayList<>();
|
||||
expectedArgs.add("some message");
|
||||
expectedArgs.add("PRIORITY=%d");
|
||||
expectedArgs.add(6);
|
||||
expectedArgs.add(null);
|
||||
verify(api).journal_send("MESSAGE=%s", expectedArgs);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLogSource() {
|
||||
SystemdLibraryAPI api = mock(SystemdLibraryAPI.class);
|
||||
Message message = mock(Message.class);
|
||||
SystemdJournalAppender journalAppender =
|
||||
new SystemdJournalAppender("Journal", null, null, false, api,
|
||||
true, false, false, false, false, false, null, null);
|
||||
when(message.getFormattedMessage()).thenReturn("some message");
|
||||
LogEvent event = new Log4jLogEvent.Builder() //
|
||||
.setMessage(message)//
|
||||
.setLoggerFqcn(journalAppender.getClass().getName())//
|
||||
.setLevel(Level.INFO).build();
|
||||
event.setIncludeLocation(true);
|
||||
journalAppender.append(event);
|
||||
List<Object> expectedArgs = new ArrayList<>();
|
||||
expectedArgs.add("some message");
|
||||
expectedArgs.add("PRIORITY=%d");
|
||||
expectedArgs.add(6);
|
||||
expectedArgs.add("CODE_FILE=%s");
|
||||
expectedArgs.add("SystemdJournalAppenderTest.java");
|
||||
expectedArgs.add("CODE_FUNC=%s");
|
||||
expectedArgs.add("testLogSource");
|
||||
expectedArgs.add("CODE_LINE=%d");
|
||||
expectedArgs.add(69);
|
||||
expectedArgs.add(null);
|
||||
verify(api).journal_send("MESSAGE=%s", expectedArgs);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDoNotLogException() {
|
||||
SystemdLibraryAPI api = mock(SystemdLibraryAPI.class);
|
||||
Message message = mock(Message.class);
|
||||
SystemdJournalAppender journalAppender =
|
||||
new SystemdJournalAppender("Journal", null, null, false, api,
|
||||
false, false, false, false, false, false, null, null);
|
||||
when(message.getFormattedMessage()).thenReturn("some message");
|
||||
LogEvent event = new Log4jLogEvent.Builder()
|
||||
.setMessage(message)
|
||||
.setLoggerFqcn(journalAppender.getClass().getName())
|
||||
.setThrown(new Throwable())
|
||||
.setLevel(Level.INFO).build();
|
||||
event.setIncludeLocation(true);
|
||||
journalAppender.append(event);
|
||||
List<Object> expectedArgs = new ArrayList<>();
|
||||
expectedArgs.add("some message");
|
||||
expectedArgs.add("PRIORITY=%d");
|
||||
expectedArgs.add(6);
|
||||
expectedArgs.add(null);
|
||||
verify(api).journal_send("MESSAGE=%s", expectedArgs);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testThreadAndContext() {
|
||||
SystemdLibraryAPI api = mock(SystemdLibraryAPI.class);
|
||||
Message message = mock(Message.class);
|
||||
SystemdJournalAppender journalAppender =
|
||||
new SystemdJournalAppender("Journal", null, null, false, api,
|
||||
false, false, true, true, true, true, null, "some-identifier");
|
||||
when(message.getFormattedMessage()).thenReturn("some message");
|
||||
DefaultThreadContextMap contextMap = new DefaultThreadContextMap();
|
||||
LogEvent event = mock(LogEvent.class);
|
||||
when(event.getMessage()).thenReturn(message);
|
||||
when(event.getLoggerName()).thenReturn("some logger");
|
||||
when(event.getLevel()).thenReturn(Level.INFO);
|
||||
when(event.getThreadName()).thenReturn("the thread");
|
||||
when(event.getContextData()).thenReturn(contextMap);
|
||||
contextMap.put("foo%s$1%d", "bar");
|
||||
journalAppender.append(event);
|
||||
List<Object> expectedArgs = new ArrayList<>();
|
||||
expectedArgs.add("some message");
|
||||
expectedArgs.add("PRIORITY=%d");
|
||||
expectedArgs.add(6);
|
||||
expectedArgs.add("THREAD_NAME=%s");
|
||||
expectedArgs.add("the thread");
|
||||
expectedArgs.add("LOG4J_LOGGER=%s");
|
||||
expectedArgs.add("some logger");
|
||||
expectedArgs.add("LOG4J_APPENDER=%s");
|
||||
expectedArgs.add("Journal");
|
||||
expectedArgs.add("THREAD_CONTEXT_FOO_S_1_D=%s");
|
||||
expectedArgs.add("bar");
|
||||
expectedArgs.add("SYSLOG_IDENTIFIER=%s");
|
||||
expectedArgs.add("some-identifier");
|
||||
expectedArgs.add(null);
|
||||
verify(api).journal_send("MESSAGE=%s", expectedArgs);
|
||||
}
|
||||
}
|
36
log4j-systemd-journal/src/test/resources/log4j2-test.xml
Normal file
36
log4j-systemd-journal/src/test/resources/log4j2-test.xml
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?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="JournalWithLayout"
|
||||
logStacktrace="true"
|
||||
logThreadName="true"
|
||||
logSource="true"
|
||||
logLoggerName="true"
|
||||
logAppenderName="true"
|
||||
logThreadContext="true"
|
||||
threadContextPrefix="THREAD_CONTEXT_"
|
||||
syslogIdentifier="log4j2-test-with-layout">
|
||||
<PatternLayout pattern="[%t] %-5level - %msg%n" />
|
||||
</SystemdJournalAppender>
|
||||
<SystemdJournalAppender name="JournalWithoutLayout"
|
||||
logStacktrace="true"
|
||||
logThreadName="true"
|
||||
logSource="true"
|
||||
logLoggerName="true"
|
||||
logAppenderName="true"
|
||||
logThreadContext="true"
|
||||
threadContextPrefix="THREAD_CONTEXT_"
|
||||
syslogIdentifier="log4j2-test-no-layout"
|
||||
/>
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
<Root level="INFO">
|
||||
<AppenderRef ref="Console" />
|
||||
<AppenderRef ref="JournalWithLayout" />
|
||||
<AppenderRef ref="JournalWithoutLayout" />
|
||||
</Root>
|
||||
</Loggers>
|
||||
</Configuration>
|
2
settings.gradle
Normal file
2
settings.gradle
Normal file
|
@ -0,0 +1,2 @@
|
|||
include 'systemd-journal'
|
||||
include 'log4j-systemd-journal'
|
5
systemd-journal/build.gradle
Normal file
5
systemd-journal/build.gradle
Normal file
|
@ -0,0 +1,5 @@
|
|||
|
||||
dependencies {
|
||||
implementation "com.nativelibs4java:bridj:${project.property('bridj.version')}"
|
||||
testImplementation "org.mockito:mockito-junit-jupiter:${project.property('mockito.version')}"
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package org.xbib.systemd;
|
||||
package org.xbib.systemd.journal;
|
||||
|
||||
public class DefaultJournalEntry implements JournalEntry {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package org.xbib.systemd;
|
||||
package org.xbib.systemd.journal;
|
||||
|
||||
public interface JournalEntry {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package org.xbib.systemd;
|
||||
package org.xbib.systemd.journal;
|
||||
|
||||
public interface Syslog {
|
||||
int LOG_EMERG = 0;
|
|
@ -1,4 +1,4 @@
|
|||
package org.xbib.systemd;
|
||||
package org.xbib.systemd.journal;
|
||||
|
||||
import org.bridj.Pointer;
|
||||
import org.bridj.SizeT;
|
|
@ -1,9 +1,6 @@
|
|||
package org.xbib.systemd;
|
||||
package org.xbib.systemd.journal;
|
||||
|
||||
import org.bridj.BridJ;
|
||||
import org.bridj.CRuntime;
|
||||
import org.bridj.Pointer;
|
||||
import org.bridj.SizeT;
|
||||
import org.bridj.*;
|
||||
import org.bridj.ann.Library;
|
||||
import org.bridj.ann.Ptr;
|
||||
import org.bridj.ann.Runtime;
|
||||
|
@ -32,10 +29,6 @@ public class SystemdJournalLibrary {
|
|||
|
||||
public static final String SD_ID128_FORMAT_STR = "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x";
|
||||
|
||||
public static Pointer<Byte> sd_id128_to_string(sd_id128 id, Pointer<Byte> s) {
|
||||
return Pointer.pointerToAddress(sd_id128_to_string(id, Pointer.getPeer(s)), Byte.class);
|
||||
}
|
||||
|
||||
@Ptr
|
||||
protected native static long sd_id128_to_string(sd_id128 id, @Ptr long s);
|
||||
|
||||
|
@ -81,7 +74,7 @@ public class SystemdJournalLibrary {
|
|||
|
||||
protected native static int sd_journal_send(@Ptr long format, Object... varArgs1);
|
||||
|
||||
public static int sd_journal_sendv(Pointer iov, int n) {
|
||||
public static int sd_journal_sendv(Pointer<? extends StructObject> iov, int n) {
|
||||
return sd_journal_sendv(Pointer.getPeer(iov), n);
|
||||
}
|
||||
|
||||
|
@ -121,7 +114,7 @@ public class SystemdJournalLibrary {
|
|||
@Ptr long format, Object... varArgs1);
|
||||
|
||||
public static int sd_journal_sendv_with_location(Pointer<Byte> file, Pointer<Byte> line, Pointer<Byte> func,
|
||||
Pointer iov, int n) {
|
||||
Pointer<? extends StructObject> iov, int n) {
|
||||
return sd_journal_sendv_with_location(Pointer.getPeer(file), Pointer.getPeer(line), Pointer.getPeer(func),
|
||||
Pointer.getPeer(iov), n);
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package org.xbib.systemd;
|
||||
package org.xbib.systemd.journal;
|
||||
|
||||
import java.io.IOException;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package org.xbib.systemd;
|
||||
package org.xbib.systemd.journal;
|
||||
|
||||
import org.bridj.BridJ;
|
||||
import org.bridj.Pointer;
|
||||
|
@ -18,13 +18,13 @@ public class sd_id128 extends StructObject {
|
|||
|
||||
@Array({16})
|
||||
@Field(0)
|
||||
public Pointer<Byte > bytes() {
|
||||
public Pointer<Byte> bytes() {
|
||||
return io.getPointerField(this, 0);
|
||||
}
|
||||
|
||||
@Array({2})
|
||||
@Field(1)
|
||||
public Pointer<Long > qwords() {
|
||||
public Pointer<Long> qwords() {
|
||||
return io.getPointerField(this, 1);
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@ public class sd_id128 extends StructObject {
|
|||
super();
|
||||
}
|
||||
|
||||
public sd_id128(Pointer pointer) {
|
||||
public sd_id128(Pointer<? extends StructObject> pointer) {
|
||||
super(pointer);
|
||||
}
|
||||
}
|
|
@ -1,14 +1,14 @@
|
|||
package org.xbib.systemd;
|
||||
package org.xbib.systemd.journal;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledOnOs;
|
||||
import org.junit.jupiter.api.condition.EnabledOnOs;
|
||||
import org.junit.jupiter.api.condition.OS;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@DisabledOnOs({OS.MAC, OS.WINDOWS})
|
||||
@EnabledOnOs({OS.LINUX})
|
||||
class SystemdJournalReaderTest {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(SystemdJournalReaderTest.class.getName());
|
||||
|
@ -18,7 +18,7 @@ class SystemdJournalReaderTest {
|
|||
SystemdJournalConsumer consumer = new SystemdJournalConsumer("SYSLOG_IDENTIFIER=su",
|
||||
entry -> logger.log(Level.INFO, entry.toString()));
|
||||
Executors.newSingleThreadExecutor().submit(consumer);
|
||||
// exit after 1 minute
|
||||
Thread.sleep(60000L);
|
||||
// consuming for some seconds
|
||||
Thread.sleep(10000L);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue