View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  
18  package org.apache.logging.log4j.message;
19  
20  import java.util.Map;
21  
22  import org.apache.logging.log4j.util.EnglishEnums;
23  
24  /**
25   * Represents a Message that conforms to an RFC 5424 StructuredData element along with the syslog message.
26   * <p>
27   * Thread-safety note: the contents of this message can be modified after construction.
28   * When using asynchronous loggers and appenders it is not recommended to modify this message after the message is
29   * logged, because it is undefined whether the logged message string will contain the old values or the modified
30   * values.
31   * </p>
32   *
33   * @see <a href="https://tools.ietf.org/html/rfc5424">RFC 5424</a>
34   */
35  public class StructuredDataMessage extends MapMessage {
36  
37      private static final long serialVersionUID = 1703221292892071920L;
38      private static final int MAX_LENGTH = 32;
39      private static final int HASHVAL = 31;
40  
41      private StructuredDataId id;
42  
43      private String message;
44  
45      private String type;
46  
47      /**
48       * Supported formats.
49       */
50      public enum Format {
51          /** The map should be formatted as XML. */
52          XML,
53          /** Full message format includes the type and message. */
54          FULL
55      }
56  
57      /**
58       * Creates a StructuredDataMessage using an ID (max 32 characters), message, and type (max 32 characters).
59       * @param id The String id.
60       * @param msg The message.
61       * @param type The message type.
62       */
63      public StructuredDataMessage(final String id, final String msg, final String type) {
64          this.id = new StructuredDataId(id, null, null);
65          this.message = msg;
66          this.type = type;
67      }
68      /**
69       * Creates a StructuredDataMessage using an ID (max 32 characters), message, type (max 32 characters), and an
70       * initial map of structured data to include.
71       * @param id The String id.
72       * @param msg The message.
73       * @param type The message type.
74       * @param data The StructuredData map.
75       */
76      public StructuredDataMessage(final String id, final String msg, final String type,
77                                   final Map<String, String> data) {
78          super(data);
79          this.id = new StructuredDataId(id, null, null);
80          this.message = msg;
81          this.type = type;
82      }
83  
84      /**
85       * Creates a StructuredDataMessage using a StructuredDataId, message, and type (max 32 characters).
86       * @param id The StructuredDataId.
87       * @param msg The message.
88       * @param type The message type.
89       */
90      public StructuredDataMessage(final StructuredDataId id, final String msg, final String type) {
91          this.id = id;
92          this.message = msg;
93          this.type = type;
94      }
95  
96      /**
97       * Creates a StructuredDataMessage using a StructuredDataId, message, type (max 32 characters), and an initial map
98       * of structured data to include.
99       * @param id The StructuredDataId.
100      * @param msg The message.
101      * @param type The message type.
102      * @param data The StructuredData map.
103      */
104     public StructuredDataMessage(final StructuredDataId id, final String msg, final String type,
105                                  final Map<String, String> data) {
106         super(data);
107         this.id = id;
108         this.message = msg;
109         this.type = type;
110     }
111 
112 
113     /**
114      * Constructor based on a StructuredDataMessage.
115      * @param msg The StructuredDataMessage.
116      * @param map The StructuredData map.
117      */
118     private StructuredDataMessage(final StructuredDataMessage msg, final Map<String, String> map) {
119         super(map);
120         this.id = msg.id;
121         this.message = msg.message;
122         this.type = msg.type;
123     }
124 
125 
126     /**
127      * Basic constructor.
128      */
129     protected StructuredDataMessage() {
130 
131     }
132 
133     /**
134      * Returns the supported formats.
135      * @return An array of the supported format names.
136      */
137     @Override
138     public String[] getFormats() {
139         final String[] formats = new String[Format.values().length];
140         int i = 0;
141         for (final Format format : Format.values()) {
142             formats[i++] = format.name();
143         }
144         return formats;
145     }
146 
147     /**
148      * Returns this message id.
149      * @return the StructuredDataId.
150      */
151     public StructuredDataId getId() {
152         return id;
153     }
154 
155     /**
156      * Sets the id from a String. This ID can be at most 32 characters long.
157      * @param id The String id.
158      */
159     protected void setId(final String id) {
160         this.id = new StructuredDataId(id, null, null);
161     }
162 
163     /**
164      * Sets the id.
165      * @param id The StructuredDataId.
166      */
167     protected void setId(final StructuredDataId id) {
168         this.id = id;
169     }
170 
171     /**
172      * Returns this message type.
173      * @return the type.
174      */
175     public String getType() {
176         return type;
177     }
178 
179     protected void setType(final String type) {
180         if (type.length() > MAX_LENGTH) {
181             throw new IllegalArgumentException("structured data type exceeds maximum length of 32 characters: " + type);
182         }
183         this.type = type;
184     }
185 
186     /**
187      * Returns the message.
188      * @return the message.
189      */
190     @Override
191     public String getFormat() {
192         return message;
193     }
194 
195     protected void setMessageFormat(final String msg) {
196         this.message = msg;
197     }
198 
199 
200     @Override
201     protected void validate(final String key, final String value) {
202         validateKey(key);
203     }
204 
205     private void validateKey(final String key) {
206         if (key.length() > MAX_LENGTH) {
207             throw new IllegalArgumentException("Structured data keys are limited to 32 characters. key: " + key);
208         }
209         final char[] chars = key.toCharArray();
210         for (final char c : chars) {
211             if (c < '!' || c > '~' || c == '=' || c == ']' || c == '"') {
212                 throw new IllegalArgumentException("Structured data keys must contain printable US ASCII characters" +
213                         "and may not contain a space, =, ], or \"");
214             }
215         }
216     }
217 
218     /**
219      * Formats the structured data as described in RFC 5424.
220      *
221      * @return The formatted String.
222      */
223     @Override
224     public String asString() {
225         return asString(Format.FULL, null);
226     }
227 
228     /**
229      * Formats the structured data as described in RFC 5424.
230      *
231      * @param format The format identifier. Ignored in this implementation.
232      * @return The formatted String.
233      */
234 
235     @Override
236     public String asString(final String format) {
237         try {
238             return asString(EnglishEnums.valueOf(Format.class, format), null);
239         } catch (final IllegalArgumentException ex) {
240             return asString();
241         }
242     }
243 
244     /**
245      * Formats the structured data as described in RFC 5424.
246      *
247      * @param format           "full" will include the type and message. null will return only the STRUCTURED-DATA as
248      *                         described in RFC 5424
249      * @param structuredDataId The SD-ID as described in RFC 5424. If null the value in the StructuredData
250      *                         will be used.
251      * @return The formatted String.
252      */
253     public final String asString(final Format format, final StructuredDataId structuredDataId) {
254         final StringBuilder sb = new StringBuilder();
255         final boolean full = Format.FULL.equals(format);
256         if (full) {
257             final String myType = getType();
258             if (myType == null) {
259                 return sb.toString();
260             }
261             sb.append(getType()).append(' ');
262         }
263         StructuredDataId sdId = getId();
264         if (sdId != null) {
265             sdId = sdId.makeId(structuredDataId);
266         } else {
267             sdId = structuredDataId;
268         }
269         if (sdId == null || sdId.getName() == null) {
270             return sb.toString();
271         }
272         sb.append('[');
273         sb.append(sdId);
274         sb.append(' ');
275         appendMap(sb);
276         sb.append(']');
277         if (full) {
278             final String msg = getFormat();
279             if (msg != null) {
280                 sb.append(' ').append(msg);
281             }
282         }
283         return sb.toString();
284     }
285 
286     /**
287      * Formats the message and return it.
288      * @return the formatted message.
289      */
290     @Override
291     public String getFormattedMessage() {
292         return asString(Format.FULL, null);
293     }
294 
295     /**
296      * Formats the message according the the specified format.
297      * @param formats An array of Strings that provide extra information about how to format the message.
298      * StructuredDataMessage accepts only a format of "FULL" which will cause the event type to be
299      * prepended and the event message to be appended. Specifying any other value will cause only the
300      * StructuredData to be included. The default is "FULL".
301      *
302      * @return the formatted message.
303      */
304     @Override
305     public String getFormattedMessage(final String[] formats) {
306         if (formats != null && formats.length > 0) {
307             for (final String format : formats) {
308                 if (Format.XML.name().equalsIgnoreCase(format)) {
309                     return asXml();
310                 } else if (Format.FULL.name().equalsIgnoreCase(format)) {
311                     return asString(Format.FULL, null);
312                 }
313             }
314             return asString(null, null);
315         }
316         return asString(Format.FULL, null);
317     }
318 
319     private String asXml() {
320         final StringBuilder sb = new StringBuilder();
321         final StructuredDataId sdId = getId();
322         if (sdId == null || sdId.getName() == null || type == null) {
323             return sb.toString();
324         }
325         sb.append("<StructuredData>\n");
326         sb.append("<type>").append(type).append("</type>\n");
327         sb.append("<id>").append(sdId).append("</id>\n");
328         super.asXml(sb);
329         sb.append("</StructuredData>\n");
330         return sb.toString();
331     }
332 
333     @Override
334     public String toString() {
335         return asString(null, null);
336     }
337 
338 
339     @Override
340     public MapMessage newInstance(final Map<String, String> map) {
341         return new StructuredDataMessage(this, map);
342     }
343 
344     @Override
345     public boolean equals(final Object o) {
346         if (this == o) {
347             return true;
348         }
349         if (o == null || getClass() != o.getClass()) {
350             return false;
351         }
352 
353         final StructuredDataMessage that = (StructuredDataMessage) o;
354 
355         if (!super.equals(o)) {
356             return false;
357         }
358         if (type != null ? !type.equals(that.type) : that.type != null) {
359             return false;
360         }
361         if (id != null ? !id.equals(that.id) : that.id != null) {
362             return false;
363         }
364         if (message != null ? !message.equals(that.message) : that.message != null) {
365             return false;
366         }
367 
368         return true;
369     }
370 
371     @Override
372     public int hashCode() {
373         int result = super.hashCode();
374         result = HASHVAL * result + (type != null ? type.hashCode() : 0);
375         result = HASHVAL * result + (id != null ? id.hashCode() : 0);
376         result = HASHVAL * result + (message != null ? message.hashCode() : 0);
377         return result;
378     }
379 }