From 6927d31b708ca058104e4a684a25132aa19592af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=CC=88rg=20Prante?= Date: Mon, 8 Jun 2020 11:14:54 +0200 Subject: [PATCH] join JNA-based log4j appender into this bridj-based consumer implementation, update to Gradle 6.4.1, Log4j 2.13.3 --- README.md | 157 +++++++++++++++ build.gradle | 134 +++---------- gradle.properties | 7 +- gradle/compile/java.gradle | 43 ++++ gradle/documentation/asciidoc.gradle | 55 ++++++ gradle/ide/idea.gradle | 13 ++ gradle/publishing/publication.gradle | 64 ++++++ gradle/publishing/sonatype.gradle | 11 ++ gradle/test/junit5.gradle | 28 +++ gradle/wrapper/gradle-wrapper.properties | 5 +- gradlew | 31 ++- gradlew.bat | 4 + log4j-systemd-journal/build.gradle | 8 + .../log4j/systemd/ExceptionFormatter.java | 59 ++++++ .../log4j/systemd/SystemdJournalAppender.java | 183 ++++++++++++++++++ .../xbib/log4j/systemd/SystemdLibrary.java | 8 + .../xbib/log4j/systemd/SystemdLibraryAPI.java | 43 ++++ ...SystemdJournalAppenderIntegrationTest.java | 54 ++++++ .../systemd/SystemdJournalAppenderTest.java | 141 ++++++++++++++ .../src/test/resources/log4j2-test.xml | 36 ++++ settings.gradle | 2 + systemd-journal/build.gradle | 5 + .../systemd/journal}/DefaultJournalEntry.java | 2 +- .../xbib/systemd/journal}/JournalEntry.java | 2 +- .../org/xbib/systemd/journal}/Syslog.java | 2 +- .../journal}/SystemdJournalConsumer.java | 2 +- .../journal}/SystemdJournalLibrary.java | 15 +- .../journal}/SystemdJournalListener.java | 2 +- .../org/xbib/systemd/journal}/sd_id128.java | 8 +- .../journal}/SystemdJournalReaderTest.java | 10 +- 30 files changed, 975 insertions(+), 159 deletions(-) create mode 100644 README.md create mode 100644 gradle/compile/java.gradle create mode 100644 gradle/documentation/asciidoc.gradle create mode 100644 gradle/ide/idea.gradle create mode 100644 gradle/publishing/publication.gradle create mode 100644 gradle/publishing/sonatype.gradle create mode 100644 gradle/test/junit5.gradle create mode 100644 log4j-systemd-journal/build.gradle create mode 100644 log4j-systemd-journal/src/main/java/org/xbib/log4j/systemd/ExceptionFormatter.java create mode 100644 log4j-systemd-journal/src/main/java/org/xbib/log4j/systemd/SystemdJournalAppender.java create mode 100644 log4j-systemd-journal/src/main/java/org/xbib/log4j/systemd/SystemdLibrary.java create mode 100644 log4j-systemd-journal/src/main/java/org/xbib/log4j/systemd/SystemdLibraryAPI.java create mode 100644 log4j-systemd-journal/src/test/java/org/xbib/log4j/systemd/SystemdJournalAppenderIntegrationTest.java create mode 100644 log4j-systemd-journal/src/test/java/org/xbib/log4j/systemd/SystemdJournalAppenderTest.java create mode 100644 log4j-systemd-journal/src/test/resources/log4j2-test.xml create mode 100644 settings.gradle create mode 100644 systemd-journal/build.gradle rename {src/main/java/org/xbib/systemd => systemd-journal/src/main/java/org/xbib/systemd/journal}/DefaultJournalEntry.java (99%) rename {src/main/java/org/xbib/systemd => systemd-journal/src/main/java/org/xbib/systemd/journal}/JournalEntry.java (97%) rename {src/main/java/org/xbib/systemd => systemd-journal/src/main/java/org/xbib/systemd/journal}/Syslog.java (86%) rename {src/main/java/org/xbib/systemd => systemd-journal/src/main/java/org/xbib/systemd/journal}/SystemdJournalConsumer.java (99%) rename {src/main/java/org/xbib/systemd => systemd-journal/src/main/java/org/xbib/systemd/journal}/SystemdJournalLibrary.java (97%) rename {src/main/java/org/xbib/systemd => systemd-journal/src/main/java/org/xbib/systemd/journal}/SystemdJournalListener.java (80%) rename {src/main/java/org/xbib/systemd => systemd-journal/src/main/java/org/xbib/systemd/journal}/sd_id128.java (76%) rename {src/test/java/org/xbib/systemd => systemd-journal/src/test/java/org/xbib/systemd/journal}/SystemdJournalReaderTest.java (77%) diff --git a/README.md b/README.md new file mode 100644 index 0000000..c4f0783 --- /dev/null +++ b/README.md @@ -0,0 +1,157 @@ +# Systemd journal for Java + +[![Build Status](https://travis-ci.org/jprante/systemd-journal-appender.png?branch=master)](https://travis-ci.org/jprante/systemd-journal) +[![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.xbib/log4j-systemd-journal/badge.svg)](http://maven-badges.herokuapp.com/maven-central/org.xbib/log4j-systemd-journal) +[![Apache License](https://img.shields.io/github/license/jprante/log4j-systemd-journal.svg)](https://opensource.org/licenses/Apache-2.0) +[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](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 + + + + + + + + + + + + + + + +``` + +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. + diff --git a/build.gradle b/build.gradle index 55f6ff7..6fe61a0 100644 --- a/build.gradle +++ b/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') diff --git a/gradle.properties b/gradle.properties index 43c961c..1d9ee76 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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 diff --git a/gradle/compile/java.gradle b/gradle/compile/java.gradle new file mode 100644 index 0000000..c9bba7f --- /dev/null +++ b/gradle/compile/java.gradle @@ -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') +} diff --git a/gradle/documentation/asciidoc.gradle b/gradle/documentation/asciidoc.gradle new file mode 100644 index 0000000..87ba22e --- /dev/null +++ b/gradle/documentation/asciidoc.gradle @@ -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 + } +}*/ diff --git a/gradle/ide/idea.gradle b/gradle/ide/idea.gradle new file mode 100644 index 0000000..64e2167 --- /dev/null +++ b/gradle/ide/idea.gradle @@ -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") +} diff --git a/gradle/publishing/publication.gradle b/gradle/publishing/publication.gradle new file mode 100644 index 0000000..c35fcb9 --- /dev/null +++ b/gradle/publishing/publication.gradle @@ -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" + } + } +} diff --git a/gradle/publishing/sonatype.gradle b/gradle/publishing/sonatype.gradle new file mode 100644 index 0000000..e1813f3 --- /dev/null +++ b/gradle/publishing/sonatype.gradle @@ -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" + } +} diff --git a/gradle/test/junit5.gradle b/gradle/test/junit5.gradle new file mode 100644 index 0000000..f994dba --- /dev/null +++ b/gradle/test/junit5.gradle @@ -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" + } + } +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 58879c5..21e622d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/gradlew b/gradlew index 83f2acf..fbd7c51 100755 --- a/gradlew +++ b/gradlew @@ -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" "$@" diff --git a/gradlew.bat b/gradlew.bat index 24467a1..a9f778a 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -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% diff --git a/log4j-systemd-journal/build.gradle b/log4j-systemd-journal/build.gradle new file mode 100644 index 0000000..7d5dd46 --- /dev/null +++ b/log4j-systemd-journal/build.gradle @@ -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')}" +} diff --git a/log4j-systemd-journal/src/main/java/org/xbib/log4j/systemd/ExceptionFormatter.java b/log4j-systemd-journal/src/main/java/org/xbib/log4j/systemd/ExceptionFormatter.java new file mode 100644 index 0000000..049d1d8 --- /dev/null +++ b/log4j-systemd-journal/src/main/java/org/xbib/log4j/systemd/ExceptionFormatter.java @@ -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); + } + } +} diff --git a/log4j-systemd-journal/src/main/java/org/xbib/log4j/systemd/SystemdJournalAppender.java b/log4j-systemd-journal/src/main/java/org/xbib/log4j/systemd/SystemdJournalAppender.java new file mode 100644 index 0000000..1ca1978 --- /dev/null +++ b/log4j-systemd-journal/src/main/java/org/xbib/log4j/systemd/SystemdJournalAppender.java @@ -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 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 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]", "_"); + } +} diff --git a/log4j-systemd-journal/src/main/java/org/xbib/log4j/systemd/SystemdLibrary.java b/log4j-systemd-journal/src/main/java/org/xbib/log4j/systemd/SystemdLibrary.java new file mode 100644 index 0000000..eb39778 --- /dev/null +++ b/log4j-systemd-journal/src/main/java/org/xbib/log4j/systemd/SystemdLibrary.java @@ -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); +} diff --git a/log4j-systemd-journal/src/main/java/org/xbib/log4j/systemd/SystemdLibraryAPI.java b/log4j-systemd-journal/src/main/java/org/xbib/log4j/systemd/SystemdLibraryAPI.java new file mode 100644 index 0000000..758eb50 --- /dev/null +++ b/log4j-systemd-journal/src/main/java/org/xbib/log4j/systemd/SystemdLibraryAPI.java @@ -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 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); + } + } +} diff --git a/log4j-systemd-journal/src/test/java/org/xbib/log4j/systemd/SystemdJournalAppenderIntegrationTest.java b/log4j-systemd-journal/src/test/java/org/xbib/log4j/systemd/SystemdJournalAppenderIntegrationTest.java new file mode 100644 index 0000000..ba24682 --- /dev/null +++ b/log4j-systemd-journal/src/test/java/org/xbib/log4j/systemd/SystemdJournalAppenderIntegrationTest.java @@ -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")); + } + +} diff --git a/log4j-systemd-journal/src/test/java/org/xbib/log4j/systemd/SystemdJournalAppenderTest.java b/log4j-systemd-journal/src/test/java/org/xbib/log4j/systemd/SystemdJournalAppenderTest.java new file mode 100644 index 0000000..0efe83c --- /dev/null +++ b/log4j-systemd-journal/src/test/java/org/xbib/log4j/systemd/SystemdJournalAppenderTest.java @@ -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 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 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 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 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); + } +} diff --git a/log4j-systemd-journal/src/test/resources/log4j2-test.xml b/log4j-systemd-journal/src/test/resources/log4j2-test.xml new file mode 100644 index 0000000..d029095 --- /dev/null +++ b/log4j-systemd-journal/src/test/resources/log4j2-test.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..c63affa --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +include 'systemd-journal' +include 'log4j-systemd-journal' diff --git a/systemd-journal/build.gradle b/systemd-journal/build.gradle new file mode 100644 index 0000000..099fd8e --- /dev/null +++ b/systemd-journal/build.gradle @@ -0,0 +1,5 @@ + +dependencies { + implementation "com.nativelibs4java:bridj:${project.property('bridj.version')}" + testImplementation "org.mockito:mockito-junit-jupiter:${project.property('mockito.version')}" +} diff --git a/src/main/java/org/xbib/systemd/DefaultJournalEntry.java b/systemd-journal/src/main/java/org/xbib/systemd/journal/DefaultJournalEntry.java similarity index 99% rename from src/main/java/org/xbib/systemd/DefaultJournalEntry.java rename to systemd-journal/src/main/java/org/xbib/systemd/journal/DefaultJournalEntry.java index 5b8ccc2..6e386ec 100644 --- a/src/main/java/org/xbib/systemd/DefaultJournalEntry.java +++ b/systemd-journal/src/main/java/org/xbib/systemd/journal/DefaultJournalEntry.java @@ -1,4 +1,4 @@ -package org.xbib.systemd; +package org.xbib.systemd.journal; public class DefaultJournalEntry implements JournalEntry { diff --git a/src/main/java/org/xbib/systemd/JournalEntry.java b/systemd-journal/src/main/java/org/xbib/systemd/journal/JournalEntry.java similarity index 97% rename from src/main/java/org/xbib/systemd/JournalEntry.java rename to systemd-journal/src/main/java/org/xbib/systemd/journal/JournalEntry.java index d6fc899..a4940a7 100644 --- a/src/main/java/org/xbib/systemd/JournalEntry.java +++ b/systemd-journal/src/main/java/org/xbib/systemd/journal/JournalEntry.java @@ -1,4 +1,4 @@ -package org.xbib.systemd; +package org.xbib.systemd.journal; public interface JournalEntry { diff --git a/src/main/java/org/xbib/systemd/Syslog.java b/systemd-journal/src/main/java/org/xbib/systemd/journal/Syslog.java similarity index 86% rename from src/main/java/org/xbib/systemd/Syslog.java rename to systemd-journal/src/main/java/org/xbib/systemd/journal/Syslog.java index faf0702..0129be4 100644 --- a/src/main/java/org/xbib/systemd/Syslog.java +++ b/systemd-journal/src/main/java/org/xbib/systemd/journal/Syslog.java @@ -1,4 +1,4 @@ -package org.xbib.systemd; +package org.xbib.systemd.journal; public interface Syslog { int LOG_EMERG = 0; diff --git a/src/main/java/org/xbib/systemd/SystemdJournalConsumer.java b/systemd-journal/src/main/java/org/xbib/systemd/journal/SystemdJournalConsumer.java similarity index 99% rename from src/main/java/org/xbib/systemd/SystemdJournalConsumer.java rename to systemd-journal/src/main/java/org/xbib/systemd/journal/SystemdJournalConsumer.java index 2effbad..e720d31 100644 --- a/src/main/java/org/xbib/systemd/SystemdJournalConsumer.java +++ b/systemd-journal/src/main/java/org/xbib/systemd/journal/SystemdJournalConsumer.java @@ -1,4 +1,4 @@ -package org.xbib.systemd; +package org.xbib.systemd.journal; import org.bridj.Pointer; import org.bridj.SizeT; diff --git a/src/main/java/org/xbib/systemd/SystemdJournalLibrary.java b/systemd-journal/src/main/java/org/xbib/systemd/journal/SystemdJournalLibrary.java similarity index 97% rename from src/main/java/org/xbib/systemd/SystemdJournalLibrary.java rename to systemd-journal/src/main/java/org/xbib/systemd/journal/SystemdJournalLibrary.java index 72122db..a2ce55e 100644 --- a/src/main/java/org/xbib/systemd/SystemdJournalLibrary.java +++ b/systemd-journal/src/main/java/org/xbib/systemd/journal/SystemdJournalLibrary.java @@ -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 sd_id128_to_string(sd_id128 id, Pointer 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 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 file, Pointer line, Pointer func, - Pointer iov, int n) { + Pointer iov, int n) { return sd_journal_sendv_with_location(Pointer.getPeer(file), Pointer.getPeer(line), Pointer.getPeer(func), Pointer.getPeer(iov), n); } diff --git a/src/main/java/org/xbib/systemd/SystemdJournalListener.java b/systemd-journal/src/main/java/org/xbib/systemd/journal/SystemdJournalListener.java similarity index 80% rename from src/main/java/org/xbib/systemd/SystemdJournalListener.java rename to systemd-journal/src/main/java/org/xbib/systemd/journal/SystemdJournalListener.java index 93daa59..e60e14a 100644 --- a/src/main/java/org/xbib/systemd/SystemdJournalListener.java +++ b/systemd-journal/src/main/java/org/xbib/systemd/journal/SystemdJournalListener.java @@ -1,4 +1,4 @@ -package org.xbib.systemd; +package org.xbib.systemd.journal; import java.io.IOException; diff --git a/src/main/java/org/xbib/systemd/sd_id128.java b/systemd-journal/src/main/java/org/xbib/systemd/journal/sd_id128.java similarity index 76% rename from src/main/java/org/xbib/systemd/sd_id128.java rename to systemd-journal/src/main/java/org/xbib/systemd/journal/sd_id128.java index 013f8df..96e5e56 100644 --- a/src/main/java/org/xbib/systemd/sd_id128.java +++ b/systemd-journal/src/main/java/org/xbib/systemd/journal/sd_id128.java @@ -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 bytes() { + public Pointer bytes() { return io.getPointerField(this, 0); } @Array({2}) @Field(1) - public Pointer qwords() { + public Pointer 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 pointer) { super(pointer); } } diff --git a/src/test/java/org/xbib/systemd/SystemdJournalReaderTest.java b/systemd-journal/src/test/java/org/xbib/systemd/journal/SystemdJournalReaderTest.java similarity index 77% rename from src/test/java/org/xbib/systemd/SystemdJournalReaderTest.java rename to systemd-journal/src/test/java/org/xbib/systemd/journal/SystemdJournalReaderTest.java index a5d2fff..f5e0dae 100644 --- a/src/test/java/org/xbib/systemd/SystemdJournalReaderTest.java +++ b/systemd-journal/src/test/java/org/xbib/systemd/journal/SystemdJournalReaderTest.java @@ -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); } }