starting work on systemd-journal-jna

This commit is contained in:
Jörg Prante 2020-11-15 21:39:26 +01:00
parent dc96f3365d
commit e6eec6d886
24 changed files with 916 additions and 22 deletions

View file

@ -2,7 +2,7 @@ group = org.xbib
name = systemd-journal
version = 2.0.0
gradle.wrapper.version = 6.4.1
gradle.wrapper.version = 6.6.1
bridj.version = 0.7.0
jna.version = 5.5.0
log4j.version = 2.13.3

View file

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.4.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

2
gradlew vendored
View file

@ -130,7 +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

21
gradlew.bat vendored
View file

@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@ -54,7 +54,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@ -64,21 +64,6 @@ echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
@ -86,7 +71,7 @@ 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%
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell

View file

@ -1,2 +1,3 @@
include 'systemd-journal'
include 'log4j-systemd-journal'
include 'systemd-journal-bridj'
include 'systemd-journal-jna'

View file

@ -0,0 +1,4 @@
dependencies {
implementation "net.java.dev.jna:jna:${project.property('jna.version')}"
testImplementation "org.mockito:mockito-junit-jupiter:${project.property('mockito.version')}"
}

View file

@ -0,0 +1,421 @@
package org.xbib.systemd.journal;
public class DefaultJournalEntry implements JournalEntry {
private String message;
private String messageId;
private int priority;
private String codeFile;
private String codeLine;
private String codeFunc;
private String errno;
private String syslogFacility;
private String syslogIdentifier;
private String syslogPid;
private String syslogTimestamp;
private String syslogRaw;
private String pid;
private String uid;
private String gid;
private String comm;
private String exe;
private String cmdLine;
private String capEffective;
private String auditSession;
private String auditLoginUid;
private String systemdCgroup;
private String systemdSlice;
private String systemdUnit;
private String systemdUserSlice;
private String systemdUserUnit;
private String systemdSession;
private String systemdOwnerUid;
private String selinuxContext;
private String sourceRealtimeTimestamp;
private String bootId;
private String machineId;
private String systemdInvocationId;
private String hostname;
private String transport;
private String streamId;
private String lineBreak;
public void setMessage(String message) {
this.message = message;
}
@Override
public String getMessage() {
return message;
}
public void setMessageId(String messageId) {
this.messageId = messageId;
}
@Override
public String getMessageId() {
return messageId;
}
public void setPriority(int priority) {
this.priority = priority;
}
@Override
public int getPriority() {
return priority;
}
public void setCodeFile(String codeFile) {
this.codeFile = codeFile;
}
@Override
public String getCodeFile() {
return codeFile;
}
public void setCodeLine(String codeLine) {
this.codeLine = codeLine;
}
@Override
public String getCodeLine() {
return codeLine;
}
public void setCodeFunc(String codeFunc) {
this.codeFunc = codeFunc;
}
@Override
public String getCodeFunc() {
return codeFunc;
}
public void setErrno(String errno) {
this.errno = errno;
}
@Override
public String getErrno() {
return errno;
}
public void setSyslogFacility(String syslogFacility) {
this.syslogFacility = syslogFacility;
}
@Override
public String getSyslogFacility() {
return syslogFacility;
}
public void setSyslogIdentifier(String syslogIdentifier) {
this.syslogIdentifier = syslogIdentifier;
}
@Override
public String getSyslogIdentifier() {
return syslogIdentifier;
}
public void setSyslogPid(String syslogPid) {
this.syslogPid = syslogPid;
}
@Override
public String getSyslogPid() {
return syslogPid;
}
public void setSyslogTimestamp(String syslogTimestamp) {
this.syslogTimestamp = syslogTimestamp;
}
@Override
public String getSyslogTimestamp() {
return syslogTimestamp;
}
public void setSyslogRaw(String syslogRaw) {
this.syslogRaw = syslogRaw;
}
@Override
public String getSyslogRaw() {
return syslogRaw;
}
public void setPid(String pid) {
this.pid = pid;
}
@Override
public String getPid() {
return pid;
}
public void setUid(String uid) {
this.uid = uid;
}
@Override
public String getUid() {
return uid;
}
public void setGid(String gid) {
this.gid = gid;
}
@Override
public String getGid() {
return gid;
}
public void setComm(String comm) {
this.comm = comm;
}
@Override
public String getComm() {
return comm;
}
public void setExe(String exe) {
this.exe = exe;
}
@Override
public String getExe() {
return exe;
}
public void setCmdLine(String cmdline) {
this.cmdLine = cmdline;
}
@Override
public String getCmdLine() {
return cmdLine;
}
public void setCapEffective(String capEffective) {
this.capEffective = capEffective;
}
@Override
public String getCapEffective() {
return capEffective;
}
public void setAuditSession(String auditSession) {
this.auditSession = auditSession;
}
@Override
public String getAuditSession() {
return auditSession;
}
public void setAuditLoginUid(String auditLoginUid) {
this.auditLoginUid = auditLoginUid;
}
@Override
public String getAuditLoginUid() {
return auditLoginUid;
}
public void setSystemdCgroup(String systemdCgroup) {
this.systemdCgroup = systemdCgroup;
}
@Override
public String getSystemdCgroup() {
return systemdCgroup;
}
public void setSystemdSlice(String systemdSlice) {
this.systemdSlice = systemdSlice;
}
@Override
public String getSystemdSlice() {
return systemdSlice;
}
public void setSystemdUnit(String systemdUnit) {
this.systemdUnit = systemdUnit;
}
@Override
public String getSystemdUnit() {
return systemdUnit;
}
public void setSystemdUserSlice(String systemdUserSlice) {
this.systemdUserSlice = systemdUserSlice;
}
@Override
public String getSystemdUserSlice() {
return systemdUserSlice;
}
public void setSystemdUserUnit(String systemdUserUnit) {
this.systemdUserUnit = systemdUserUnit;
}
@Override
public String getSystemdUserUnit() {
return systemdUserUnit;
}
public void setSystemdSession(String systemdSession) {
this.systemdSession = systemdSession;
}
@Override
public String getSystemdSession() {
return systemdSession;
}
public void setSystemdOwnerUid(String systemdOwnerUid) {
this.systemdOwnerUid = systemdOwnerUid;
}
@Override
public String getSystemdOwnerUid() {
return systemdOwnerUid;
}
public void setSelinuxContext(String selinuxContext) {
this.selinuxContext = selinuxContext;
}
@Override
public String getSelinuxContext() {
return selinuxContext;
}
public void setSourceRealtimeTimestamp(String sourceRealtimeTimestamp) {
this.sourceRealtimeTimestamp = sourceRealtimeTimestamp;
}
@Override
public String getSourceRealtimeTimestamp() {
return sourceRealtimeTimestamp;
}
public void setBootId(String bootId) {
this.bootId = bootId;
}
@Override
public String getBootId() {
return bootId;
}
public void setMachineId(String machineId) {
this.machineId = machineId;
}
@Override
public String getMachineId() {
return machineId;
}
public void setSystemdInvocationId(String systemdInvocationId) {
this.systemdInvocationId = systemdInvocationId;
}
@Override
public String getSystemdInvocationId() {
return systemdInvocationId;
}
public void setHostname(String hostname) {
this.hostname = hostname;
}
@Override
public String getHostname() {
return hostname;
}
public void setTransport(String transport) {
this.transport = transport;
}
@Override
public String getTransport() {
return transport;
}
public void setStreamId(String streamId) {
this.streamId = streamId;
}
@Override
public String getStreamId() {
return streamId;
}
public void setLineBreak(String lineBreak) {
this.lineBreak = lineBreak;
}
@Override
public String getLineBreak() {
return lineBreak;
}
@Override
public String getFieldValue(String fieldName) {
return null;
}
@Override
public String toString() {
return "priority=" + this.priority + ",message=" + this.message;
}
}

