Initial import

This commit is contained in:
Mark Nellemann 2020-09-22 20:33:22 +02:00
commit dc09a0f3e1
20 changed files with 970 additions and 0 deletions

11
.editorconfig Normal file
View file

@ -0,0 +1,11 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 4
[*.yml]
indent_size = 2

6
.gitattributes vendored Normal file
View file

@ -0,0 +1,6 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# These are explicitly windows files and should use crlf
*.bat text eol=crlf

6
.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
# Ignore Gradle project-specific cache directory
.gradle
.idea
# Ignore Gradle build output directory
build

23
bitbucket-pipelines.yml Normal file
View file

@ -0,0 +1,23 @@
image: adoptopenjdk:8-openj9
#image: openjdk:8
pipelines:
branches:
master:
- step:
caches:
- gradle
name: Build and Test
script:
- ./gradlew clean build
tags: # add the 'tags' section
v*: # specify the tag
- step: # define the build pipeline for the tag
caches:
- gradle
name: Build and Release
script:
- ./gradlew clean build shadowJar startShadowScripts buildRpm buildDeb
- shopt -s nullglob ; for file in ${BITBUCKET_CLONE_DIR}/build/libs/*-all.jar ; do curl -X POST --user "${BB_AUTH_STRING}" "https://api.bitbucket.org/2.0/repositories/${BITBUCKET_REPO_OWNER}/${BITBUCKET_REPO_SLUG}/downloads" --form files=@"${file}" ; done
- shopt -s nullglob ; for file in ${BITBUCKET_CLONE_DIR}/build/distributions/*.rpm ; do curl -X POST --user "${BB_AUTH_STRING}" "https://api.bitbucket.org/2.0/repositories/${BITBUCKET_REPO_OWNER}/${BITBUCKET_REPO_SLUG}/downloads" --form files=@"${file}" ; done
- shopt -s nullglob ; for file in ${BITBUCKET_CLONE_DIR}/build/distributions/*.deb ; do curl -X POST --user "${BB_AUTH_STRING}" "https://api.bitbucket.org/2.0/repositories/${BITBUCKET_REPO_OWNER}/${BITBUCKET_REPO_SLUG}/downloads" --form files=@"${file}" ; done

88
build.gradle Normal file
View file

@ -0,0 +1,88 @@
plugins {
id 'java'
id 'groovy'
id 'application'
id "com.github.johnrengelman.shadow" version "6.0.0"
id "net.nemerosa.versioning" version "2.14.0"
id "nebula.ospackage" version "8.4.1"
}
repositories {
// Use jcenter for resolving dependencies.
// You can declare any Maven/Ivy/file repository here.
jcenter()
}
dependencies {
compile 'info.picocli:picocli:4.5.1'
annotationProcessor 'info.picocli:picocli-codegen:4.5.1'
implementation 'org.slf4j:slf4j-api:1.7.+'
runtimeOnly 'ch.qos.logback:logback-classic:1.+'
// Use the awesome Spock testing and specification framework
testImplementation('org.spockframework:spock-core:2.0-M3-groovy-3.0')
testImplementation("org.slf4j:slf4j-simple:1.7.+")
}
application {
// Define the main class for the application.
mainClassName = 'biz.nellemann.syslogd.SyslogServer'
}
test {
useJUnitPlatform()
}
apply plugin: 'nebula.ospackage'
ospackage {
packageName = 'syslogd'
release = '1'
user = 'root'
packager = "Mark Nellemann <mark.nellemann@gmail.com>"
into '/opt/syslogd'
from(shadowJar.outputs.files) {
into 'lib'
}
from('build/scriptsShadow') {
into 'bin'
}
from(['README.md', 'LICENSE']) {
into 'doc'
}
}
buildRpm {
dependsOn startShadowScripts
requires('java-1.8.0-openjdk-headless')
os = LINUX
}
buildDeb {
dependsOn startShadowScripts
requires('default-jre-headless')
}
processResources.dependsOn.add("versionFile")
versionFile {
// Path to the file to be written
file = new File(project.buildDir, 'resources/main/version.properties')
}
jar {
manifest {
attributes(
'Built-By' : System.properties['user.name'],
'Build-Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ss.SSSZ").toString(),
'Build-Revision' : versioning.info.commit,
'Created-By' : "Gradle ${gradle.gradleVersion}",
'Build-Jdk' : "${System.properties['java.version']} (${System.properties['java.vendor']} ${System.properties['java.vm.version']})",
'Build-OS' : "${System.properties['os.name']} ${System.properties['os.arch']} ${System.properties['os.version']}"
)
}
}

3
gradle.properties Normal file
View file

@ -0,0 +1,3 @@
id = syslogd
group = biz.nellemann.syslogd
version = 1.0.0

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View file

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

185
gradlew vendored Executable file
View file

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
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
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
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
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
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" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
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"
exec "$JAVACMD" "$@"

89
gradlew.bat vendored Executable file
View file

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
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"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
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 %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

10
settings.gradle Normal file
View file

@ -0,0 +1,10 @@
/*
* This file was generated by the Gradle 'init' task.
*
* The settings file is used to specify which projects to include in your build.
*
* Detailed information about configuring a multi-project build in Gradle can be found
* in the user manual at https://docs.gradle.org/6.6.1/userguide/multi_project_builds.html
*/
rootProject.name = 'syslogd'

