diff --git a/tinylog-impl/src/main/java/org/tinylog/writers/SyslogWriter.java b/tinylog-impl/src/main/java/org/tinylog/writers/SyslogWriter.java new file mode 100755 index 000000000..4b234f518 --- /dev/null +++ b/tinylog-impl/src/main/java/org/tinylog/writers/SyslogWriter.java @@ -0,0 +1,69 @@ +/* + * Copyright 2023 Piotr Karlowicz + * + * 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 org.tinylog.writers; + +import java.io.IOException; +import java.util.Locale; +import java.util.Map; + +import org.tinylog.core.LogEntry; +import org.tinylog.writers.raw.AbstractSocketWriter; +import org.tinylog.writers.raw.TcpSocketWriter; +import org.tinylog.writers.raw.UdpSocketWriter; + +/** + * Writer for outputting log entries to syslog server. + */ +public final class SyslogWriter extends AbstractFormatPatternWriter { + + private AbstractSocketWriter socketWriter; + + /** + * @param properties + * Configuration for writer + * + * @throws IOException + * Socket cannot be opened for write access + * @throws IllegalArgumentException + * A property has an invalid value or is missing in configuration + */ + public SyslogWriter(final Map properties) throws IllegalArgumentException, IOException { + super(properties); + + String protocol = getStringValue("protocol"); + if (protocol == null || protocol.toUpperCase(Locale.ROOT).equals("UDP")) { + socketWriter = new UdpSocketWriter(properties); + } else if (protocol.toUpperCase(Locale.ROOT).equals("TCP")) { + socketWriter = new TcpSocketWriter(properties); + } else { + throw new IllegalArgumentException("Invalid protocol"); + } + } + + @Override + public void write(final LogEntry logEntry) throws Exception { + socketWriter.write(logEntry); + } + + @Override + public void flush() throws Exception { + socketWriter.flush(); + } + + @Override + public void close() throws Exception { + socketWriter.close(); + } + +} diff --git a/tinylog-impl/src/main/java/org/tinylog/writers/raw/AbstractSocketWriter.java b/tinylog-impl/src/main/java/org/tinylog/writers/raw/AbstractSocketWriter.java new file mode 100644 index 000000000..9bbab7e46 --- /dev/null +++ b/tinylog-impl/src/main/java/org/tinylog/writers/raw/AbstractSocketWriter.java @@ -0,0 +1,129 @@ +/* + * Copyright 2023 Piotr Karlowicz + * + * 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 org.tinylog.writers.raw; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.charset.Charset; +import java.util.Map; + +import org.tinylog.Level; +import org.tinylog.core.LogEntry; +import org.tinylog.writers.AbstractFormatPatternWriter; + +/** + * Base writer for outputting log entries to syslog server. + */ +public abstract class AbstractSocketWriter extends AbstractFormatPatternWriter { + + private static final String DEFAULT_HOST_NAME = "localhost"; + private static final int DEFAULT_PORT_NUMBER = 514; + private static final String DEFAULT_FACILITY = "USER"; + private static final int FACILITY_CODE_SHIFT = 3; + private static final String DEFAULT_SEVERITY = "INFO"; + + private final InetAddress inetAddress; + private final int port; + private final Charset charset; + private final String identification; + + /** + * @param properties + * Configuration for writer + * + * @throws UnknownHostException + * Host name cannot be identified + */ + public AbstractSocketWriter(final Map properties) throws UnknownHostException { + super(properties); + + String host = getStringValue("host"); + if (host == null) { + host = DEFAULT_HOST_NAME; + } + inetAddress = InetAddress.getByName("localhost"); + String portNumber = getStringValue("port"); + if (portNumber == null) { + port = DEFAULT_PORT_NUMBER; + } else { + port = Integer.parseInt(portNumber); + } + charset = super.getCharset(); + String identification = getStringValue("identification"); + if (identification == null) { + this.identification = ""; + } else { + this.identification = identification; + } + } + + public final InetAddress getInetAddress() { + return inetAddress; + } + + public final int getPort() { + return port; + } + + /** + * Return the priority code for facility and severity. + * + * @param level + * Log level to use when severity is not specified. + * + * @return The priority code calculated from facility and severity code. + */ + public int getCode(final Level level) { + String facility = getStringValue("facility"); + String severity = getStringValue("severity"); + + if (facility == null) { + facility = DEFAULT_FACILITY; + } + + int facilityCode = SyslogFacility.valueOf(facility).getCode(); + + if (severity == null) { + if (level != null) { + severity = SyslogSeverity.getSeverity(level).name(); + } else { + severity = DEFAULT_SEVERITY; + } + } + + int severityCode = SyslogSeverity.valueOf(severity).getCode(); + + return (facilityCode << FACILITY_CODE_SHIFT) + severityCode; + } + + /** + * Return the formated syslog message. + * + * @param logEntry + * Log entry for rendering. + * + * @return The formated message. + */ + public byte[] formatMessage(final LogEntry logEntry) { + StringBuilder builder = new StringBuilder(); + builder.append("<"); + builder.append(getCode(logEntry.getLevel())); + builder.append(">"); + builder.append(identification); + builder.append(": "); + builder.append(render(logEntry)); + return builder.toString().getBytes(charset); + } + +} diff --git a/tinylog-impl/src/main/java/org/tinylog/writers/raw/SyslogFacility.java b/tinylog-impl/src/main/java/org/tinylog/writers/raw/SyslogFacility.java new file mode 100644 index 000000000..374965913 --- /dev/null +++ b/tinylog-impl/src/main/java/org/tinylog/writers/raw/SyslogFacility.java @@ -0,0 +1,154 @@ +/* + * Copyright 2023 Piotr Karlowicz + * + * 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 org.tinylog.writers.raw; + +/** + * Syslog Facility codes. + */ +public enum SyslogFacility { + + /** + * Kernel messages. + */ + KERN(0), + + /** + * User level messages. + */ + USER(1), + + /** + * Mail system. + */ + MAIL(2), + + /** + * System daemons. + */ + DAEMON(3), + + /** + * Security/Authorization messages. + */ + AUTH(4), + + /** + * Messages generated by syslogd. + */ + SYSLOG(5), + + /** + * Line printer subsystem. + */ + LPR(6), + + /** + * Network news subsystem. + */ + NEWS(7), + + /** + * UUCP subsystem. + */ + UUCP(8), + + /** + * Clock daemon. + */ + CRON(9), + + /** + * Security/Authorization messages. + */ + AUTHPRIV(10), + + /** + * FTP daemon. + */ + FTP(11), + + /** + * NTP subsystem. + */ + NTP(12), + + /** + * Log audit. + */ + LOG_AUDIT(13), + + /** + * Log alert. + */ + LOG_ALERT(14), + + /** + * Clock daemon. + */ + CLOCK(15), + + /** + * Local use 0. + */ + LOCAL0(16), + + /** + * Local use 1. + */ + LOCAL1(17), + + /** + * Local use 2. + */ + LOCAL2(18), + + /** + * Local use 3. + */ + LOCAL3(19), + + /** + * Local use 4. + */ + LOCAL4(20), + + /** + * Local use 5. + */ + LOCAL5(21), + + /** + * Local use 6. + */ + LOCAL6(22), + + /** + * Local use 7. + */ + LOCAL7(23); + + private final int code; + + SyslogFacility(final int code) { + this.code = code; + } + + /** + * Retrieve the value of the enumeration. + * @return The value associated with the enumeration. + */ + public int getCode() { + return this.code; + } +} diff --git a/tinylog-impl/src/main/java/org/tinylog/writers/raw/SyslogSeverity.java b/tinylog-impl/src/main/java/org/tinylog/writers/raw/SyslogSeverity.java new file mode 100644 index 000000000..4512f1eb2 --- /dev/null +++ b/tinylog-impl/src/main/java/org/tinylog/writers/raw/SyslogSeverity.java @@ -0,0 +1,93 @@ +/* + * Copyright 2023 Piotr Karlowicz + * + * 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 org.tinylog.writers.raw; + +import org.tinylog.Level; + +/** + * Syslog severity codes. + */ +public enum SyslogSeverity { + + /** + * System is unusable. + */ + EMERG(0), + /** + * Action must be taken immediately. + */ + ALERT(1), + /** + * Critical conditions. + */ + CRITICAL(2), + /** + * Error conditions. + */ + ERROR(3), + /** + * Warning conditions. + */ + WARNING(4), + /** + * Normal but significant conditions. + */ + NOTICE(5), + /** + * Informational messages. + */ + INFO(6), + /** + * Debug level messages. + */ + DEBUG(7); + + private final int code; + + SyslogSeverity(final int code) { + this.code = code; + } + + /** + * Returns the severity code. + * @return The numeric value associated with the Severity. + */ + public int getCode() { + return this.code; + } + + /** + * Returns the Severity for the specified Level. + * @param level The Level. + * @return The matching Severity, or INFO if there is no match. + */ + public static SyslogSeverity getSeverity(final Level level) { + switch (level) { + case TRACE: + return DEBUG; + case DEBUG: + return DEBUG; + case INFO: + return INFO; + case WARN: + return WARNING; + case ERROR: + return ERROR; + case OFF: + return EMERG; + default: + return INFO; + } + } +} diff --git a/tinylog-impl/src/main/java/org/tinylog/writers/raw/TcpSocketWriter.java b/tinylog-impl/src/main/java/org/tinylog/writers/raw/TcpSocketWriter.java new file mode 100644 index 000000000..d22ee05fa --- /dev/null +++ b/tinylog-impl/src/main/java/org/tinylog/writers/raw/TcpSocketWriter.java @@ -0,0 +1,51 @@ +/* + * Copyright 2023 Piotr Karlowicz + * + * 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 org.tinylog.writers.raw; + +import java.io.IOException; +import java.net.Socket; +import java.util.Map; + +import org.tinylog.core.LogEntry; + +/** + * Base writer for outputting log entries to syslog server via TCP. + */ +public class TcpSocketWriter extends AbstractSocketWriter { + + private Socket socket; + + public TcpSocketWriter(final Map properties) throws IOException { + super(properties); + + socket = new Socket(getInetAddress(), getPort()); + } + + @Override + public void write(final LogEntry logEntry) throws IOException { + byte[] message = formatMessage(logEntry); + socket.getOutputStream().write(message); + } + + @Override + public void flush() throws Exception { + socket.getOutputStream().flush(); + } + + @Override + public void close() throws Exception { + socket.close(); + } + +} diff --git a/tinylog-impl/src/main/java/org/tinylog/writers/raw/UdpSocketWriter.java b/tinylog-impl/src/main/java/org/tinylog/writers/raw/UdpSocketWriter.java new file mode 100644 index 000000000..bd724e327 --- /dev/null +++ b/tinylog-impl/src/main/java/org/tinylog/writers/raw/UdpSocketWriter.java @@ -0,0 +1,52 @@ +/* + * Copyright 2023 Piotr Karlowicz + * + * 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 org.tinylog.writers.raw; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.util.Map; + +import org.tinylog.core.LogEntry; + +/** + * Base writer for outputting log entries to syslog server via UDP. + */ +public class UdpSocketWriter extends AbstractSocketWriter { + + private DatagramSocket datagramSocket; + + public UdpSocketWriter(final Map properties) throws IOException { + super(properties); + + datagramSocket = new DatagramSocket(); + } + + @Override + public void write(final LogEntry logEntry) throws IOException { + byte[] message = formatMessage(logEntry); + final DatagramPacket packet = new DatagramPacket(message, message.length, getInetAddress(), getPort()); + datagramSocket.send(packet); + } + + @Override + public void flush() throws Exception { + } + + @Override + public void close() throws Exception { + datagramSocket.close(); + } + +} diff --git a/tinylog-impl/src/main/resources/META-INF/services/org.tinylog.writers.Writer b/tinylog-impl/src/main/resources/META-INF/services/org.tinylog.writers.Writer index 349550ee1..132ba54fd 100644 --- a/tinylog-impl/src/main/resources/META-INF/services/org.tinylog.writers.Writer +++ b/tinylog-impl/src/main/resources/META-INF/services/org.tinylog.writers.Writer @@ -5,3 +5,4 @@ org.tinylog.writers.LogcatWriter org.tinylog.writers.RollingFileWriter org.tinylog.writers.SharedFileWriter org.tinylog.writers.JsonWriter +org.tinylog.writers.SyslogWriter diff --git a/tinylog-impl/src/test/java/org/tinylog/util/TcpSyslogServer.java b/tinylog-impl/src/test/java/org/tinylog/util/TcpSyslogServer.java new file mode 100644 index 000000000..d8527b0db --- /dev/null +++ b/tinylog-impl/src/test/java/org/tinylog/util/TcpSyslogServer.java @@ -0,0 +1,79 @@ +/* + * Copyright 2023 Piotr Karlowicz + * + * 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 org.tinylog.util; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.nio.charset.Charset; + +import org.tinylog.Level; +import org.tinylog.provider.InternalLogger; + +public class TcpSyslogServer extends Thread { + + private final ServerSocket serverSocket; + private volatile boolean shutdown; + private Thread thread; + private String lastMessage = ""; + + public TcpSyslogServer(final int port) throws IOException { + this.serverSocket = new ServerSocket(port); + this.shutdown = false; + } + + @Override + public void run() { + final byte[] bytes = new byte[4096]; + this.thread = Thread.currentThread(); + try { + Socket socket = serverSocket.accept(); + while (!shutdown) { + int len = socket.getInputStream().read(bytes, 0, bytes.length); + if (len != -1) { + lastMessage = new String(bytes, 0, len, Charset.defaultCharset()).trim(); + } + } + } catch (final IOException ex) { + if (!shutdown) { + InternalLogger.log(Level.ERROR, "Failed retrieving TCP message"); + } + } + } + + public void shutdown() throws IOException { + shutdown = true; + if (serverSocket != null) { + serverSocket.close(); + } + if (thread != null) { + thread.interrupt(); + try { + thread.join(100); + } catch (final InterruptedException ex) { + InternalLogger.log(Level.ERROR, "TCP server thread shutdown failed."); + } + } + } + + /** + * Return the last message received. + * + * @return The last message. + */ + public String getLastMessage() { + return lastMessage; + } + +} diff --git a/tinylog-impl/src/test/java/org/tinylog/util/UdpSyslogServer.java b/tinylog-impl/src/test/java/org/tinylog/util/UdpSyslogServer.java new file mode 100644 index 000000000..0703aa959 --- /dev/null +++ b/tinylog-impl/src/test/java/org/tinylog/util/UdpSyslogServer.java @@ -0,0 +1,81 @@ +/* + * Copyright 2023 Piotr Karlowicz + * + * 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 org.tinylog.util; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.SocketException; +import java.nio.charset.Charset; + +import org.tinylog.Level; +import org.tinylog.provider.InternalLogger; + +public class UdpSyslogServer extends Thread { + + private final DatagramSocket socket; + private volatile boolean shutdown; + private Thread thread; + private String lastMessage = ""; + + public UdpSyslogServer(final int port) throws SocketException { + this.socket = new DatagramSocket(port); + this.shutdown = false; + } + + @Override + public void run() { + final byte[] bytes = new byte[4096]; + this.thread = Thread.currentThread(); + final DatagramPacket packet = new DatagramPacket(bytes, bytes.length); + try { + while (!shutdown) { + socket.receive(packet); + lastMessage = new String(packet.getData(), 0, packet.getLength(), Charset.defaultCharset()).trim(); + } + } catch (final IOException ex) { + if (!shutdown) { + InternalLogger.log(Level.ERROR, "Failed retrieving UDP message"); + } + } + } + + /** + * Shutdowns the server. + */ + public void shutdown() { + shutdown = true; + if (socket != null) { + socket.close(); + } + if (thread != null) { + thread.interrupt(); + try { + thread.join(100); + } catch (final InterruptedException ex) { + InternalLogger.log(Level.ERROR, "UDP server thread shutdown failed."); + } + } + } + + /** + * Return the last message received. + * + * @return The last message. + */ + public String getLastMessage() { + return lastMessage; + } + +} diff --git a/tinylog-impl/src/test/java/org/tinylog/writers/SyslogWriterTest.java b/tinylog-impl/src/test/java/org/tinylog/writers/SyslogWriterTest.java new file mode 100755 index 000000000..36ab12390 --- /dev/null +++ b/tinylog-impl/src/test/java/org/tinylog/writers/SyslogWriterTest.java @@ -0,0 +1,361 @@ +/* + * Copyright 2023 Piotr Karlowicz + * + * 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 org.tinylog.writers; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Test; +import org.tinylog.Level; +import org.tinylog.util.LogEntryBuilder; +import org.tinylog.util.TcpSyslogServer; +import org.tinylog.util.UdpSyslogServer; +import org.tinylog.writers.raw.SyslogFacility; +import org.tinylog.writers.raw.SyslogSeverity; + +import static java.util.Collections.singletonMap; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.tinylog.util.Maps.doubletonMap; +import static org.tinylog.util.Maps.tripletonMap; + +/** + * Tests for {@link SyslogWriter}. + */ +public final class SyslogWriterTest { + + private static final Integer TEST_PORT_NUMBER = 9999; + private static final String TEST_MESSAGE = "Test Message"; + + private String getExpectedSyslogMessage(final String message, + final SyslogFacility facility, + final SyslogSeverity severity, + final String identification) { + int code = (facility.getCode() << 3) + severity.getCode(); + return "<" + code + ">" + identification + ": " + message; + } + + /** + * Sends udp message with default settings and verifies it is received. + * + * @throws Exception Failed. + */ + @Test + public void sendDefaultUdpSyslogMessage() throws Exception { + UdpSyslogServer server = new UdpSyslogServer(TEST_PORT_NUMBER); + server.start(); + + SyslogWriter writer = new SyslogWriter(tripletonMap("format", "{message}", "protocol", "udp", "port", TEST_PORT_NUMBER.toString())); + writer.write(LogEntryBuilder.empty().message(TEST_MESSAGE).create()); + + Thread.sleep(250); + assertThat(server.getLastMessage()).isEqualTo(getExpectedSyslogMessage(TEST_MESSAGE, SyslogFacility.USER, SyslogSeverity.INFO, "")); + server.shutdown(); + } + + /** + * Sends udp message with non-default settings and verifies it is received. + * + * @throws Exception Failed. + */ + @Test + public void sendNondefaultUdpSyslogMessage() throws Exception { + UdpSyslogServer server = new UdpSyslogServer(TEST_PORT_NUMBER); + server.start(); + + SyslogFacility facility = SyslogFacility.LOCAL0; + SyslogSeverity severity = SyslogSeverity.ERROR; + Map properties = new HashMap<>(); + properties.put("format", "{message}"); + properties.put("protocol", "udp"); + properties.put("port", TEST_PORT_NUMBER.toString()); + properties.put("facility", facility.toString()); + properties.put("severity", severity.toString()); + + SyslogWriter writer = new SyslogWriter(properties); + writer.write(LogEntryBuilder.empty().message(TEST_MESSAGE).create()); + + Thread.sleep(250); + assertThat(server.getLastMessage()).isEqualTo(getExpectedSyslogMessage(TEST_MESSAGE, facility, severity, "")); + server.shutdown(); + } + + /** + * Verifies that default UDP prrotocol will be used if no protocol is specified. + * + * @throws Exception Failed. + */ + @Test + public void missingProtocol() throws Exception { + UdpSyslogServer server = new UdpSyslogServer(TEST_PORT_NUMBER); + server.start(); + + SyslogWriter writer = new SyslogWriter(doubletonMap("format", "{message}", "port", TEST_PORT_NUMBER.toString())); + writer.write(LogEntryBuilder.empty().message(TEST_MESSAGE).create()); + + Thread.sleep(250); + assertThat(server.getLastMessage()).isEqualTo(getExpectedSyslogMessage(TEST_MESSAGE, SyslogFacility.USER, SyslogSeverity.INFO, "")); + server.shutdown(); + } + + /** + * Verifies that an exception will be thrown, if protocol name is invalid. + * + * @throws Exception Failed. + */ + @Test + public void invalidProtocol() throws Exception { + assertThatThrownBy(() -> new SyslogWriter(singletonMap("protocol", "invalid"))).hasMessageMatching("(?i).*protocol.*"); + } + + /** + * Verifies that an exception will be thrown, if facility is invalid. + * + * @throws Exception Failed. + */ + @Test + public void invalidFacility() throws Exception { + assertThatThrownBy(() -> new SyslogWriter(doubletonMap("protocol", "udp", "facility", "invalid")) + .write(LogEntryBuilder.empty().message(TEST_MESSAGE).create())).hasMessageMatching("(?i).*SyslogFacility.*"); + } + + /** + * Verifies that an exception will be thrown, if severity is invalid. + * + * @throws Exception Failed. + */ + @Test + public void invalidSeverity() throws Exception { + assertThatThrownBy(() -> new SyslogWriter(doubletonMap("protocol", "udp", "severity", "invalid")) + .write(LogEntryBuilder.empty().message(TEST_MESSAGE).create())).hasMessageMatching("(?i).*SyslogSeverity.*"); + } + + /** + * Sends udp message with specified identification string and verifies it is received. + * + * @throws Exception Failed. + */ + @Test + public void sendUdpSyslogMessageWithIdentification() throws Exception { + UdpSyslogServer server = new UdpSyslogServer(TEST_PORT_NUMBER); + server.start(); + + SyslogFacility facility = SyslogFacility.KERN; + SyslogSeverity severity = SyslogSeverity.DEBUG; + String identification = "SyslogWriterTest"; + Map properties = new HashMap<>(); + properties.put("format", "{message}"); + properties.put("protocol", "udp"); + properties.put("port", TEST_PORT_NUMBER.toString()); + properties.put("facility", facility.toString()); + properties.put("severity", severity.toString()); + properties.put("identification", identification); + + SyslogWriter writer = new SyslogWriter(properties); + writer.write(LogEntryBuilder.empty().message(TEST_MESSAGE).create()); + + Thread.sleep(250); + assertThat(server.getLastMessage()).isEqualTo(getExpectedSyslogMessage(TEST_MESSAGE, facility, severity, identification)); + server.shutdown(); + } + + /** + * Sends udp message with different levels and verifies it is received. + * + * @throws Exception Failed. + */ + @Test + public void sendLevelControlledUdpSyslogMessage() throws Exception { + UdpSyslogServer server = new UdpSyslogServer(TEST_PORT_NUMBER); + server.start(); + + SyslogFacility facility = SyslogFacility.AUTH; + Map properties = new HashMap<>(); + properties.put("format", "{message}"); + properties.put("protocol", "udp"); + properties.put("port", TEST_PORT_NUMBER.toString()); + properties.put("facility", facility.toString()); + + SyslogWriter writer = new SyslogWriter(properties); + LogEntryBuilder log = LogEntryBuilder.empty().message(TEST_MESSAGE); + log.level(Level.DEBUG); + writer.write(log.create()); + + Thread.sleep(250); + assertThat(server.getLastMessage()).isEqualTo(getExpectedSyslogMessage(TEST_MESSAGE, facility, SyslogSeverity.DEBUG, "")); + + log.level(Level.ERROR); + writer.write(log.create()); + + Thread.sleep(250); + assertThat(server.getLastMessage()).isEqualTo(getExpectedSyslogMessage(TEST_MESSAGE, facility, SyslogSeverity.ERROR, "")); + + log.level(Level.INFO); + writer.write(log.create()); + + Thread.sleep(250); + assertThat(server.getLastMessage()).isEqualTo(getExpectedSyslogMessage(TEST_MESSAGE, facility, SyslogSeverity.INFO, "")); + + log.level(Level.OFF); + writer.write(log.create()); + + Thread.sleep(250); + assertThat(server.getLastMessage()).isEqualTo(getExpectedSyslogMessage(TEST_MESSAGE, facility, SyslogSeverity.EMERG, "")); + + log.level(Level.TRACE); + writer.write(log.create()); + + Thread.sleep(250); + assertThat(server.getLastMessage()).isEqualTo(getExpectedSyslogMessage(TEST_MESSAGE, facility, SyslogSeverity.DEBUG, "")); + + log.level(Level.WARN); + writer.write(log.create()); + + Thread.sleep(250); + assertThat(server.getLastMessage()).isEqualTo(getExpectedSyslogMessage(TEST_MESSAGE, facility, SyslogSeverity.WARNING, "")); + server.shutdown(); + } + + /** + * Sends tcp message with default settings and verifies it is received. + * + * @throws Exception Failed. + */ + @Test + public void sendDefaultTcpSyslogMessage() throws Exception { + TcpSyslogServer server = new TcpSyslogServer(TEST_PORT_NUMBER); + server.start(); + + SyslogWriter writer = new SyslogWriter(tripletonMap("format", "{message}", "protocol", "tcp", "port", TEST_PORT_NUMBER.toString())); + writer.write(LogEntryBuilder.empty().message(TEST_MESSAGE).create()); + + Thread.sleep(250); + assertThat(server.getLastMessage()).isEqualTo(getExpectedSyslogMessage(TEST_MESSAGE, SyslogFacility.USER, SyslogSeverity.INFO, "")); + server.shutdown(); + } + + /** + * Sends tcp message with non-default settings and verifies it is received. + * + * @throws Exception Failed. + */ + @Test + public void sendNondefaultTcpSyslogMessage() throws Exception { + TcpSyslogServer server = new TcpSyslogServer(TEST_PORT_NUMBER); + server.start(); + + SyslogFacility facility = SyslogFacility.LOCAL0; + SyslogSeverity severity = SyslogSeverity.ERROR; + Map properties = new HashMap<>(); + properties.put("format", "{message}"); + properties.put("protocol", "tcp"); + properties.put("port", TEST_PORT_NUMBER.toString()); + properties.put("facility", facility.toString()); + properties.put("severity", severity.toString()); + + SyslogWriter writer = new SyslogWriter(properties); + writer.write(LogEntryBuilder.empty().message(TEST_MESSAGE).create()); + + Thread.sleep(250); + assertThat(server.getLastMessage()).isEqualTo(getExpectedSyslogMessage(TEST_MESSAGE, facility, severity, "")); + server.shutdown(); + } + + /** + * Sends tcp message with specified identification string and verifies it is received. + * + * @throws Exception Failed. + */ + @Test + public void sendTcpSyslogMessageWithIdentification() throws Exception { + TcpSyslogServer server = new TcpSyslogServer(TEST_PORT_NUMBER); + server.start(); + + SyslogFacility facility = SyslogFacility.KERN; + SyslogSeverity severity = SyslogSeverity.DEBUG; + String identification = "SyslogWriterTest"; + Map properties = new HashMap<>(); + properties.put("format", "{message}"); + properties.put("protocol", "tcp"); + properties.put("port", TEST_PORT_NUMBER.toString()); + properties.put("facility", facility.toString()); + properties.put("severity", severity.toString()); + properties.put("identification", identification); + + SyslogWriter writer = new SyslogWriter(properties); + writer.write(LogEntryBuilder.empty().message(TEST_MESSAGE).create()); + + Thread.sleep(250); + assertThat(server.getLastMessage()).isEqualTo(getExpectedSyslogMessage(TEST_MESSAGE, facility, severity, identification)); + server.shutdown(); + } + + /** + * Sends tcp message with different levels and verifies it is received. + * + * @throws Exception Failed. + */ + @Test + public void sendLevelControlledTcpSyslogMessage() throws Exception { + TcpSyslogServer server = new TcpSyslogServer(TEST_PORT_NUMBER); + server.start(); + + SyslogFacility facility = SyslogFacility.AUTH; + Map properties = new HashMap<>(); + properties.put("format", "{message}"); + properties.put("protocol", "tcp"); + properties.put("port", TEST_PORT_NUMBER.toString()); + properties.put("facility", facility.toString()); + + SyslogWriter writer = new SyslogWriter(properties); + LogEntryBuilder log = LogEntryBuilder.empty().message(TEST_MESSAGE); + log.level(Level.DEBUG); + writer.write(log.create()); + + Thread.sleep(250); + assertThat(server.getLastMessage()).isEqualTo(getExpectedSyslogMessage(TEST_MESSAGE, facility, SyslogSeverity.DEBUG, "")); + + log.level(Level.ERROR); + writer.write(log.create()); + + Thread.sleep(250); + assertThat(server.getLastMessage()).isEqualTo(getExpectedSyslogMessage(TEST_MESSAGE, facility, SyslogSeverity.ERROR, "")); + + log.level(Level.INFO); + writer.write(log.create()); + + Thread.sleep(250); + assertThat(server.getLastMessage()).isEqualTo(getExpectedSyslogMessage(TEST_MESSAGE, facility, SyslogSeverity.INFO, "")); + + log.level(Level.OFF); + writer.write(log.create()); + + Thread.sleep(250); + assertThat(server.getLastMessage()).isEqualTo(getExpectedSyslogMessage(TEST_MESSAGE, facility, SyslogSeverity.EMERG, "")); + + log.level(Level.TRACE); + writer.write(log.create()); + + Thread.sleep(250); + assertThat(server.getLastMessage()).isEqualTo(getExpectedSyslogMessage(TEST_MESSAGE, facility, SyslogSeverity.DEBUG, "")); + + log.level(Level.WARN); + writer.write(log.create()); + + Thread.sleep(250); + assertThat(server.getLastMessage()).isEqualTo(getExpectedSyslogMessage(TEST_MESSAGE, facility, SyslogSeverity.WARNING, "")); + server.shutdown(); + } + +}