View file

@ -0,0 +1,80 @@
package org.xbib.systemd.journal;
public interface JournalEntry {
String getMessage();
String getMessageId();
int getPriority();
String getCodeFile();
String getCodeLine();
String getCodeFunc();
String getErrno();
String getSyslogFacility();
String getSyslogIdentifier();
String getSyslogPid();
String getSyslogTimestamp();
String getSyslogRaw();
String getPid();
String getUid();
String getGid();
String getComm();
String getExe();
String getCmdLine();
String getCapEffective();
String getAuditSession();
String getAuditLoginUid();
String getSystemdCgroup();
String getSystemdSlice();
String getSystemdUnit();
String getSystemdUserSlice();
String getSystemdUserUnit();
String getSystemdSession();
String getSystemdOwnerUid();
String getSelinuxContext();
String getSourceRealtimeTimestamp();
String getBootId();
String getMachineId();
String getSystemdInvocationId();
String getHostname();
String getTransport();
String getStreamId();
String getLineBreak();
String getFieldValue(String fieldName);
}

View file

@ -0,0 +1,6 @@
package org.xbib.systemd.journal;
import com.sun.jna.Structure;
public class SdJournal extends Structure {
}

View file

@ -0,0 +1,218 @@
package org.xbib.systemd.journal;
import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.StringArray;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import static org.xbib.systemd.journal.SystemdLibraryAPI.SD_JOURNAL_LOCAL_ONLY;
public class SystemdJournalConsumer implements Runnable {
private static final Logger logger = Logger.getLogger(SystemdJournalConsumer.class.getName());
private final String match;
private final String field;
private final SystemdJournalListener listener;
public SystemdJournalConsumer(String match, SystemdJournalListener listener) {
this(match, null, listener);
}
public SystemdJournalConsumer(String match, String field, SystemdJournalListener listener) {
this.match = match;
this.field = field;
this.listener = listener;
}
@Override
public void run() {
try {
loop();
} catch (Exception e) {
logger.log(Level.SEVERE, e.getMessage(), e);
}
}
private void loop() throws IOException {
SystemdLibraryAPI api = SystemdLibraryAPI.getInstance();
SdJournal sdJournal = new SdJournal() {
@Override
public int hashCode() {
return super.hashCode();
}
};
int rc = api.sd_journal_open(sdJournal, SD_JOURNAL_LOCAL_ONLY);
if (rc < 0) {
logger.log(Level.WARNING, "error opening journal for read: " + rc);
}
if (match != null) {
rc = api.sd_journal_add_match(sdJournal, match, match.length());
if (rc < 0) {
logger.log(Level.WARNING, "error in add_match: " + rc);
}
}
rc = api.sd_journal_get_fd(sdJournal);
rc = api.sd_journal_seek_tail(sdJournal);
rc = api.sd_journal_previous(sdJournal);
rc = api.sd_journal_next(sdJournal);
String[] strings = new String[1];
StringArray cursor = new StringArray(strings);
api.sd_journal_get_cursor(sdJournal, cursor);
while (true) {
do {
rc = api.sd_journal_wait(sdJournal, -1);
} while (rc == 0); // NOP
while (api.sd_journal_next(sdJournal) > 0) {
if (field != null) {
Pointer dataPointer = new Memory(Native.POINTER_SIZE);
Pointer sizePointer = new Memory(Native.POINTER_SIZE);
rc = api.sd_journal_get_data(sdJournal, field, dataPointer, sizePointer);
if (rc < 0) {
throw new IOException("error in get_data: " + rc);
}
int size = sizePointer.getInt(0);
byte[] b = dataPointer.getByteArray(0, size);
String s = new String(b, StandardCharsets.UTF_8);
if (listener != null) {
listener.handleEntry(makeEntry(Collections.singletonList(s)));
}
} else {
String[] strings2 = new String[1];
StringArray nextCursor = new StringArray(strings2);
api.sd_journal_get_cursor(sdJournal, nextCursor);
if (!cursor.getString(0).equals(nextCursor.getString(0))) {
cursor = nextCursor;
Pointer dataPointer = new Memory(Native.POINTER_SIZE);
Pointer sizePointer = new Memory(Native.POINTER_SIZE);
List<String> list = new ArrayList<>();
while (api.sd_journal_enumerate_data(sdJournal, dataPointer, sizePointer) > 0) {
//Pointer<Byte> data = dataPointer.as(Byte.class);
//String line = data.getPointer(Byte.class).getCString();
int size = sizePointer.getInt(0);
byte[] b = dataPointer.getByteArray(0, size);
String s = new String(b, StandardCharsets.UTF_8);
list.add(s);
}
rc = api.sd_journal_restart_data(sdJournal);
if (listener != null) {
listener.handleEntry(makeEntry(list));
}
}
}
}
}
}
private JournalEntry makeEntry(List<String> list) {
DefaultJournalEntry journalEntry = new DefaultJournalEntry();
for (String string : list) {
if (string.startsWith("MESSAGE=")) {
journalEntry.setMessage(string.substring(8));
continue;
}
if (string.startsWith("MESSAGE_ID=")) {
journalEntry.setMessageId(string.substring(11));
continue;
}
if (string.startsWith("PRIORITY=")) {
journalEntry.setPriority(Integer.parseInt(string.substring(9)));
continue;
}
if (string.startsWith("CODE_FILE=")) {
journalEntry.setCodeFile(string.substring(10));
continue;
}
if (string.startsWith("CODE_LINE=")) {
journalEntry.setCodeLine(string.substring(10));
continue;
}
if (string.startsWith("CODE_FUNC=")) {
journalEntry.setCodeFunc(string.substring(10));
continue;
}
if (string.startsWith("ERRNO=")) {
journalEntry.setErrno(string.substring(6));
continue;
}
if (string.startsWith("SYSLOG_FACILITY=")) {
journalEntry.setSyslogFacility(string.substring(16));
continue;
}
if (string.startsWith("SYSLOG_IDENTIFIER=")) {
journalEntry.setSyslogIdentifier(string.substring(18));
continue;
}
if (string.startsWith("SYSLOG_PID=")) {
journalEntry.setSyslogPid(string.substring(11));
continue;
}
if (string.startsWith("SYSLOG_TIMESTAMP=")) {
journalEntry.setSyslogTimestamp(string.substring(17));
continue;
}
if (string.startsWith("SYSLOG_RAW=")) {
journalEntry.setSyslogRaw(string.substring(11));
continue;
}
if (string.startsWith("_PID=")) {
journalEntry.setPid(string.substring(5));
continue;
}
if (string.startsWith("_UID=")) {
journalEntry.setUid(string.substring(5));
continue;
}
if (string.startsWith("_GID=")) {
journalEntry.setGid(string.substring(5));
continue;
}
if (string.startsWith("_COMM=")) {
journalEntry.setComm(string.substring(6));
continue;
}
if (string.startsWith("_EXE=")) {
journalEntry.setExe(string.substring(5));
continue;
}
if (string.startsWith("_CMDLINE=")) {
journalEntry.setCmdLine(string.substring(9));
continue;
}
if (string.startsWith("_CAP_EFFECTIVE=")) {
journalEntry.setCapEffective(string.substring(15));
continue;
}
if (string.startsWith("_TRANSPORT=")) {
journalEntry.setTransport(string.substring(11));
continue;
}
if (string.startsWith("_SYSTEMD_OWNER_UID=")) {
journalEntry.setSystemdOwnerUid(string.substring(19));
continue;
}
if (string.startsWith("_SYSTEMD_UNIT=")) {
journalEntry.setSystemdUnit(string.substring(13));
continue;
}
if (string.startsWith("_SYSTEMD_USER_SLICE=")) {
journalEntry.setSystemdUserSlice(string.substring(19));
continue;
}
if (string.startsWith("_SYSTEMD_USER_UNIT=")) {
journalEntry.setSystemdUserUnit(string.substring(18));
}
}
return journalEntry;
}
}

