diff --git a/src/main/java/com/cloudbees/syslog/SDElement.java b/src/main/java/com/cloudbees/syslog/SDElement.java
new file mode 100644
index 0000000..f58f0e2
--- /dev/null
+++ b/src/main/java/com/cloudbees/syslog/SDElement.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2010-2014, CloudBees Inc.
+ *
+ * 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 com.cloudbees.syslog;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A SD-ELEMENT
+ *
+ * @author Brett Bergquist
+ */
+public class SDElement implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Reserved SD-IDs as documented in RFC-5424
+ */
+ public static final String[] RESERVED_SDID = new String[]{
+ "timeQuality",
+ "tzKnown",
+ "isSynced",
+ "syncAccuracy"
+ };
+
+ public SDElement(String sdID) {
+ validateSDID(sdID);
+ this.sdID = sdID;
+ }
+
+ public SDElement(String sdID, SDParam... sdParams) {
+ validateSDID(sdID);
+ this.sdID = sdID;
+ this.sdParams.addAll(Arrays.asList(sdParams));
+ }
+
+ private String sdID;
+
+ /**
+ * Get the value of sdID
+ *
+ * @return the value of sdID
+ */
+ public String getSdID() {
+ return sdID;
+ }
+
+ private List sdParams = new ArrayList();
+
+ /**
+ * Get the value of sdParams
+ *
+ * @return the value of sdParams
+ */
+ public List getSdParams() {
+ return sdParams;
+ }
+
+ /**
+ * Set the value of sdParams
+ *
+ * @param sdParams new value of sdParams
+ */
+ public void setSdParams(List sdParams) {
+ if (null == sdParams) {
+ throw new IllegalArgumentException("sdParams list cannot be null");
+ }
+ this.sdParams.addAll(sdParams);
+ }
+
+ /**
+ * Adds a SDParam
+ * @param paramName the PARAM-NAME
+ * @param paramValue the PARAM-VALUE
+ * @return
+ */
+ public SDElement addSDParam(String paramName, String paramValue) {
+ return addSDParam(new SDParam(paramName, paramValue));
+ }
+
+ public SDElement addSDParam(SDParam sdParam) {
+ this.sdParams.add(sdParam);
+ return this;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 7;
+ hash = 97 * hash + Objects.hashCode(this.sdID);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final SDElement other = (SDElement) obj;
+ return Objects.equals(this.sdID, other.sdID);
+ }
+
+ private void validateSDID(String sdName) {
+ if (null == sdName) {
+ throw new IllegalArgumentException("SD-ID cannot be null");
+ }
+ if (sdName.length() > 32) {
+ throw new IllegalArgumentException("SD-ID must be less than 32 characters: " + sdName);
+ }
+ if (sdName.contains("=")) {
+ throw new IllegalArgumentException("SD-ID cannot contain '='");
+ }
+ if (sdName.contains(" ")) {
+ throw new IllegalArgumentException("SD-ID cannot contain ' '");
+ }
+ if (sdName.contains("]")) {
+ throw new IllegalArgumentException("SD-ID cannot contain ']'");
+ }
+ if (sdName.contains("\"")) {
+ throw new IllegalArgumentException("SD-ID cannot contain '\"'");
+ }
+ if (! sdName.contains("@")) {
+ boolean found = false;
+ for (String rn : RESERVED_SDID) {
+ if (rn.equals(sdName)) {
+ found = true;
+ break;
+ }
+ }
+ if (! found) {
+ throw new IllegalArgumentException("SD-ID is not known registered SDID: " + sdName);
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/com/cloudbees/syslog/SDParam.java b/src/main/java/com/cloudbees/syslog/SDParam.java
new file mode 100644
index 0000000..de0527f
--- /dev/null
+++ b/src/main/java/com/cloudbees/syslog/SDParam.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2010-2014, CloudBees Inc.
+ *
+ * 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 com.cloudbees.syslog;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+/**
+ *
+ * @author Brett Bergquist
+ */
+public class SDParam implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ public SDParam(String paramName, String paramValue) {
+ validateParamName(paramName);
+ this.paramName = paramName;
+ this.paramValue = paramValue;
+ }
+
+ private String paramName;
+
+ /**
+ * Get the value of paramName
+ *
+ * @return the value of paramName
+ */
+ public String getParamName() {
+ return paramName;
+ }
+
+ /**
+ * Set the value of paramName
+ *
+ * @param paramName new value of paramName
+ */
+ public void setParamName(String paramName) {
+ validateParamName(paramName);
+ this.paramName = paramName;
+ }
+
+ private String paramValue;
+
+ /**
+ * Get the value of paramValue
+ *
+ * @return the value of paramValue
+ */
+ public String getParamValue() {
+ return paramValue;
+ }
+
+ /**
+ * Set the value of paramValue
+ *
+ * @param paramValue new value of paramValue
+ */
+ public void setParamValue(String paramValue) {
+ this.paramValue = paramValue;
+ }
+
+ private void validateParamName(String sdName) {
+ if (null == sdName) {
+ throw new IllegalArgumentException("PARAM-NAME cannot be null");
+ }
+ if (sdName.length() > 32) {
+ throw new IllegalArgumentException("PARAM-NAME must be less than 32 characters: " + sdName);
+ }
+ if (sdName.contains("=")) {
+ throw new IllegalArgumentException("PARAM-NAME cannot contain '='");
+ }
+ if (sdName.contains(" ")) {
+ throw new IllegalArgumentException("PARAM-NAME cannot contain ' '");
+ }
+ if (sdName.contains("]")) {
+ throw new IllegalArgumentException("PARAM-NAME cannot contain ']'");
+ }
+ if (sdName.contains("\"")) {
+ throw new IllegalArgumentException("PARAM-NAME cannot contain '\"'");
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 7;
+ hash = 59 * hash + Objects.hashCode(this.paramName);
+ hash = 59 * hash + Objects.hashCode(this.paramValue);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final SDParam other = (SDParam) obj;
+ if (!Objects.equals(this.paramName, other.paramName)) {
+ return false;
+ }
+ if (!Objects.equals(this.paramValue, other.paramValue)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "SDParam{" + "paramName=" + paramName + ", paramValue=" + paramValue + '}';
+ }
+
+}
diff --git a/src/main/java/com/cloudbees/syslog/SyslogMessage.java b/src/main/java/com/cloudbees/syslog/SyslogMessage.java
index 87e065c..fb499e4 100644
--- a/src/main/java/com/cloudbees/syslog/SyslogMessage.java
+++ b/src/main/java/com/cloudbees/syslog/SyslogMessage.java
@@ -27,7 +27,9 @@
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Date;
+import java.util.HashSet;
import java.util.Locale;
+import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
@@ -91,6 +93,7 @@ protected String newObject() {
private String appName;
private String procId;
private String msgId;
+ private Set sdElements;
/**
* Use a {@link java.io.CharArrayWriter} instead of a {@link String} or a {@code char[]} because middlewares like
* Apache Tomcat use {@code CharArrayWriter} and it's convenient for pooling objects.
@@ -213,6 +216,26 @@ public SyslogMessage withMsg(final String msg) {
}
});
}
+
+ public Set getSDElements() {
+ Set ssde = sdElements;
+ if (ssde == null) {
+ ssde = new HashSet(0);
+ }
+ return ssde;
+ }
+
+ public void setSDElements(Set ssde) {
+ this.sdElements = ssde;
+ }
+
+ public SyslogMessage withSDElement(SDElement sde) {
+ if (sdElements == null) {
+ sdElements = new HashSet();
+ }
+ sdElements.add(sde);
+ return this;
+ }
/**
* Generates a Syslog message complying to the RFC-5424 format
@@ -287,7 +310,7 @@ public void toRfc5424SyslogMessage(Writer out) throws IOException {
out.write(SP);
writeNillableValue(msgId, out);// Message ID
out.write(SP);
- out.write(NILVALUE); // structured data
+ writeStructuredDataOrNillableValue(sdElements, out);
if (msg != null) {
out.write(SP);
msg.writeTo(out);
@@ -339,4 +362,53 @@ protected void writeNillableValue(@Nullable String value, @Nonnull Writer out) t
out.write(value);
}
}
+
+ protected void writeStructuredDataOrNillableValue(@Nullable Set ssde, @Nonnull Writer out) throws IOException {
+ if (ssde == null || ssde.isEmpty()) {
+ out.write(NILVALUE);
+ } else {
+ for (SDElement sde : ssde) {
+ writeSDElement(sde, out);
+ }
+ }
+ }
+
+ protected void writeSDElement(@Nonnull SDElement sde, @Nonnull Writer out) throws IOException {
+ out.write("[");
+ out.write(sde.getSdID());
+ for (SDParam sdp : sde.getSdParams()) {
+ writeSDParam(sdp, out);
+ }
+ out.write("]");
+ }
+
+ protected void writeSDParam(@Nonnull SDParam sdp, @Nonnull Writer out) throws IOException {
+ out.write(SP);
+ out.write(sdp.getParamName());
+ out.write('=');
+ out.write('"');
+ out.write(getEscapedParamValue(sdp.getParamValue()));
+ out.write('"');
+ }
+
+ protected String getEscapedParamValue(String paramValue) {
+ StringBuilder sb = new StringBuilder(paramValue.length());
+
+ for (int i = 0; i < paramValue.length(); i++) {
+ char c = paramValue.charAt(i);
+ switch (c) {
+ // Falls through
+ case '"':
+ case '\\':
+ case ']':
+ sb.append('\\');
+ break;
+ default:
+ break;
+ }
+ sb.append(c);
+ }
+
+ return sb.toString();
+ }
}
diff --git a/src/test/java/com/cloudbees/syslog/SyslogMessageTest.java b/src/test/java/com/cloudbees/syslog/SyslogMessageTest.java
index fb53cc6..b940323 100644
--- a/src/test/java/com/cloudbees/syslog/SyslogMessageTest.java
+++ b/src/test/java/com/cloudbees/syslog/SyslogMessageTest.java
@@ -56,6 +56,39 @@ public void testRfc5424Format() throws Exception {
}
+ @Test
+ public void testRfc5424FormatWithStructuredData() throws Exception {
+ Calendar cal = Calendar.getInstance();
+ cal.setTimeZone(TimeZone.getTimeZone("GMT"));
+ cal.set(2013, Calendar.DECEMBER, 5, 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")
+ .withSDElement(new SDElement("exampleSDID@32473", new SDParam("iut", "3"), new SDParam("eventSource", "Application"), new SDParam("eventID", "1011")));
+
+ String actual = message.toRfc5424SyslogMessage();
+ String expected = "<14>1 2013-12-05T10:30:05.000Z myserver.example.com my_app - - [exampleSDID@32473 iut=\"3\" eventSource=\"Application\" eventID=\"1011\"] a syslog message";
+
+ assertThat(actual, is(expected));
+
+ message.withSDElement(new SDElement("examplePriority@32473", new SDParam("class", "high")));
+ actual = message.toRfc5424SyslogMessage();
+ expected = "<14>1 2013-12-05T10:30:05.000Z myserver.example.com my_app - - [exampleSDID@32473 iut=\"3\" eventSource=\"Application\" eventID=\"1011\"][examplePriority@32473 class=\"high\"] a syslog message";
+
+ assertThat(actual, is(expected));
+ }
+
@Test
public void testRfc3164Format() throws Exception {