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  import org.apache.logging.log4j.util.StringBuilders;
24  
25  /**
26   * Represents a Message that conforms to an RFC 5424 StructuredData element along with the syslog message.
27   * <p>
28   * Thread-safety note: the contents of this message can be modified after construction.
29   * When using asynchronous loggers and appenders it is not recommended to modify this message after the message is
30   * logged, because it is undefined whether the logged message string will contain the old values or the modified
31   * values.
32   * </p>
33   *
34   * @see <a href="https://tools.ietf.org/html/rfc5424">RFC 5424</a>
35   */
36  @AsynchronouslyFormattable
37  public class StructuredDataMessage extends MapMessage<StructuredDataMessage, String> {
38  
39      private static final long serialVersionUID = 1703221292892071920L;
40      private static final int MAX_LENGTH = 32;
41      private static final int HASHVAL = 31;
42  
43      private StructuredDataId id;
44  
45      private String message;
46  
47      private String type;
48  
49      private final int maxLength;
50  
51      /**
52       * Supported formats.
53       */
54      public enum Format {
55          /** The map should be formatted as XML. */
56          XML,
57          /** Full message format includes the type and message. */
58          FULL
59      }
60  
61      /**
62       * Creates a StructuredDataMessage using an ID (max 32 characters), message, and type (max 32 characters).
63       * @param id The String id.
64       * @param msg The message.
65       * @param type The message type.
66       */
67      public StructuredDataMessage(final String id, final String msg, final String type) {
68          this(id, msg, type, MAX_LENGTH);
69      }
70  
71      /**
72       * Creates a StructuredDataMessage using an ID (user specified max characters), message, and type (user specified
73       * maximum number of characters).
74       * @param id The String id.
75       * @param msg The message.
76       * @param type The message type.
77       * @param maxLength The maximum length of keys;
78       * @since 2.9
79       */
80      public StructuredDataMessage(final String id, final String msg, final String type, final int maxLength) {
81          this.id = new StructuredDataId(id, null, null, maxLength);
82          this.message = msg;
83          this.type = type;
84          this.maxLength = maxLength;
85      }
86      
87      /**
88       * Creates a StructuredDataMessage using an ID (max 32 characters), message, type (max 32 characters), and an
89       * initial map of structured data to include.
90       * @param id The String id.
91       * @param msg The message.
92       * @param type The message type.
93       * @param data The StructuredData map.
94       */
95      public StructuredDataMessage(final String id, final String msg, final String type,
96                                   final Map<String, String> data) {
97          this(id, msg, type, data, MAX_LENGTH);
98      }
99  
100     /**
101      * Creates a StructuredDataMessage using an (user specified max characters), message, and type (user specified
102      * maximum number of characters, and an initial map of structured data to include.
103      * @param id The String id.
104      * @param msg The message.
105      * @param type The message type.
106      * @param data The StructuredData map.
107      * @param maxLength The maximum length of keys;
108      * @since 2.9
109      */
110     public StructuredDataMessage(final String id, final String msg, final String type,
111                                  final Map<String, String> data, final int maxLength) {
112         super(data);
113         this.id = new StructuredDataId(id, null, null, maxLength);
114         this.message = msg;
115         this.type = type;
116         this.maxLength = maxLength;
117     }
118 
119     /**
120      * Creates a StructuredDataMessage using a StructuredDataId, message, and type (max 32 characters).
121      * @param id The StructuredDataId.
122      * @param msg The message.
123      * @param type The message type.
124      */
125     public StructuredDataMessage(final StructuredDataId id, final String msg, final String type) {
126         this(id, msg, type, MAX_LENGTH);
127     }
128 
129     /**
130      * Creates a StructuredDataMessage using a StructuredDataId, message, and type (max 32 characters).
131      * @param id The StructuredDataId.
132      * @param msg The message.
133      * @param type The message type.
134      * @param maxLength The maximum length of keys;
135      * @since 2.9
136      */
137     public StructuredDataMessage(final StructuredDataId id, final String msg, final String type, final int maxLength) {
138         this.id = id;
139         this.message = msg;
140         this.type = type;
141         this.maxLength = maxLength;
142     }
143 
144     /**
145      * Creates a StructuredDataMessage using a StructuredDataId, message, type (max 32 characters), and an initial map
146      * of structured data to include.
147      * @param id The StructuredDataId.
148      * @param msg The message.
149      * @param type The message type.
150      * @param data The StructuredData map.
151      */
152     public StructuredDataMessage(final StructuredDataId id, final String msg, final String type,
153                                  final Map<String, String> data) {
154         this(id, msg, type, data, MAX_LENGTH);
155     }
156 
157     /**
158      * Creates a StructuredDataMessage using a StructuredDataId, message, type (max 32 characters), and an initial map
159      * of structured data to include.
160      * @param id The StructuredDataId.
161      * @param msg The message.
162      * @param type The message type.
163      * @param data The StructuredData map.
164      * @param maxLength The maximum length of keys;
165      * @since 2.9
166      */
167     public StructuredDataMessage(final StructuredDataId id, final String msg, final String type,
168                                  final Map<String, String> data, final int maxLength) {
169         super(data);
170         this.id = id;
171         this.message = msg;
172         this.type = type;
173         this.maxLength = maxLength;
174     }
175 
176 
177     /**
178      * Constructor based on a StructuredDataMessage.
179      * @param msg The StructuredDataMessage.
180      * @param map The StructuredData map.
181      */
182     private StructuredDataMessage(final StructuredDataMessage msg, final Map<String, String> map) {
183         super(map);
184         this.id = msg.id;
185         this.message = msg.message;
186         this.type = msg.type;
187         this.maxLength = MAX_LENGTH;
188     }
189 
190     /**
191      * Basic constructor.
192      */
193     protected StructuredDataMessage() {
194         maxLength = MAX_LENGTH;
195     }
196 
197     /**
198      * Returns the supported formats.
199      * @return An array of the supported format names.
200      */
201     @Override
202     public String[] getFormats() {
203         final String[] formats = new String[Format.values().length];
204         int i = 0;
205         for (final Format format : Format.values()) {
206             formats[i++] = format.name();
207         }
208         return formats;
209     }
210 
211     /**
212      * Returns this message id.
213      * @return the StructuredDataId.
214      */
215     public StructuredDataId getId() {
216         return id;
217     }
218 
219     /**
220      * Sets the id from a String. This ID can be at most 32 characters long.
221      * @param id The String id.
222      */
223     protected void setId(final String id) {
224         this.id = new StructuredDataId(id, null, null);
225     }
226 
227     /**
228      * Sets the id.
229      * @param id The StructuredDataId.
230      */
231     protected void setId(final StructuredDataId id) {
232         this.id = id;
233     }
234 
235     /**
236      * Returns this message type.
237      * @return the type.
238      */
239     public String getType() {
240         return type;
241     }
242 
243     protected void setType(final String type) {
244         if (type.length() > MAX_LENGTH) {
245             throw new IllegalArgumentException("structured data type exceeds maximum length of 32 characters: " + type);
246         }
247         this.type = type;
248     }
249 
250     @Override
251     public void formatTo(final StringBuilder buffer) {
252         asString(Format.FULL, null, buffer);
253     }
254 
255     @Override
256     public void formatTo(final String[] formats, final StringBuilder buffer) {
257         asString(getFormat(formats), null, buffer);
258     }
259 
260     /**
261      * Returns the message.
262      * @return the message.
263      */
264     @Override
265     public String getFormat() {
266         return message;
267     }
268 
269     protected void setMessageFormat(final String msg) {
270         this.message = msg;
271     }
272 
273     /**
274      * Formats the structured data as described in RFC 5424.
275      *
276      * @return The formatted String.
277      */
278     @Override
279     public String asString() {
280         return asString(Format.FULL, null);
281     }
282 
283     /**
284      * Formats the structured data as described in RFC 5424.
285      *
286      * @param format The format identifier. Ignored in this implementation.
287      * @return The formatted String.
288      */
289 
290     @Override
291     public String asString(final String format) {
292         try {
293             return asString(EnglishEnums.valueOf(Format.class, format), null);
294         } catch (final IllegalArgumentException ex) {
295             return asString();
296         }
297     }
298 
299     /**
300      * Formats the structured data as described in RFC 5424.
301      *
302      * @param format           "full" will include the type and message. null will return only the STRUCTURED-DATA as
303      *                         described in RFC 5424
304      * @param structuredDataId The SD-ID as described in RFC 5424. If null the value in the StructuredData
305      *                         will be used.
306      * @return The formatted String.
307      */
308     public final String asString(final Format format, final StructuredDataId structuredDataId) {
309         final StringBuilder sb = new StringBuilder();
310         asString(format, structuredDataId, sb);
311         return sb.toString();
312     }
313 
314     /**
315      * Formats the structured data as described in RFC 5424.
316      *
317      * @param format           "full" will include the type and message. null will return only the STRUCTURED-DATA as
318      *                         described in RFC 5424
319      * @param structuredDataId The SD-ID as described in RFC 5424. If null the value in the StructuredData
320      *                         will be used.
321      * @param sb The StringBuilder to append the formatted message to.
322      */
323     public final void asString(final Format format, final StructuredDataId structuredDataId, final StringBuilder sb) {
324         final boolean full = Format.FULL.equals(format);
325         if (full) {
326             final String myType = getType();
327             if (myType == null) {
328                 return;
329             }
330             sb.append(getType()).append(' ');
331         }
332         StructuredDataId sdId = getId();
333         if (sdId != null) {
334             sdId = sdId.makeId(structuredDataId); // returns sdId if structuredDataId is null
335         } else {
336             sdId = structuredDataId;
337         }
338         if (sdId == null || sdId.getName() == null) {
339             return;
340         }
341         if (Format.XML.equals(format)) {
342             asXml(sdId, sb);
343             return;
344         }
345         sb.append('[');
346         StringBuilders.appendValue(sb, sdId); // avoids toString if implements StringBuilderFormattable
347         sb.append(' ');
348         appendMap(sb);
349         sb.append(']');
350         if (full) {
351             final String msg = getFormat();
352             if (msg != null) {
353                 sb.append(' ').append(msg);
354             }
355         }
356     }
357 
358     private void asXml(final StructuredDataId structuredDataId, final StringBuilder sb) {
359         sb.append("<StructuredData>\n");
360         sb.append("<type>").append(type).append("</type>\n");
361         sb.append("<id>").append(structuredDataId).append("</id>\n");
362         super.asXml(sb);
363         sb.append("\n</StructuredData>\n");
364     }
365 
366     /**
367      * Formats the message and return it.
368      * @return the formatted message.
369      */
370     @Override
371     public String getFormattedMessage() {
372         return asString(Format.FULL, null);
373     }
374 
375     /**
376      * Formats the message according the the specified format.
377      * @param formats An array of Strings that provide extra information about how to format the message.
378      * StructuredDataMessage accepts only a format of "FULL" which will cause the event type to be
379      * prepended and the event message to be appended. Specifying any other value will cause only the
380      * StructuredData to be included. The default is "FULL".
381      *
382      * @return the formatted message.
383      */
384     @Override
385     public String getFormattedMessage(final String[] formats) {
386         return asString(getFormat(formats), null);
387     }
388 
389     private Format getFormat(final String[] formats) {
390         if (formats != null && formats.length > 0) {
391             for (int i = 0; i < formats.length; i++) {
392                 final String format = formats[i];
393                 if (Format.XML.name().equalsIgnoreCase(format)) {
394                     return Format.XML;
395                 } else if (Format.FULL.name().equalsIgnoreCase(format)) {
396                     return Format.FULL;
397                 }
398             }
399             return null;
400         }
401         return Format.FULL;
402     }
403 
404     @Override
405     public String toString() {
406         return asString(null, null);
407     }
408 
409 
410     @Override
411     public StructuredDataMessage newInstance(final Map<String, String> map) {
412         return new StructuredDataMessage(this, map);
413     }
414 
415     @Override
416     public boolean equals(final Object o) {
417         if (this == o) {
418             return true;
419         }
420         if (o == null || getClass() != o.getClass()) {
421             return false;
422         }
423 
424         final StructuredDataMessage that = (StructuredDataMessage) o;
425 
426         if (!super.equals(o)) {
427             return false;
428         }
429         if (type != null ? !type.equals(that.type) : that.type != null) {
430             return false;
431         }
432         if (id != null ? !id.equals(that.id) : that.id != null) {
433             return false;
434         }
435         if (message != null ? !message.equals(that.message) : that.message != null) {
436             return false;
437         }
438 
439         return true;
440     }
441 
442     @Override
443     public int hashCode() {
444         int result = super.hashCode();
445         result = HASHVAL * result + (type != null ? type.hashCode() : 0);
446         result = HASHVAL * result + (id != null ? id.hashCode() : 0);
447         result = HASHVAL * result + (message != null ? message.hashCode() : 0);
448         return result;
449     }
450 
451     @Override
452     protected void validate(final String key, final boolean value) {
453         validateKey(key);
454     }
455 
456     /**
457      * @since 2.9
458      */
459     @Override
460     protected void validate(final String key, final byte value) {
461         validateKey(key);
462     }
463 
464     /**
465      * @since 2.9
466      */
467     @Override
468     protected void validate(final String key, final char value) {
469         validateKey(key);
470     }
471     
472     /**
473      * @since 2.9
474      */
475     @Override
476     protected void validate(final String key, final double value) {
477         validateKey(key);
478     }
479     
480     /**
481      * @since 2.9
482      */
483     @Override
484     protected void validate(final String key, final float value) {
485         validateKey(key);
486     }
487 
488     /**
489      * @since 2.9
490      */
491     @Override
492     protected void validate(final String key, final int value) {
493         validateKey(key);
494     }
495 
496     /**
497      * @since 2.9
498      */
499     @Override
500     protected void validate(final String key, final long value) {
501         validateKey(key);
502     }
503 
504     /**
505      * @since 2.9
506      */
507     @Override
508     protected void validate(final String key, final Object value) {
509         validateKey(key);
510     }
511 
512     /**
513      * @since 2.9
514      */
515     @Override
516     protected void validate(final String key, final short value) {
517         validateKey(key);
518     }
519 
520     @Override
521     protected void validate(final String key, final String value) {
522         validateKey(key);
523     }
524 
525     protected void validateKey(final String key) {
526         if (maxLength > 0 && key.length() > maxLength) {
527             throw new IllegalArgumentException("Structured data keys are limited to " + maxLength +
528                     " characters. key: " + key);
529         }
530         for (int i = 0; i < key.length(); i++) {
531             final char c = key.charAt(i);
532             if (c < '!' || c > '~' || c == '=' || c == ']' || c == '"') {
533                 throw new IllegalArgumentException("Structured data keys must contain printable US ASCII characters" +
534                         "and may not contain a space, =, ], or \"");
535             }
536         }
537     }
538 
539 }