From fb0a208deeea2aee0d7a870d970b716593de6244 Mon Sep 17 00:00:00 2001 From: Cyrille Le Clerc Date: Wed, 4 Mar 2020 19:13:37 +0100 Subject: [PATCH] Fix #37 - Fix RFC 3164 date padding: use ` ` for padding day of month instead of using `0` --- .../com/cloudbees/syslog/SyslogMessage.java | 8 +- .../util/ConcurrentRfc3164DateFormat.java | 101 ++++++++++++++++++ .../cloudbees/syslog/SyslogMessageTest.java | 39 +++++-- .../sender/TcpSyslogMessageSenderTest.java | 26 +++-- .../util/ConcurrentRfc3164DateFormatTest.java | 47 ++++++++ 5 files changed, 203 insertions(+), 18 deletions(-) create mode 100644 src/main/java/com/cloudbees/syslog/util/ConcurrentRfc3164DateFormat.java create mode 100644 src/test/java/com/cloudbees/syslog/util/ConcurrentRfc3164DateFormatTest.java diff --git a/src/main/java/com/cloudbees/syslog/SyslogMessage.java b/src/main/java/com/cloudbees/syslog/SyslogMessage.java index 6d07d13..62b10f0 100755 --- a/src/main/java/com/cloudbees/syslog/SyslogMessage.java +++ b/src/main/java/com/cloudbees/syslog/SyslogMessage.java @@ -34,6 +34,7 @@ import com.cloudbees.syslog.util.CachingReference; import com.cloudbees.syslog.util.ConcurrentDateFormat; +import com.cloudbees.syslog.util.ConcurrentRfc3164DateFormat; /** * Syslog message as defined in RFC 5424 - The Syslog Protocol. @@ -48,7 +49,7 @@ public class SyslogMessage { private final static int DEFAULT_CONCURRENCY = 50; protected final static ConcurrentDateFormat rfc3339DateFormat; - protected final static ConcurrentDateFormat rfc3164DateFormat; + protected final static ConcurrentRfc3164DateFormat rfc3164DateFormat; private static CachingReference localhostNameReference = new CachingReference(10, TimeUnit.SECONDS) { @Override protected String newObject() { @@ -81,9 +82,8 @@ protected String newObject() { * The TIMESTAMP field is the local time and is in the format of "Mmm dd hh:mm:ss" (without the quote marks) * */ - rfc3164DateFormat = new ConcurrentDateFormat( - "MMM dd HH:mm:ss", - Locale.US, + rfc3164DateFormat = new ConcurrentRfc3164DateFormat( + Locale.getDefault(), TimeZone.getDefault(), concurrency); } diff --git a/src/main/java/com/cloudbees/syslog/util/ConcurrentRfc3164DateFormat.java b/src/main/java/com/cloudbees/syslog/util/ConcurrentRfc3164DateFormat.java new file mode 100644 index 0000000..9095bf1 --- /dev/null +++ b/src/main/java/com/cloudbees/syslog/util/ConcurrentRfc3164DateFormat.java @@ -0,0 +1,101 @@ +package com.cloudbees.syslog.util; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import javax.annotation.Nonnull; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingDeque; + +/** + * https://tools.ietf.org/html/rfc3164#section-4.1.2 + *
+ * The TIMESTAMP field is the local time and is in the format of "Mmm dd
+ *    hh:mm:ss" (without the quote marks) where:
+ *
+ *          Mmm is the English language abbreviation for the month of the
+ *          year with the first character in uppercase and the other two
+ *          characters in lowercase.  The following are the only acceptable
+ *          values:
+ *
+ *          Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec
+ *
+ *          dd is the day of the month.  If the day of the month is less
+ *          than 10, then it MUST be represented as a space and then the
+ *          number.  For example, the 7th day of August would be
+ *          represented as "Aug  7", with two spaces between the "g" and
+ *          the "7".
+ *
+ *          hh:mm:ss is the local time.  The hour (hh) is represented in a
+ *          24-hour format.  Valid entries are between 00 and 23,
+ *          inclusive.  The minute (mm) and second (ss) entries are between
+ *          00 and 59 inclusive.
+ *  
+ */ +public class ConcurrentRfc3164DateFormat { + private final BlockingQueue monthDateFormats; + private final BlockingQueue timeDateFormats; + private final Locale locale; + private final TimeZone timeZone; + + /** + * @param locale the locale whose date pattern symbols should be used + * @param timeZone the timezone used by the underlying calendar + * @param maxCacheSize + * @throws NullPointerException if the given pattern or locale is null + * @throws IllegalArgumentException if the given pattern is invalid + */ + public ConcurrentRfc3164DateFormat(Locale locale, TimeZone timeZone, int maxCacheSize) { + this.monthDateFormats = new LinkedBlockingDeque<>(maxCacheSize); + this.timeDateFormats = new LinkedBlockingDeque<>(maxCacheSize); + this.locale = locale; + this.timeZone = timeZone; + } + + /** + * Formats a Date into a date/time string. + * + * @param date the time value to be formatted into a time string. + * @return the formatted time string. + */ + @Nonnull + @SuppressFBWarnings("RV_RETURN_VALUE_IGNORED_BAD_PRACTICE") + public String format(@Nonnull Date date) { + + + // MONTH + + String month; + + + // DAY OF MONTH + GregorianCalendar calendar = new GregorianCalendar(); + calendar.setTime(date); + + int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH); + String dayofMonthPadding = dayOfMonth < 10 ? " " : ""; + + // TIME + SimpleDateFormat timeDateFormat = timeDateFormats.poll(); + if (timeDateFormat == null) { + timeDateFormat = new SimpleDateFormat("HH:mm:ss", locale); + timeDateFormat.setTimeZone(timeZone); + } + String time; + try { + time = timeDateFormat.format(date); + } finally { + timeDateFormats.offer(timeDateFormat); + } + + String result = month + ' ' + dayofMonthPadding + dayOfMonth + ' ' + time; + + return result; + } + + @Override + public String toString() { + return "ConcurrentRfc3164DateFormat"; + } +} diff --git a/src/test/java/com/cloudbees/syslog/SyslogMessageTest.java b/src/test/java/com/cloudbees/syslog/SyslogMessageTest.java index 3f615cb..8f7c40a 100755 --- a/src/test/java/com/cloudbees/syslog/SyslogMessageTest.java +++ b/src/test/java/com/cloudbees/syslog/SyslogMessageTest.java @@ -95,7 +95,6 @@ public void testRfc5424FormatWithStructuredData() throws Exception { System.out.println(SyslogMessage.rfc3339DateFormat.format(cal.getTime())); System.out.println(cal.getTimeInMillis()); - SyslogMessage message = new SyslogMessage() .withTimestamp(cal.getTimeInMillis()) .withAppName("my_app") @@ -119,15 +118,15 @@ public void testRfc5424FormatWithStructuredData() throws Exception { } @Test - public void testRfc3164Format() throws Exception { + public void testRfc3164Format_withTwoDigitsDay() throws Exception { Calendar cal = Calendar.getInstance(); cal.setTimeZone(TimeZone.getDefault()); - cal.set(2013, Calendar.DECEMBER, 5, 10, 30, 5); + cal.set(2013, Calendar.DECEMBER, 15, 10, 30, 5); cal.set(Calendar.MILLISECOND, 0); - System.out.println(SyslogMessage.rfc3339DateFormat.format(cal.getTime())); - System.out.println(cal.getTimeInMillis()); + // System.out.println(SyslogMessage.rfc3339DateFormat.format(cal.getTime())); + // System.out.println(cal.getTimeInMillis()); SyslogMessage message = new SyslogMessage() @@ -140,9 +139,37 @@ public void testRfc3164Format() throws Exception { .withMsg("a syslog message"); String actual = message.toRfc3164SyslogMessage(); - String expected = "<14>Dec 05 10:30:05 myserver.example.com my_app: a syslog message"; + String expected = "<14>Dec 15 10:30:05 myserver.example.com my_app: a syslog message"; assertThat(actual, is(expected)); + } + + /** + * https://tools.ietf.org/html/rfc3164#section-4.1.2 + */ + @Test + public void testRfc3164Format_withSingleDigitDay() throws Exception { + + Calendar cal = Calendar.getInstance(); + cal.setTimeZone(TimeZone.getDefault()); + cal.set(2013, Calendar.AUGUST, 7, 10, 30, 5); + cal.set(Calendar.MILLISECOND, 0); + + // System.out.println(SyslogMessage.rfc3339DateFormat.format(cal.getTime())); + // System.out.println(cal.getTimeInMillis()); + SyslogMessage message = new SyslogMessage() + .withTimestamp(cal.getTimeInMillis()) + .withAppName("my_app") + .withHostname("myserver.example.com") + .withFacility(Facility.USER) + .withSeverity(Severity.INFORMATIONAL) + .withTimestamp(cal.getTimeInMillis()) + .withMsg("a syslog message"); + + String actual = message.toRfc3164SyslogMessage(); + String expected = "<14>Aug 7 10:30:05 myserver.example.com my_app: a syslog message"; + + assertThat(actual, is(expected)); } } diff --git a/src/test/java/com/cloudbees/syslog/sender/TcpSyslogMessageSenderTest.java b/src/test/java/com/cloudbees/syslog/sender/TcpSyslogMessageSenderTest.java index 0889109..99edcb9 100644 --- a/src/test/java/com/cloudbees/syslog/sender/TcpSyslogMessageSenderTest.java +++ b/src/test/java/com/cloudbees/syslog/sender/TcpSyslogMessageSenderTest.java @@ -33,18 +33,23 @@ public class TcpSyslogMessageSenderTest { @Test public void send() throws Exception { TcpSyslogMessageSender messageSender = new TcpSyslogMessageSender(); - messageSender.setDefaultMessageHostname("mysecretkey"); + // messageSender.setDefaultMessageHostname("mysecretkey"); messageSender.setDefaultAppName("myapp"); messageSender.setDefaultFacility(Facility.USER); messageSender.setDefaultSeverity(Severity.INFORMATIONAL); - messageSender.setSyslogServerHostname("logs2.papertrailapp.com"); - messageSender.setSyslogServerPort(46022); + + // messageSender.setSyslogServerHostname("logs2.papertrailapp.com"); + // messageSender.setSyslogServerPort(46022); + // messageSender.setSsl(true); + + messageSender.setSyslogServerHostname("localhost"); + messageSender.setSyslogServerPort(9000); + messageSender.setMessageFormat(MessageFormat.RFC_3164); - messageSender.setSsl(true); messageSender.sendMessage("unit test message over tcp éèà " + getClass() + " - " + new Timestamp(System.currentTimeMillis())); } - @Ignore + // @Ignore @Test public void send2() throws Exception { @@ -57,10 +62,15 @@ public void send2() throws Exception { .withTimestamp(System.currentTimeMillis()); TcpSyslogMessageSender messageSender = new TcpSyslogMessageSender(); - messageSender.setSyslogServerHostname("logs2.papertrailapp.com"); - messageSender.setSyslogServerPort(46022); + + // messageSender.setSyslogServerHostname("logs2.papertrailapp.com"); + // messageSender.setSyslogServerPort(46022); + // messageSender.setSsl(true); + + messageSender.setSyslogServerHostname("localhost"); + messageSender.setSyslogServerPort(9000); + messageSender.setMessageFormat(MessageFormat.RFC_3164); - messageSender.setSsl(true); System.out.println(msg.toSyslogMessage(messageSender.getMessageFormat())); diff --git a/src/test/java/com/cloudbees/syslog/util/ConcurrentRfc3164DateFormatTest.java b/src/test/java/com/cloudbees/syslog/util/ConcurrentRfc3164DateFormatTest.java new file mode 100644 index 0000000..9f4fca6 --- /dev/null +++ b/src/test/java/com/cloudbees/syslog/util/ConcurrentRfc3164DateFormatTest.java @@ -0,0 +1,47 @@ +package com.cloudbees.syslog.util; + +import org.hamcrest.Matchers; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +public class ConcurrentRfc3164DateFormatTest { + + @Test + public void test_format_date_with_single_digit_day_of_month(){ + TimeZone timeZone = TimeZone.getTimeZone("UTC"); + Calendar cal = Calendar.getInstance(); + cal.setTimeZone(timeZone); + cal.set(2013, Calendar.AUGUST, 7, 10, 30, 5); + cal.set(Calendar.MILLISECOND, 0); + + Date singleDigitDayOfMonthDate = cal.getTime(); + + ConcurrentRfc3164DateFormat rfc3164DateFormat = new ConcurrentRfc3164DateFormat(Locale.US, timeZone, 50); + + String actual = rfc3164DateFormat.format(singleDigitDayOfMonthDate); + + Assert.assertThat(actual, Matchers.is("Aug 7 10:30:05")); + } + + @Test + public void test_format_date_with_double_digit_day_of_month(){ + TimeZone timeZone = TimeZone.getTimeZone("UTC"); + Calendar cal = Calendar.getInstance(); + cal.setTimeZone(timeZone); + cal.set(2013, Calendar.AUGUST, 17, 10, 30, 5); + cal.set(Calendar.MILLISECOND, 0); + + Date singleDigitDayOfMonthDate = cal.getTime(); + + ConcurrentRfc3164DateFormat rfc3164DateFormat = new ConcurrentRfc3164DateFormat(Locale.US, timeZone, 50); + + String actual = rfc3164DateFormat.format(singleDigitDayOfMonthDate); + + Assert.assertThat(actual, Matchers.is("Aug 17 10:30:05")); + } +}