View file

@ -0,0 +1,18 @@
package biz.nellemann.syslogd;
import java.util.EventObject;
public class LogEvent extends EventObject {
private String message;
public LogEvent(Object source, String message ) {
super( source );
this.message = message;
}
public String getMessage() {
return message;
}
}

View file

@ -0,0 +1,5 @@
package biz.nellemann.syslogd;
public interface LogListener {
public void onLogEvent(LogEvent event);
}

View file

@ -0,0 +1,77 @@
package biz.nellemann.syslogd;
import java.time.Instant;
public class SyslogMessage {
/*
0 kernel messages
1 user-level messages
2 mail system
3 system daemons
4 security/authorization messages
5 messages generated internally by syslogd
6 line printer subsystem
7 network news subsystem
8 UUCP subsystem
9 clock daemon
10 security/authorization messages
11 FTP daemon
12 NTP subsystem
13 log audit
14 log alert
15 clock daemon (note 2)
16 local use 0 (local0)
17 local use 1 (local1)
18 local use 2 (local2)
19 local use 3 (local3)
20 local use 4 (local4)
21 local use 5 (local5)
22 local use 6 (local6)
23 local use 7 (local7)
*/
protected Integer facility;
/*
0 Emergency: system is unusable
1 Alert: action must be taken immediately
2 Critical: critical conditions
3 Error: error conditions
4 Warning: warning conditions
5 Notice: normal but significant condition
6 Informational: informational messages
7 Debug: debug-level messages
*/
Integer severity;
// The VERSION field denotes the version of the syslog protocol specification.
Integer version;
// The TIMESTAMP field is a formalized timestamp derived from [RFC3339].
Instant timestamp;
// The HOSTNAME field identifies the machine that originally sent the syslog message.
String hostname;
// The APP-NAME field SHOULD identify the device or application that originated the message.
String application;
// The PROCID field is often used to provide the process name or process ID associated with a syslog system.
String processId;
// The MSGID SHOULD identify the type of message.
String messageId;
// STRUCTURED-DATA provides a mechanism to express information in a well defined, easily parseable and interpretable data format.
String structuredData;
// The MSG part contains a free-form message that provides information about the event.
String message;
public String toString() {
return String.format("%s %s %s: %s", timestamp.toString(), hostname, application, message);
}
}

View file

