diff --git a/gradle.properties b/gradle.properties index bdcfd3b..e8f0478 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ id = syslogd group = biz.nellemann.syslogd -version = 1.0.9 +version = 1.0.10 diff --git a/src/main/java/biz/nellemann/syslogd/Application.java b/src/main/java/biz/nellemann/syslogd/Application.java index d5f38fd..c806e74 100644 --- a/src/main/java/biz/nellemann/syslogd/Application.java +++ b/src/main/java/biz/nellemann/syslogd/Application.java @@ -15,6 +15,13 @@ */ package biz.nellemann.syslogd; +import biz.nellemann.syslogd.msg.SyslogMessage; +import biz.nellemann.syslogd.net.TcpServer; +import biz.nellemann.syslogd.net.UdpClient; +import biz.nellemann.syslogd.net.UdpServer; +import biz.nellemann.syslogd.parser.SyslogParser; +import biz.nellemann.syslogd.parser.SyslogParserRfc3164; +import biz.nellemann.syslogd.parser.SyslogParserRfc5424; import org.slf4j.impl.SimpleLogger; import picocli.CommandLine; @@ -32,6 +39,7 @@ import java.util.regex.Pattern; public class Application implements Callable, LogListener { private boolean doForward = false; + private SyslogParser syslogParser; private UdpClient udpClient; @@ -67,6 +75,12 @@ public class Application implements Callable, LogListener { System.setProperty(SimpleLogger.DEFAULT_LOG_LEVEL_KEY, "DEBUG"); } + if(rfc5424) { + syslogParser = new SyslogParserRfc5424(); + } else { + syslogParser = new SyslogParserRfc3164(); + } + if(forward != null && !forward.isEmpty()) { String fHost, fPort; Pattern pattern = Pattern.compile("^([^:]+)(?::([0-9]+))?$", Pattern.CASE_INSENSITIVE); @@ -110,11 +124,7 @@ public class Application implements Callable, LogListener { String message = event.getMessage(); SyslogMessage msg = null; try { - if(rfc5424) { - msg = SyslogParser.parseRfc5424(message); - } else { - msg = SyslogParser.parseRfc3164(message); - } + msg = syslogParser.parse(message); } catch(Exception e) { e.printStackTrace(); } diff --git a/src/main/java/biz/nellemann/syslogd/LogListener.java b/src/main/java/biz/nellemann/syslogd/LogListener.java index a42b529..380bd8d 100644 --- a/src/main/java/biz/nellemann/syslogd/LogListener.java +++ b/src/main/java/biz/nellemann/syslogd/LogListener.java @@ -16,5 +16,5 @@ package biz.nellemann.syslogd; public interface LogListener { - void onLogEvent(LogEvent event); + public void onLogEvent(LogEvent event); } diff --git a/src/main/java/biz/nellemann/syslogd/SyslogParser.java b/src/main/java/biz/nellemann/syslogd/SyslogParser.java deleted file mode 100644 index 52271a8..0000000 --- a/src/main/java/biz/nellemann/syslogd/SyslogParser.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - Copyright 2020 mark.nellemann@gmail.com - - 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 - - http://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. - */ -package biz.nellemann.syslogd; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.time.*; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeParseException; -import java.util.Arrays; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class SyslogParser { - - private final static Logger log = LoggerFactory.getLogger(SyslogParser.class); - - - /** - * Parses [rfc3164](https://tools.ietf.org/html/rfc3164) syslog messages. - * - * @param input - * @return - * @throws NumberFormatException - */ - public static SyslogMessage parseRfc3164(final String input) throws NumberFormatException { - - Pattern pattern = Pattern.compile("^<(\\d{1,3})>(\\D{3}\\s+\\d{1,2} \\d{2}:\\d{2}:\\d{2})\\s+(Message forwarded from \\S+:|\\S+)\\s+([^\\s:]+):?\\s+(.*)", Pattern.CASE_INSENSITIVE); - Matcher matcher = pattern.matcher(input); - boolean matchFound = matcher.find(); - if(!matchFound) { - //log.warn("parseRfc3164() - Match not found in: "); - System.err.println("!" + input); - return null; - } - - String pri = matcher.group(1); - String date = matcher.group(2); - String hostname = matcher.group(3); - String application = matcher.group(4); - String msg = matcher.group(5); - - if(hostname.endsWith(":")) { - String[] tmp = hostname.split(" "); - hostname = tmp[tmp.length-1]; - hostname = hostname.substring(0, hostname.length()-1); - } - - Integer facility = getFacility(pri); - Integer severity = getSeverity(pri); - - SyslogMessage syslogMessage = new SyslogMessage(msg.trim()); - syslogMessage.facility = Facility.getByNumber(facility); - syslogMessage.severity = Severity.getByNumber(severity); - syslogMessage.timestamp = parseRfc3164Timestamp(date); - syslogMessage.hostname = hostname; - syslogMessage.application = application; - - return syslogMessage; - } - - - /** - * Parses [rfc5424](https://tools.ietf.org/html/rfc5424) syslog messages. - * - * @param input - * @return - * @throws NumberFormatException - */ - public static SyslogMessage parseRfc5424(final String input) throws NumberFormatException { - - log.warn("parseRfc5424() " + input); - - // "<165>1 2003-08-24T05:14:15.000003-07:00 192.0.2.1 myproc 8710 - - %% It's time to make the do-nuts." - // '<13>1 2020-09-23T08:57:30.950699+02:00 xps13 mark - - [exampleSDID@32473 iut="3" eventSource="Application" eventID="1011"] adfdfdf3432434565656' - 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.debug("parseRfc5424() - Match not found in: " + input); - System.err.println("!" + input); - 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); - - Integer facility = getFacility(pri); - Integer severity = getSeverity(pri); - - SyslogMessage syslogMessage = new SyslogMessage(msg.trim()); - syslogMessage.facility = Facility.getByNumber(facility); - syslogMessage.severity = Severity.getByNumber(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; - if(data != null && !data.equals("-")) - syslogMessage.structuredData = data; - - return syslogMessage; - } - - - /** - * Parse rfc3164 TIMESTAMP field into Instant. - * - * @param dateString - * @return - */ - static protected Instant parseRfc3164Timestamp(String dateString) { - - // We need to add year to parse date correctly - OffsetDateTime odt = OffsetDateTime.now(); - - // Date: Mmm dd hh:mm:ss - Instant instant = null; - try { - DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy MMM [ ]d HH:mm:ss").withZone(ZoneOffset.UTC); - instant = Instant.from(dateTimeFormatter.parse(odt.getYear() + " " + dateString)); - } catch(DateTimeParseException e) { - log.error("parseDate()", e); - } - - return instant; - } - - - /** - * Parse rfc5424 TIMESTAMP field into Instant. - * - * @param dateString - * @return - */ - static protected Instant parseRfc5424Timestamp(String dateString) { - - /* - https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html - - ex1: 1985-04-12T23:20:50.52Z - ex2: 1985-04-12T19:20:50.52-04:00 - ex3: 2003-10-11T22:14:15.003Z - ex4: 2003-08-24T05:14:15.000003-07:00 - ex5: 2003-08-24T05:14:15.000000003-07:00 - */ - - List formatStrings = Arrays.asList( - "yyyy-MM-dd'T'HH:mm:ss.SS'Z'", - "yyyy-MM-dd'T'HH:mm:ss.SSXXX", - "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", - "yyyy-MM-dd'T'HH:mm:ss.SSSSSSXXX", - "yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSSXXX" - ); - - for(String formatString : formatStrings) - { - try { - return new SimpleDateFormat(formatString).parse(dateString).toInstant(); - } - catch (ParseException e) {} - } - /* - try { - DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME; - instant = Instant.from(dateTimeFormatter.parse(dateString)); - } catch(DateTimeParseException e) { - log.error("parseTimestamp()", e); - } - return instant;*/ - return null; - } - - - /** - * Converts syslog PRI field into Facility. - * - * @param pri - * @return - */ - static protected int getFacility(String pri) { - - int priority = Integer.parseInt(pri); - int facility = priority >> 3; - - //log.debug("getFacility() - " + pri + " => " + facility); - return facility; - } - - - /** - * Converts syslog PRI field into Severity. - * - * @param pri - * @return - */ - static protected int getSeverity(String pri) { - - int priority = Integer.parseInt(pri); - int severity = priority & 0x07; - - //log.debug("getSeverity() - " + pri + " => " + severity); - return severity; - } - - -} diff --git a/src/main/java/biz/nellemann/syslogd/SyslogPrinter.java b/src/main/java/biz/nellemann/syslogd/SyslogPrinter.java index da6be63..8f3dff7 100644 --- a/src/main/java/biz/nellemann/syslogd/SyslogPrinter.java +++ b/src/main/java/biz/nellemann/syslogd/SyslogPrinter.java @@ -1,5 +1,8 @@ package biz.nellemann.syslogd; +import biz.nellemann.syslogd.msg.Facility; +import biz.nellemann.syslogd.msg.Severity; +import biz.nellemann.syslogd.msg.SyslogMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -7,9 +10,10 @@ public class SyslogPrinter { private final static Logger log = LoggerFactory.getLogger(SyslogPrinter.class); + private final static char SPACE = ' '; + public static String toString(SyslogMessage msg) { - StringBuilder sb = new StringBuilder(); - sb.append(msg.timestamp.toString()); + StringBuilder sb = new StringBuilder(msg.timestamp.toString()); sb.append(String.format(" [%8.8s.%-6.6s] ", msg.facility, msg.severity)); sb.append(String.format(" %-16.16s ", msg.hostname)); sb.append(String.format(" %-32.32s ", msg.application)); @@ -19,9 +23,7 @@ public class SyslogPrinter { public static String toAnsiString(SyslogMessage msg) { - StringBuilder sb = new StringBuilder(); - - sb.append(msg.timestamp.toString()); + StringBuilder sb = new StringBuilder(msg.timestamp.toString()); if (msg.severity.toNumber() < 3) { sb.append(Ansi.RED); @@ -45,10 +47,9 @@ public class SyslogPrinter { StringBuilder sb = new StringBuilder(); sb.append(getPri(msg.facility, msg.severity)); sb.append(new java.text.SimpleDateFormat("MMM dd HH:mm:ss").format(new java.util.Date(msg.timestamp.toEpochMilli()))); - sb.append(" " + msg.hostname); - sb.append(" " + msg.application); - sb.append(": " + msg.message); - + sb.append(SPACE).append(msg.hostname); + sb.append(SPACE).append(msg.application); + sb.append(":").append(SPACE).append(msg.message); log.debug(sb.toString()); return sb.toString(); } @@ -59,21 +60,21 @@ public class SyslogPrinter { public static String toRfc5424(SyslogMessage msg) { StringBuilder sb = new StringBuilder(); sb.append(getPri(msg.facility, msg.severity)).append("1"); - sb.append(" " + new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").format(new java.util.Date(msg.timestamp.toEpochMilli()))); - sb.append(" " + msg.hostname); - sb.append(" " + msg.application); - sb.append(" " + msg.processId); - sb.append(" " + msg.messageId); - sb.append(" " + msg.structuredData); - sb.append(" " + msg.message); + sb.append(SPACE).append(new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").format(new java.util.Date(msg.timestamp.toEpochMilli()))); + sb.append(SPACE).append(msg.hostname); + sb.append(SPACE).append(msg.application); + sb.append(SPACE).append(msg.processId); + sb.append(SPACE).append(msg.messageId); + sb.append(SPACE).append(msg.structuredData); + sb.append(SPACE).append(msg.message); log.debug(sb.toString()); return sb.toString(); } static private String getPri(Facility facility, Severity severity) { - int prival = (facility.toNumber() * 8) + severity.toNumber(); - return String.format("<%d>", prival); + int pri = (facility.toNumber() * 8) + severity.toNumber(); + return String.format("%c%d%c", '<', pri, '>'); } } diff --git a/src/main/java/biz/nellemann/syslogd/UdpClient.java b/src/main/java/biz/nellemann/syslogd/UdpClient.java deleted file mode 100644 index 296bc47..0000000 --- a/src/main/java/biz/nellemann/syslogd/UdpClient.java +++ /dev/null @@ -1,30 +0,0 @@ -package biz.nellemann.syslogd; - -import java.io.IOException; -import java.net.*; - -public class UdpClient { - - private DatagramSocket socket; - private InetAddress address; - private Integer port; - - private byte[] buf; - - public UdpClient(String host, Integer port) throws UnknownHostException, SocketException { - socket = new DatagramSocket(); - address = InetAddress.getByName(host); - this.port = port; - } - - public void send(String msg) throws IOException { - buf = msg.getBytes(); - DatagramPacket packet - = new DatagramPacket(buf, buf.length, address, port); - socket.send(packet); - } - - public void close() { - socket.close(); - } -} diff --git a/src/main/java/biz/nellemann/syslogd/Facility.java b/src/main/java/biz/nellemann/syslogd/msg/Facility.java similarity index 98% rename from src/main/java/biz/nellemann/syslogd/Facility.java rename to src/main/java/biz/nellemann/syslogd/msg/Facility.java index 001a7c4..79b675b 100644 --- a/src/main/java/biz/nellemann/syslogd/Facility.java +++ b/src/main/java/biz/nellemann/syslogd/msg/Facility.java @@ -1,4 +1,4 @@ -package biz.nellemann.syslogd; +package biz.nellemann.syslogd.msg; import java.util.HashMap; import java.util.Map; diff --git a/src/main/java/biz/nellemann/syslogd/Severity.java b/src/main/java/biz/nellemann/syslogd/msg/Severity.java similarity index 96% rename from src/main/java/biz/nellemann/syslogd/Severity.java rename to src/main/java/biz/nellemann/syslogd/msg/Severity.java index 4364afb..3f8a1df 100644 --- a/src/main/java/biz/nellemann/syslogd/Severity.java +++ b/src/main/java/biz/nellemann/syslogd/msg/Severity.java @@ -1,4 +1,4 @@ -package biz.nellemann.syslogd; +package biz.nellemann.syslogd.msg; import java.util.HashMap; import java.util.Map; diff --git a/src/main/java/biz/nellemann/syslogd/SyslogMessage.java b/src/main/java/biz/nellemann/syslogd/msg/SyslogMessage.java similarity index 77% rename from src/main/java/biz/nellemann/syslogd/SyslogMessage.java rename to src/main/java/biz/nellemann/syslogd/msg/SyslogMessage.java index 5dfa80a..7326f64 100644 --- a/src/main/java/biz/nellemann/syslogd/SyslogMessage.java +++ b/src/main/java/biz/nellemann/syslogd/msg/SyslogMessage.java @@ -13,40 +13,40 @@ See the License for the specific language governing permissions and limitations under the License. */ -package biz.nellemann.syslogd; +package biz.nellemann.syslogd.msg; import java.time.Instant; public class SyslogMessage { - protected Facility facility; - protected Severity severity; + public Facility facility; + public Severity severity; // The VERSION field denotes the version of the syslog protocol specification. - protected Integer version; + public Integer version; // The TIMESTAMP field is a formalized timestamp derived from [RFC3339]. - protected Instant timestamp; + public Instant timestamp; // The HOSTNAME field identifies the machine that originally sent the syslog message. - protected String hostname; + public String hostname; // The APP-NAME field SHOULD identify the device or application that originated the message. - protected String application = "-"; + public String application = "-"; // The PROCID field is often used to provide the process name or process ID associated with a syslog system. - protected String processId = "-"; + public String processId = "-"; // The MSGID SHOULD identify the type of message. - protected String messageId = "-"; + public String messageId = "-"; // STRUCTURED-DATA provides a mechanism to express information in a well defined, easily parseable and interpretable data format. - protected String structuredData = "-"; + public String structuredData = "-"; // The MSG part contains a free-form message that provides information about the event. - protected final String message; + public final String message; - SyslogMessage(final String message) { + public SyslogMessage(final String message) { this.message = message; } diff --git a/src/main/java/biz/nellemann/syslogd/TcpServer.java b/src/main/java/biz/nellemann/syslogd/net/TcpServer.java similarity index 94% rename from src/main/java/biz/nellemann/syslogd/TcpServer.java rename to src/main/java/biz/nellemann/syslogd/net/TcpServer.java index 65f5cf0..ed66153 100644 --- a/src/main/java/biz/nellemann/syslogd/TcpServer.java +++ b/src/main/java/biz/nellemann/syslogd/net/TcpServer.java @@ -13,26 +13,29 @@ See the License for the specific language governing permissions and limitations under the License. */ -package biz.nellemann.syslogd; +package biz.nellemann.syslogd.net; + +import biz.nellemann.syslogd.LogEvent; +import biz.nellemann.syslogd.LogListener; 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.List; +import java.util.ArrayList; public class TcpServer { private final int port; private ServerSocket serverSocket; - TcpServer() { + public TcpServer() { this(514); } - TcpServer(int port) { + public TcpServer(int port) { this.port = port; } diff --git a/src/main/java/biz/nellemann/syslogd/net/UdpClient.java b/src/main/java/biz/nellemann/syslogd/net/UdpClient.java new file mode 100644 index 0000000..9b57cf2 --- /dev/null +++ b/src/main/java/biz/nellemann/syslogd/net/UdpClient.java @@ -0,0 +1,50 @@ +package biz.nellemann.syslogd.net; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.*; +import java.nio.charset.StandardCharsets; + +public class UdpClient { + + private final static Logger log = LoggerFactory.getLogger(UdpClient.class); + + private DatagramSocket socket; + private InetAddress address; + private final Integer port; + + public UdpClient(String host, Integer port) throws UnknownHostException, SocketException { + + try { + this.address = InetAddress.getByName(host); + } catch (UnknownHostException e) { + log.error("UdpClient() - UnknownHostException: " + e.getMessage()); + } + + try { + this.socket = new DatagramSocket(); + } catch (SocketException e) { + log.error("UdpClient() - Could not instantiate DatagramSocket: " + e.getMessage()); + } + + this.port = port; + } + + public void send(String msg) { + byte[] buf = msg.getBytes(StandardCharsets.US_ASCII); + DatagramPacket packet = new DatagramPacket(buf, buf.length, address, port); + if(this.socket != null) { + try { + socket.send(packet); + } catch (IOException e) { + log.error("send() - Could not send packet: " + e.getMessage()); + } + } + } + + public void close() { + socket.close(); + } +} diff --git a/src/main/java/biz/nellemann/syslogd/UdpServer.java b/src/main/java/biz/nellemann/syslogd/net/UdpServer.java similarity index 90% rename from src/main/java/biz/nellemann/syslogd/UdpServer.java rename to src/main/java/biz/nellemann/syslogd/net/UdpServer.java index 8e34d71..850f23b 100644 --- a/src/main/java/biz/nellemann/syslogd/UdpServer.java +++ b/src/main/java/biz/nellemann/syslogd/net/UdpServer.java @@ -13,7 +13,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -package biz.nellemann.syslogd; +package biz.nellemann.syslogd.net; + +import biz.nellemann.syslogd.Application; +import biz.nellemann.syslogd.LogEvent; +import biz.nellemann.syslogd.LogListener; import java.io.IOException; import java.net.DatagramPacket; @@ -68,7 +72,7 @@ public class UdpServer extends Thread { protected List eventListeners = new ArrayList<>(); - public synchronized void addEventListener( LogListener l ) { + public synchronized void addEventListener(Application l ) { eventListeners.add( l ); } diff --git a/src/main/java/biz/nellemann/syslogd/parser/SyslogParser.java b/src/main/java/biz/nellemann/syslogd/parser/SyslogParser.java new file mode 100644 index 0000000..d7fc4fc --- /dev/null +++ b/src/main/java/biz/nellemann/syslogd/parser/SyslogParser.java @@ -0,0 +1,66 @@ +/* + Copyright 2020 mark.nellemann@gmail.com + + 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 + + http://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. + */ +package biz.nellemann.syslogd.parser; + +import biz.nellemann.syslogd.msg.SyslogMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.Instant; + +public abstract class SyslogParser { + + private final static Logger log = LoggerFactory.getLogger(SyslogParser.class); + + + public abstract SyslogMessage parse(final String input); + + abstract Instant parseTimestamp(final String dateString); + + + /** + * Converts syslog PRI field into Facility. + * + * @param pri + * @return + */ + public int getFacility(String pri) { + + int priority = Integer.parseInt(pri); + int facility = priority >> 3; + + //log.debug("getFacility() - " + pri + " => " + facility); + return facility; + } + + + /** + * Converts syslog PRI field into Severity. + * + * @param pri + * @return + */ + public int getSeverity(String pri) { + + int priority = Integer.parseInt(pri); + int severity = priority & 0x07; + + //log.debug("getSeverity() - " + pri + " => " + severity); + return severity; + } + + +} diff --git a/src/main/java/biz/nellemann/syslogd/parser/SyslogParserRfc3164.java b/src/main/java/biz/nellemann/syslogd/parser/SyslogParserRfc3164.java new file mode 100644 index 0000000..37da9ca --- /dev/null +++ b/src/main/java/biz/nellemann/syslogd/parser/SyslogParserRfc3164.java @@ -0,0 +1,103 @@ +/* + Copyright 2020 mark.nellemann@gmail.com + + 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 + + http://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. + */ +package biz.nellemann.syslogd.parser; + +import biz.nellemann.syslogd.msg.Facility; +import biz.nellemann.syslogd.msg.Severity; +import biz.nellemann.syslogd.msg.SyslogMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.*; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SyslogParserRfc3164 extends SyslogParser { + + private final static Logger log = LoggerFactory.getLogger(SyslogParserRfc3164.class); + + private final Pattern pattern = Pattern.compile("^<(\\d{1,3})>(\\D{3}\\s+\\d{1,2} \\d{2}:\\d{2}:\\d{2})\\s+(Message forwarded from \\S+:|\\S+)\\s+([^\\s:]+):?\\s+(.*)", Pattern.CASE_INSENSITIVE); + private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy MMM [ ]d HH:mm:ss").withZone(ZoneId.systemDefault()); //.withZone(ZoneOffset.UTC); + + /** + * Parses [rfc3164](https://tools.ietf.org/html/rfc3164) syslog messages. + * + * @param input + * @return + * @throws NumberFormatException + */ + @Override + public SyslogMessage parse(final String input) throws NumberFormatException { + + log.debug("parseRfc3164() " + input); + + Matcher matcher = pattern.matcher(input); + if(!matcher.find()) { + System.err.println("!" + input); + return null; + } + + String pri = matcher.group(1); + String date = matcher.group(2); + String hostname = matcher.group(3); + String application = matcher.group(4); + String msg = matcher.group(5); + + if(hostname.endsWith(":")) { + String[] tmp = hostname.split(" "); + hostname = tmp[tmp.length-1]; + hostname = hostname.substring(0, hostname.length()-1); + } + + Integer facility = getFacility(pri); + Integer severity = getSeverity(pri); + + SyslogMessage syslogMessage = new SyslogMessage(msg.trim()); + syslogMessage.facility = Facility.getByNumber(facility); + syslogMessage.severity = Severity.getByNumber(severity); + syslogMessage.timestamp = parseTimestamp(date); + syslogMessage.hostname = hostname; + syslogMessage.application = application; + + return syslogMessage; + } + + + /** + * Parse rfc3164 TIMESTAMP field into Instant. + * + * @param dateString + * @return + */ + protected Instant parseTimestamp(String dateString) { + + // We need to add year to parse date correctly + OffsetDateTime odt = OffsetDateTime.now(); + + // Date: Mmm dd hh:mm:ss + Instant instant = null; + try { + instant = Instant.from(dateTimeFormatter.parse(odt.getYear() + " " + dateString)); + } catch(DateTimeParseException e) { + log.error("parseDate()", e); + } + + return instant; + } + +} diff --git a/src/main/java/biz/nellemann/syslogd/parser/SyslogParserRfc5424.java b/src/main/java/biz/nellemann/syslogd/parser/SyslogParserRfc5424.java new file mode 100644 index 0000000..dc16626 --- /dev/null +++ b/src/main/java/biz/nellemann/syslogd/parser/SyslogParserRfc5424.java @@ -0,0 +1,134 @@ +/* + Copyright 2020 mark.nellemann@gmail.com + + 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 + + http://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. + */ +package biz.nellemann.syslogd.parser; + +import biz.nellemann.syslogd.msg.Severity; +import biz.nellemann.syslogd.msg.Facility; +import biz.nellemann.syslogd.msg.SyslogMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.*; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SyslogParserRfc5424 extends SyslogParser { + + private final static Logger log = LoggerFactory.getLogger(SyslogParserRfc5424.class); + + private final Pattern pattern = Pattern.compile("^<(\\d{1,3})>(\\d+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\[.*\\]|-)\\s+(\\S+)", Pattern.CASE_INSENSITIVE); + + /** + * Parses [rfc5424](https://tools.ietf.org/html/rfc5424) syslog messages. + * + * @param input + * @return + * @throws NumberFormatException + */ + @Override + public SyslogMessage parse(final String input) throws NumberFormatException { + + log.debug("parseRfc5424() " + input); + + Matcher matcher = pattern.matcher(input); + boolean matchFound = matcher.find(); + if(!matchFound) { + log.debug("parseRfc5424() - Match not found in: " + input); + System.err.println("!" + input); + 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); + + Integer facility = getFacility(pri); + Integer severity = getSeverity(pri); + + SyslogMessage syslogMessage = new SyslogMessage(msg.trim()); + syslogMessage.facility = Facility.getByNumber(facility); + syslogMessage.severity = Severity.getByNumber(severity); + syslogMessage.version = Integer.parseInt(ver); + syslogMessage.timestamp = parseTimestamp(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; + if(data != null && !data.equals("-")) + syslogMessage.structuredData = data; + + return syslogMessage; + } + + + /** + * Parse rfc5424 TIMESTAMP field into Instant. + * + * @param dateString + * @return + */ + protected Instant parseTimestamp(String dateString) { + + /* + https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html + + ex1: 1985-04-12T23:20:50.52Z + ex2: 1985-04-12T19:20:50.52-04:00 + ex3: 2003-10-11T22:14:15.003Z + ex4: 2003-08-24T05:14:15.000003-07:00 + ex5: 2003-08-24T05:14:15.000000003-07:00 + */ + + List formatStrings = Arrays.asList( + "yyyy-MM-dd'T'HH:mm:ss.SS'Z'", + "yyyy-MM-dd'T'HH:mm:ss.SSXXX", + "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", + "yyyy-MM-dd'T'HH:mm:ss.SSSSSSXXX", + "yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSSXXX" + ); + + for(String formatString : formatStrings) + { + try { + return new SimpleDateFormat(formatString).parse(dateString).toInstant(); + } + catch (ParseException e) {} + } + /* + try { + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME; + instant = Instant.from(dateTimeFormatter.parse(dateString)); + } catch(DateTimeParseException e) { + log.error("parseTimestamp()", e); + } + return instant;*/ + return null; + } + +} diff --git a/src/test/groovy/biz/nellemann/syslogd/SyslogParserRfc3164Test.groovy b/src/test/groovy/biz/nellemann/syslogd/SyslogParserRfc3164Test.groovy new file mode 100644 index 0000000..221f938 --- /dev/null +++ b/src/test/groovy/biz/nellemann/syslogd/SyslogParserRfc3164Test.groovy @@ -0,0 +1,112 @@ +package biz.nellemann.syslogd + +import biz.nellemann.syslogd.msg.SyslogMessage +import biz.nellemann.syslogd.parser.SyslogParser +import biz.nellemann.syslogd.parser.SyslogParserRfc3164 +import spock.lang.Specification +import java.time.Instant +import java.time.OffsetDateTime; + +class SyslogParserRfc3164Test extends Specification { + + SyslogParser syslogParser; + + void setup() { + syslogParser = new SyslogParserRfc3164(); + } + + + void "test rfc3164 aix/vios message"() { + + setup: + def input = "<13>Sep 23 08:37:09 Message forwarded from p924vio1: padmin: test" + + when: + SyslogMessage msg = syslogParser.parse(input) + + then: + msg.message == "test" + msg.hostname == "p924vio1" + msg.application == "padmin" + } + + void "test another rfc3164 aix/vios message"() { + + setup: + def input = "<13>Dec 18 10:09:22 Message forwarded from p924vio1: root: [errnotify] seq: 24266 - AA8AB241 1218100920 T O OPERATOR OPERATOR NOTIFICATION" + + when: + SyslogMessage msg = syslogParser.parse(input) + + then: + msg.message == "[errnotify] seq: 24266 - AA8AB241 1218100920 T O OPERATOR OPERATOR NOTIFICATION" + msg.hostname == "p924vio1" + msg.application == "root" + } + + void "test rfc3164 normal message"() { + + setup: + def input = "<13>Sep 23 08:53:28 xps13 mark: adfdfdf3432434" + + when: + SyslogMessage msg = syslogParser.parse(input) + + then: + msg.message == "adfdfdf3432434" + msg.hostname == "xps13" + msg.application == "mark" + } + + void "test rsyslogd sudo message"() { + setup: + String input = "<85>Oct 5 17:13:41 xps13 sudo: mark : TTY=pts/1 ; PWD=/etc/rsyslog.d ; USER=root ; COMMAND=/usr/sbin/service rsyslog restart" + + when: + SyslogMessage msg = syslogParser.parse(input) + + then: + msg.application == "sudo" + msg.message == "mark : TTY=pts/1 ; PWD=/etc/rsyslog.d ; USER=root ; COMMAND=/usr/sbin/service rsyslog restart" + } + + void "test gdm-session message"() { + setup: + String input = "<12>Oct 5 18:31:01 xps13 /usr/lib/gdm3/gdm-x-session[1921]: (EE) event5 - CUST0001:00 06CB:76AF Touchpad: kernel bug: Touch jump detected and discarded." + + when: + SyslogMessage msg = syslogParser.parse(input) + + then: + msg.application == "/usr/lib/gdm3/gdm-x-session[1921]" + msg.message == "(EE) event5 - CUST0001:00 06CB:76AF Touchpad: kernel bug: Touch jump detected and discarded." + } + + void "test intellij message"() { + setup: + String input = "<14>Oct 6 05:10:26 xps13 com.jetbrains.IntelliJ-IDEA-Ulti git4idea.commands.GitStandardProgressAnalyzer\$1.onLineAvailable(GitStandardProgressAnalyzer.java:45)" + + when: + SyslogMessage msg = syslogParser.parse(input) + + then: + msg.application == "com.jetbrains.IntelliJ-IDEA-Ulti" + msg.message == "git4idea.commands.GitStandardProgressAnalyzer\$1.onLineAvailable(GitStandardProgressAnalyzer.java:45)" + } + + void "test parseRfc3164Timestamp"() { + + setup: + OffsetDateTime odt = OffsetDateTime.now() + String dateString = "Sep 12 20:50:13" + + when: + Instant inst = syslogParser.parseTimestamp(dateString) + + then: + inst.toString() == "${odt.getYear()}-09-12T18:50:13Z" + } + +} + + diff --git a/src/test/groovy/biz/nellemann/syslogd/SyslogParserRfc5424Test.groovy b/src/test/groovy/biz/nellemann/syslogd/SyslogParserRfc5424Test.groovy new file mode 100644 index 0000000..6eaa7a7 --- /dev/null +++ b/src/test/groovy/biz/nellemann/syslogd/SyslogParserRfc5424Test.groovy @@ -0,0 +1,125 @@ +package biz.nellemann.syslogd + +import biz.nellemann.syslogd.msg.SyslogMessage +import biz.nellemann.syslogd.parser.SyslogParser +import biz.nellemann.syslogd.parser.SyslogParserRfc5424 +import spock.lang.Specification +import java.time.Instant + +class SyslogParserRfc5424Test extends Specification { + + SyslogParser syslogParser; + + void setup() { + syslogParser = new SyslogParserRfc5424(); + } + + void "test rfc5424 message"() { + + setup: + def input = '<13>1 2020-09-23T08:57:30.950699+02:00 xps13 mark - - [exampleSDID@32473 iut="3" eventSource="Application" eventID="1011"] adfdfdf3432434565656' + + when: + SyslogMessage msg = syslogParser.parse(input) + + then: + msg.message == "adfdfdf3432434565656" + msg.processId == "-" + } + + void "test rfc5424 example message"() { + + setup: + def input = "<34>1 2003-10-11T22:14:15.003Z mymachine.example.com su - ID47 - BOM'su root' failed for lonvick on /dev/pts/8" + + when: + SyslogMessage msg = syslogParser.parse(input) + + then: + msg.hostname == "mymachine.example.com" + msg.application == "su" + msg.processId == "-" + msg.messageId == "ID47" + msg.structuredData == "-" + } + + void "test rfc5424 example2 message"() { + + setup: + def input = "<165>1 2003-08-24T05:14:15.000003-07:00 192.0.2.1 myproc 8710 - - %% It's time to make the do-nuts." + + when: + SyslogMessage msg = syslogParser.parse(input) + + then: + msg.hostname == "192.0.2.1" + msg.application == "myproc" + msg.processId == "8710" + msg.messageId == "-" + msg.structuredData == "-" + } + + void "test parseRfc5424Timestamp ex1"() { + setup: + String dateString = "1985-04-12T23:20:50.52Z" + + when: + Instant inst = syslogParser.parseTimestamp(dateString) + + then: + inst.toString() == "1985-04-12T21:20:50.052Z" + inst.toEpochMilli() == 482188850052 + } + + void "test parseRfc5424Timestamp ex2"() { + setup: + String dateString = "1985-04-12T19:20:50.52-04:00" + + when: + Instant inst = syslogParser.parseTimestamp(dateString) + + then: + inst.toString() == "1985-04-12T23:20:50.052Z" + inst.toEpochMilli() == 482196050052 + } + + void "test parseRfc5424Timestamp ex3"() { + setup: + String dateString = "2003-10-11T22:14:15.003Z" + + when: + Instant inst = syslogParser.parseTimestamp(dateString) + + then: + inst.toString() == "2003-10-11T20:14:15.003Z" + inst.toEpochMilli() == 1065903255003 + } + + void "test parseRfc5424Timestamp ex4"() { + setup: + String dateString = "2003-08-24T05:14:15.000003-07:00" + + when: + Instant inst = syslogParser.parseTimestamp(dateString) + + then: + inst.toString() == "2003-08-24T12:14:15.003Z" + inst.toEpochMilli() == 1061727255003 + } + + void "test parseRfc5424Timestamp ex5"() { + setup: + String dateString = "2003-08-24T05:14:15.000000003-07:00" + + when: + Instant inst = syslogParser.parseTimestamp(dateString) + + then: + inst.toString() == "2003-08-24T12:14:15.003Z" + inst.toEpochMilli() == 1061727255003 + } + + +} + + diff --git a/src/test/groovy/biz/nellemann/syslogd/SyslogParserTest.groovy b/src/test/groovy/biz/nellemann/syslogd/SyslogParserTest.groovy index ff35567..55e72eb 100644 --- a/src/test/groovy/biz/nellemann/syslogd/SyslogParserTest.groovy +++ b/src/test/groovy/biz/nellemann/syslogd/SyslogParserTest.groovy @@ -1,223 +1,33 @@ package biz.nellemann.syslogd -import spock.lang.Ignore +import biz.nellemann.syslogd.msg.Facility +import biz.nellemann.syslogd.msg.Severity +import biz.nellemann.syslogd.parser.SyslogParser +import biz.nellemann.syslogd.parser.SyslogParserRfc5424 import spock.lang.Specification -import java.time.Instant -import java.time.OffsetDateTime; class SyslogParserTest extends Specification { - void "test rfc5424 message"() { + SyslogParser syslogParser; - setup: - def input = '<13>1 2020-09-23T08:57:30.950699+02:00 xps13 mark - - [exampleSDID@32473 iut="3" eventSource="Application" eventID="1011"] adfdfdf3432434565656' - - when: - SyslogMessage msg = SyslogParser.parseRfc5424(input) - - then: - msg.message == "adfdfdf3432434565656" - msg.processId == "-" + void setup() { + syslogParser = new SyslogParserRfc5424(); } - void "test rfc5424 example message"() { - - setup: - def input = "<34>1 2003-10-11T22:14:15.003Z mymachine.example.com su - ID47 - BOM'su root' failed for lonvick on /dev/pts/8" - + void "test facility LOCAL0"() { when: - SyslogMessage msg = SyslogParser.parseRfc5424(input) + int code = syslogParser.getFacility("132") then: - msg.hostname == "mymachine.example.com" - msg.application == "su" - msg.processId == "-" - msg.messageId == "ID47" - msg.structuredData == "-" + code == Facility.LOCAL0.toNumber() } - void "test rfc5424 example2 message"() { - - setup: - def input = "<165>1 2003-08-24T05:14:15.000003-07:00 192.0.2.1 myproc 8710 - - %% It's time to make the do-nuts." - + void "test severity WARN"() { when: - SyslogMessage msg = SyslogParser.parseRfc5424(input) + int code = syslogParser.getSeverity("132") then: - msg.hostname == "192.0.2.1" - msg.application == "myproc" - msg.processId == "8710" - msg.messageId == "-" - msg.structuredData == "-" - } - - - void "test rfc3164 aix/vios message"() { - - setup: - def input = "<13>Sep 23 08:37:09 Message forwarded from p924vio1: padmin: test" - - when: - SyslogMessage msg = SyslogParser.parseRfc3164(input) - - then: - msg.message == "test" - msg.hostname == "p924vio1" - msg.application == "padmin" - } - - void "test another rfc3164 aix/vios message"() { - - setup: - def input = "<13>Dec 18 10:09:22 Message forwarded from p924vio1: root: [errnotify] seq: 24266 - AA8AB241 1218100920 T O OPERATOR OPERATOR NOTIFICATION" - - when: - SyslogMessage msg = SyslogParser.parseRfc3164(input) - - then: - msg.message == "[errnotify] seq: 24266 - AA8AB241 1218100920 T O OPERATOR OPERATOR NOTIFICATION" - msg.hostname == "p924vio1" - msg.application == "root" - } - - void "test rfc3164 normal message"() { - - setup: - def input = "<13>Sep 23 08:53:28 xps13 mark: adfdfdf3432434" - - when: - SyslogMessage msg = SyslogParser.parseRfc3164(input) - - then: - msg.message == "adfdfdf3432434" - msg.hostname == "xps13" - msg.application == "mark" - } - - void "test rsyslogd sudo message"() { - setup: - String input = "<85>Oct 5 17:13:41 xps13 sudo: mark : TTY=pts/1 ; PWD=/etc/rsyslog.d ; USER=root ; COMMAND=/usr/sbin/service rsyslog restart" - - when: - SyslogMessage msg = SyslogParser.parseRfc3164(input) - - then: - msg.application == "sudo" - msg.message == "mark : TTY=pts/1 ; PWD=/etc/rsyslog.d ; USER=root ; COMMAND=/usr/sbin/service rsyslog restart" - } - - void "test gdm-session message"() { - setup: - String input = "<12>Oct 5 18:31:01 xps13 /usr/lib/gdm3/gdm-x-session[1921]: (EE) event5 - CUST0001:00 06CB:76AF Touchpad: kernel bug: Touch jump detected and discarded." - - when: - SyslogMessage msg = SyslogParser.parseRfc3164(input) - - then: - msg.application == "/usr/lib/gdm3/gdm-x-session[1921]" - msg.message == "(EE) event5 - CUST0001:00 06CB:76AF Touchpad: kernel bug: Touch jump detected and discarded." - } - - void "test intellij message"() { - setup: - String input = "<14>Oct 6 05:10:26 xps13 com.jetbrains.IntelliJ-IDEA-Ulti git4idea.commands.GitStandardProgressAnalyzer\$1.onLineAvailable(GitStandardProgressAnalyzer.java:45)" - - when: - SyslogMessage msg = SyslogParser.parseRfc3164(input) - - then: - msg.application == "com.jetbrains.IntelliJ-IDEA-Ulti" - msg.message == "git4idea.commands.GitStandardProgressAnalyzer\$1.onLineAvailable(GitStandardProgressAnalyzer.java:45)" - } - - void "test parseRfc3164Timestamp"() { - - setup: - OffsetDateTime odt = OffsetDateTime.now() - String dateString = "Sep 12 20:50:13" - - when: - Instant inst = SyslogParser.parseRfc3164Timestamp(dateString) - - then: - inst.toString() == "${odt.getYear()}-09-12T20:50:13Z" - } - - void "test parseRfc5424Timestamp ex1"() { - setup: - String dateString = "1985-04-12T23:20:50.52Z" - - when: - Instant inst = SyslogParser.parseRfc5424Timestamp(dateString) - - then: - inst.toString() == "1985-04-12T21:20:50.052Z" - inst.toEpochMilli() == 482188850052 - } - - void "test parseRfc5424Timestamp ex2"() { - setup: - String dateString = "1985-04-12T19:20:50.52-04:00" - - when: - Instant inst = SyslogParser.parseRfc5424Timestamp(dateString) - - then: - inst.toString() == "1985-04-12T23:20:50.052Z" - inst.toEpochMilli() == 482196050052 - } - - void "test parseRfc5424Timestamp ex3"() { - setup: - String dateString = "2003-10-11T22:14:15.003Z" - - when: - Instant inst = SyslogParser.parseRfc5424Timestamp(dateString) - - then: - inst.toString() == "2003-10-11T20:14:15.003Z" - inst.toEpochMilli() == 1065903255003 - } - - void "test parseRfc5424Timestamp ex4"() { - setup: - String dateString = "2003-08-24T05:14:15.000003-07:00" - - when: - Instant inst = SyslogParser.parseRfc5424Timestamp(dateString) - - then: - inst.toString() == "2003-08-24T12:14:15.003Z" - inst.toEpochMilli() == 1061727255003 - } - - void "test parseRfc5424Timestamp ex5"() { - setup: - String dateString = "2003-08-24T05:14:15.000000003-07:00" - - when: - Instant inst = SyslogParser.parseRfc5424Timestamp(dateString) - - then: - inst.toString() == "2003-08-24T12:14:15.003Z" - inst.toEpochMilli() == 1061727255003 - } - - - - @Ignore - 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 + code == Severity.WARN.toNumber() } }