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