@ -0,0 +1,155 @@
package biz.nellemann.syslogd;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAccessor;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/*
https://tools.ietf.org/html/rfc5424
*/
public class SyslogParser {
private final static Logger log = LoggerFactory.getLogger(SyslogParser.class);
// example: <19>Sep 22 16:22:25 xps13 mark: adfdfdf34344545
public static SyslogMessage parseRfc3164(String input) throws NumberFormatException {
Pattern pattern = Pattern.compile("^<(\\d{1,3})>(\\D{3} \\d{2} \\d{2}:\\d{2}:\\d{2})\\s+(\\S+)\\s+(\\S+): (.*)", Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(input);
boolean matchFound = matcher.find();
if(!matchFound) {
log.warn("Match not found");
return null;
}
String pri = matcher.group(1);
String date = matcher.group(2);
String hostname = matcher.group(3);
String application = matcher.group(4);
String message = matcher.group(5);
log.debug("PRI: " + pri);
log.debug("DATE: " + date);
log.debug("HOST: " + hostname);
log.debug("APP: " + application);
log.debug("MSG: " + message);
Integer facility = Integer.parseInt(pri.substring(0, pri.length()-1));
Integer severity = Integer.parseInt(pri.substring(pri.length()-1));
log.debug("facility: " + facility);
log.debug("severity: " + severity);
SyslogMessage syslogMessage = new SyslogMessage();
syslogMessage.facility = facility;
syslogMessage.severity = severity;
syslogMessage.timestamp = parseRfc3164Timestamp(date);
syslogMessage.hostname = hostname;
syslogMessage.application = application;
syslogMessage.message = message;
return syslogMessage;
}
public static SyslogMessage parseRfc5424(String input) throws NumberFormatException {
Pattern pattern = Pattern.compile("^<(\\d{1,3})>(\\d+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\[.*\\])\\s+(\\S+)", Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(input);
boolean matchFound = matcher.find();
if(!matchFound) {
log.warn("Match not found");
return null;
}
String pri = matcher.group(1);
String ver = matcher.group(2);
String date = matcher.group(3);
String host = matcher.group(4);
String app = matcher.group(5);
String procId = matcher.group(6);
String msgId = matcher.group(7);
String data = matcher.group(8);
String msg = matcher.group(9);
log.debug("PRI: " + pri);
log.debug("VER: " + ver);
log.debug("DATE: " + date);
log.debug("HOST: " + host);
log.debug("APP: " + app);
log.debug("PROCID: " + procId);
log.debug("MSGID: " + msgId);
log.debug("DATA: " + data);
log.debug("MSG: " + msg);
Integer facility = Integer.parseInt(pri.substring(0, pri.length()-1));
Integer severity = Integer.parseInt(pri.substring(pri.length()-1));
log.debug("facility: " + facility);
log.debug("severity: " + severity);
SyslogMessage syslogMessage = new SyslogMessage();
syslogMessage.facility = facility;
syslogMessage.severity = severity;
syslogMessage.version = Integer.parseInt(ver);
syslogMessage.timestamp = parseRfc5424Timestamp(date);
syslogMessage.hostname = host;
if(app != null && !app.equals("-"))
syslogMessage.application = app;
if(procId != null && !procId.equals("-"))
syslogMessage.processId = procId;
if(msgId != null && !msgId.equals("-"))
syslogMessage.messageId = msgId;
syslogMessage.structuredData = data;
syslogMessage.message = msg;
return syslogMessage;
}
// The rfc3164 date
static protected Instant parseRfc3164Timestamp(String dateString) {
// We need to add year to parse date correctly
LocalDateTime dt = LocalDateTime.now();
// Date: Mmm dd hh:mm:ss
Instant instant = null;
try {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy MMM dd HH:mm:ss")
.withLocale(Locale.getDefault())
.withZone(ZoneId.systemDefault());
LocalDateTime dateTime = LocalDateTime.parse(dt.getYear() + " " + dateString, dateTimeFormatter);
instant = dateTime.toInstant(ZoneOffset.UTC);
} catch(DateTimeParseException e) {
log.error("parseDate()", e);
}
return instant;
}
// The rfc5424 timestamp
static protected Instant parseRfc5424Timestamp(String dateString) {
Instant instant = null;
try {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
instant = Instant.from(dateTimeFormatter.parse(dateString));
} catch(DateTimeParseException e) {
log.error("parseTimestamp()", e);
}
return instant;
}
}

View file

@ -0,0 +1,77 @@
package biz.nellemann.syslogd;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import java.util.concurrent.Callable;
@Command(name = "syslogd", mixinStandardHelpOptions = true, version = "syslogd 1.0",
description = "Simple syslog server that prints messages to stdout.")
public class SyslogServer implements Callable<Integer>, LogListener {
private final static Logger log = LoggerFactory.getLogger(SyslogServer.class);
@CommandLine.Option(names = {"-p", "--port"}, description = "Listening port, 514 by default.")
private int port = 8080;
@CommandLine.Option(names = "--no-udp", negatable = true, description = "Listen on UDP, true by default.")
boolean udpServer = true;
@CommandLine.Option(names = "--no-tcp", negatable = true, description = "Listen on TCP, true by default.")
boolean tcpServer = true;
@CommandLine.Option(names = "--rfc3164", negatable = true, description = "Parse rfc3164 syslog message, false by default.")
boolean rfc3164 = false;
@CommandLine.Option(names = "--no-rfc5424", negatable = true, description = "Parse rfc5424 syslog message, true by default.")
boolean rfc5424 = true;
public static void main(String... args) {
int exitCode = new CommandLine(new SyslogServer()).execute(args);
System.exit(exitCode);
}
@Override
public Integer call() throws Exception {
if(udpServer) {
UdpServer udpServer = new UdpServer(port);
udpServer.addEventListener(this);
udpServer.start();
}
if(tcpServer) {
TcpServer tcpServer = new TcpServer(port);
tcpServer.addEventListener(this);
tcpServer.start();
}
return 0;
}
@Override
public void onLogEvent(LogEvent event) {
String message = event.getMessage();
SyslogMessage msg = null;
try {
if(rfc5424) {
msg = SyslogParser.parseRfc5424(message);
} else if(rfc3164) {
msg = SyslogParser.parseRfc3164(message);
}
} catch(Exception e) {
log.error("Problem parsing message: ", e);
}
if(msg != null) {
System.out.println(msg);
}
}
}

View file

@ -0,0 +1,96 @@
package biz.nellemann.syslogd;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class TcpServer {
private int port;
private ServerSocket serverSocket;
TcpServer() {
this(514);
}
TcpServer(int port) {
this.port = port;
}
public void start() throws IOException {
serverSocket = new ServerSocket(port);
while (true)
new ClientHandler(serverSocket.accept(), eventListeners).start();
}
public void stop() throws IOException {
serverSocket.close();
}
/**
* Event Listener Configuration
*/
protected List<LogListener> eventListeners = new ArrayList<>();
public synchronized void addEventListener( LogListener l ) {
eventListeners.add( l );
}
public synchronized void removeEventListener( LogListener l ) {
eventListeners.remove( l );
}
private static class ClientHandler extends Thread {
protected List<LogListener> eventListeners;
private Socket clientSocket;
private BufferedReader in;
public ClientHandler(Socket socket, List<LogListener> eventListeners) {
this.clientSocket = socket;
this.eventListeners = eventListeners;
}
public void run() {
try {
in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
String inputLine;
while ((inputLine = in.readLine()) != null) {
sendEvent(inputLine);
}
} catch (IOException e) {
e.printStackTrace();
}
try {
in.close();
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private synchronized void sendEvent(String message) {
LogEvent event = new LogEvent( this, message );
Iterator listeners = eventListeners.iterator();
while( listeners.hasNext() ) {
( (LogListener) listeners.next() ).onLogEvent( event );
}
}
}
}

View file

@ -0,0 +1,65 @@
package biz.nellemann.syslogd;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class UdpServer extends Thread {
protected DatagramSocket socket = null;
protected boolean listen = true;
public UdpServer() throws IOException {
this(514);
}
public UdpServer(int port) throws IOException {
super("SyslogServer");
socket = new DatagramSocket(port);
}
public void run() {
byte[] buf = new byte[10000];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
while (listen) {
try {
socket.receive(packet);
String packetData = new String(packet.getData(), "UTF8");
sendEvent(packetData);
} catch (Exception e) {
e.printStackTrace();
listen = false;
}
}
socket.close();
}
private synchronized void sendEvent(String message) {
LogEvent event = new LogEvent( this, message );
Iterator listeners = eventListeners.iterator();
while( listeners.hasNext() ) {
( (LogListener) listeners.next() ).onLogEvent( event );
}
}
/**
* Event Listener Configuration
*/
protected List<LogListener> eventListeners = new ArrayList<>();
public synchronized void addEventListener( LogListener l ) {
eventListeners.add( l );
}
public synchronized void removeEventListener( LogListener l ) {
eventListeners.remove( l );
}
}

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="biz.nellemann.syslogd" level="INFO"/>
<root level="WARN">
<appender-ref ref="STDOUT" />
</root>
</configuration>

View file

@ -0,0 +1,35 @@
package biz.nellemann.syslogd
import spock.lang.Specification
import java.time.Instant
import java.time.LocalDateTime;
class SyslogParserTest extends Specification {
void "test parseRfc3164Timestamp"() {
setup:
LocalDateTime dt = LocalDateTime.now()
String dateString = "Sep 12 22:50:13"
when:
Instant inst = SyslogParser.parseRfc3164Timestamp(dateString)
then:
inst.toString() == "${dt.getYear()}-09-12T22:50:13Z"
}
void "test parseRfc5424Timestamp"() {
setup:
String dateString = "2020-09-22T20:10:30.925438+02:00"
when:
Instant inst = SyslogParser.parseRfc5424Timestamp(dateString)
then:
inst.toString() == "2020-09-22T18:10:30.925438Z"
inst.toEpochMilli() == 1600798230925l
}
}