View file

@ -0,0 +1,8 @@
package org.xbib.systemd.journal;
import java.io.IOException;
public interface SystemdJournalListener {
void handleEntry(JournalEntry entry) throws IOException;
}

View file

@ -0,0 +1,32 @@
package org.xbib.systemd.journal;
import com.sun.jna.Library;
import com.sun.jna.Pointer;
import com.sun.jna.StringArray;
public interface SystemdLibrary extends Library {
int sd_journal_send(String format, Object... args);
int sd_journal_open(SdJournal sdJournal, int flag);
int sd_journal_get_fd(SdJournal sdJournal);
int sd_journal_seek_tail(SdJournal sdJournal);
int sd_journal_previous(SdJournal sdJournal);
int sd_journal_next(SdJournal sdJournal);
int sd_journal_get_cursor(SdJournal sdJournal, StringArray cursor);
int sd_journal_add_match(SdJournal sdJournal, String match, int len);
int sd_journal_wait(SdJournal sdJournal, long timeout);
int sd_journal_get_data(SdJournal sdJournal, String field, Pointer data, Pointer length);
int sd_journal_enumerate_data(SdJournal sdJournal, Pointer data, Pointer length);
int sd_journal_restart_data(SdJournal sdJournal);
}

View file

@ -0,0 +1,105 @@
package org.xbib.systemd.journal;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.StringArray;
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 static final int SD_JOURNAL_LOCAL_ONLY = 1;
public static final int SD_JOURNAL_RUNTIME_ONLY = 2;
public static final int SD_JOURNAL_SYSTEM = 4;
public static final int SD_JOURNAL_CURRENT_USER = 8;
public static final int SD_JOURNAL_NOP = 0;
public static final int SD_JOURNAL_APPEND = 1;
public static final int SD_JOURNAL_INVALIDATE = 2;
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 int sd_journal_send(String format, Object... args) {
return systemdLibrary.sd_journal_send(format, args);
}
public int sd_journal_send(String format, List<Object> args) {
return systemdLibrary.sd_journal_send(format, args.toArray());
}
public int sd_journal_open(SdJournal sdJournal, int flag) {
return systemdLibrary.sd_journal_open(sdJournal, flag);
}
public int sd_journal_get_fd(SdJournal sdJournal) {
return systemdLibrary.sd_journal_get_fd(sdJournal);
}
public int sd_journal_seek_tail(SdJournal sdJournal) {
return systemdLibrary.sd_journal_seek_tail(sdJournal);
}
public int sd_journal_previous(SdJournal sdJournal) {
return systemdLibrary.sd_journal_previous(sdJournal);
}
public int sd_journal_next(SdJournal sdJournal) {
return systemdLibrary.sd_journal_next(sdJournal);
}
public int sd_journal_get_cursor(SdJournal sdJournal, StringArray cursor) {
return systemdLibrary.sd_journal_get_cursor(sdJournal, cursor);
}
public int sd_journal_add_match(SdJournal sdJournal, String match, int len) {
return systemdLibrary.sd_journal_add_match(sdJournal, match, len);
}
public int sd_journal_wait(SdJournal sdJournal, long timeout_musec) {
return systemdLibrary.sd_journal_wait(sdJournal, timeout_musec);
}
public int sd_journal_get_data(SdJournal sdJournal, String field, Pointer data, Pointer length) {
return systemdLibrary.sd_journal_get_data(sdJournal, field, data, length);
}
public int sd_journal_enumerate_data(SdJournal sdJournal, Pointer data, Pointer length) {
return systemdLibrary.sd_journal_enumerate_data(sdJournal, data, length);
}
public int sd_journal_restart_data(SdJournal sdJournal) {
return systemdLibrary.sd_journal_restart_data(sdJournal);
}
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);
}
}
}

View file

@ -0,0 +1,34 @@
package org.xbib.systemd.journal;
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 java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
@EnabledOnOs({OS.LINUX})
@ExtendWith(MockitoExtension.class)
public class SystemdJournalReaderTest {
private static final Logger logger = Logger.getLogger(SystemdJournalReaderTest.class.getName());
@Mock
private SystemdLibraryAPI api;
@Test
void testConsumer() throws InterruptedException {
SystemdJournalConsumer consumer = new SystemdJournalConsumer(null,
entry -> logger.log(Level.INFO, entry.toString()));
Executors.newSingleThreadExecutor().submit(consumer);
// consuming for some seconds
Thread.sleep(10000L);
}
}