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      * Add an item to the data Map in fluent style.
135      * @param key The name of the data item.
136      * @param value The value of the data item.
137      * @return {@code this}
138      */
139     @Override
140     public StructuredDataMessage with(final String key, final String value) {
141         put(key, value);
142         return this;
143     }
144 
145     /**
146      * Returns the supported formats.
147      * @return An array of the supported format names.
148      */
149     @Override
150     public String[] getFormats() {
151         final String[] formats = new String[Format.values().length];
152         int i = 0;
153         for (final Format format : Format.values()) {
154             formats[i++] = format.name();
155         }
156         return formats;
157     }
158 
159     /**
160      * Returns this message id.
161      * @return the StructuredDataId.
162      */
163     public StructuredDataId getId() {
164         return id;
165     }
166 
167     /**
168      * Sets the id from a String. This ID can be at most 32 characters long.
169      * @param id The String id.
170      */
171     protected void setId(final String id) {
172         this.id = new StructuredDataId(id, null, null);
173     }
174 
175     /**
176      * Sets the id.
177      * @param id The StructuredDataId.
178      */
179     protected void setId(final StructuredDataId id) {
180         this.id = id;
181     }
182 
183     /**
184      * Returns this message type.
185      * @return the type.
186      */
187     public String getType() {
188         return type;
189     }
190 
191     protected void setType(final String type) {
192         if (type.length() > MAX_LENGTH) {
193             throw new IllegalArgumentException("structured data type exceeds maximum length of 32 characters: " + type);
194         }
195         this.type = type;
196     }
197 
198     /**
199      * Returns the message.
200      * @return the message.
201      */
202     @Override
203     public String getFormat() {
204         return message;
205     }
206 
207     protected void setMessageFormat(final String msg) {
208         this.message = msg;
209     }
210 
211 
212     @Override
213     protected void validate(final String key, final String value) {
214         validateKey(key);
215     }
216 
217     private void validateKey(final String key) {
218         if (key.length() > MAX_LENGTH) {
219             throw new IllegalArgumentException("Structured data keys are limited to 32 characters. key: " + key);
220         }
221         final char[] chars = key.toCharArray();
222         for (final char c : chars) {
223             if (c < '!' || c > '~' || c == '=' || c == ']' || c == '"') {
224                 throw new IllegalArgumentException("Structured data keys must contain printable US ASCII characters" +
225                         "and may not contain a space, =, ], or \"");
226             }
227         }
228     }
229 
230     /**
231      * Formats the structured data as described in RFC 5424.
232      *
233      * @return The formatted String.
234      */
235     @Override
236     public String asString() {
237         return asString(Format.FULL, null);
238     }
239 
240     /**
241      * Formats the structured data as described in RFC 5424.
242      *
243      * @param format The format identifier. Ignored in this implementation.
244      * @return The formatted String.
245      */
246 
247     @Override
248     public String asString(final String format) {
249         try {
250             return asString(EnglishEnums.valueOf(Format.class, format), null);
251         } catch (final IllegalArgumentException ex) {
252             return asString();
253         }
254     }
255 
256     /**
257      * Formats the structured data as described in RFC 5424.
258      *
259      * @param format           "full" will include the type and message. null will return only the STRUCTURED-DATA as
260      *                         described in RFC 5424
261      * @param structuredDataId The SD-ID as described in RFC 5424. If null the value in the StructuredData
262      *                         will be used.
263      * @return The formatted String.
264      */
265     public final String asString(final Format format, final StructuredDataId structuredDataId) {
266         final StringBuilder sb = new StringBuilder();
267         final boolean full = Format.FULL.equals(format);
268         if (full) {
269             final String myType = getType();
270             if (myType == null) {
271                 return sb.toString();
272             }
273             sb.append(getType()).append(' ');
274         }
275         StructuredDataId sdId = getId();
276         if (sdId != null) {
277             sdId = sdId.makeId(structuredDataId);
278         } else {
279             sdId = structuredDataId;
280         }
281         if (sdId == null || sdId.getName() == null) {
282             return sb.toString();
283         }
284         sb.append('[');
285         sb.append(sdId);
286         sb.append(' ');
287         appendMap(sb);
288         sb.append(']');
289         if (full) {
290             final String msg = getFormat();
291             if (msg != null) {
292                 sb.append(' ').append(msg);
293             }
294         }
295         return sb.toString();
296     }
297 
298     /**
299      * Formats the message and return it.
300      * @return the formatted message.
301      */
302     @Override
303     public String getFormattedMessage() {
304         return asString(Format.FULL, null);
305     }
306 
307     /**
308      * Formats the message according the the specified format.
309      * @param formats An array of Strings that provide extra information about how to format the message.
310      * StructuredDataMessage accepts only a format of "FULL" which will cause the event type to be
311      * prepended and the event message to be appended. Specifying any other value will cause only the
312      * StructuredData to be included. The default is "FULL".
313      *
314      * @return the formatted message.
315      */
316     @Override
317     public String getFormattedMessage(final String[] formats) {
318         if (formats != null && formats.length > 0) {
319             for (final String format : formats) {
320                 if (Format.XML.name().equalsIgnoreCase(format)) {
321                     return asXml();
322                 } else if (Format.FULL.name().equalsIgnoreCase(format)) {
323                     return asString(Format.FULL, null);
324                 }
325             }
326             return asString(null, null);
327         }
328         return asString(Format.FULL, null);
329     }
330 
331     private String asXml() {
332         final StringBuilder sb = new StringBuilder();
333         final StructuredDataId sdId = getId();
334         if (sdId == null || sdId.getName() == null || type == null) {
335             return sb.toString();
336         }
337         sb.append("<StructuredData>\n");
338         sb.append("<type>").append(type).append("</type>\n");
339         sb.append("<id>").append(sdId).append("</id>\n");
340         super.asXml(sb);
341         sb.append("</StructuredData>\n");
342         return sb.toString();
343     }
344 
345     @Override
346     public String toString() {
347         return asString(null, null);
348     }
349 
350 
351     @Override
352     public MapMessage newInstance(final Map<String, String> map) {
353         return new StructuredDataMessage(this, map);
354     }
355 
356     @Override
357     public boolean equals(final Object o) {
358         if (this == o) {
359             return true;
360         }
361         if (o == null || getClass() != o.getClass()) {
362             return false;
363         }
364 
365         final StructuredDataMessage that = (StructuredDataMessage) o;
366 
367         if (!super.equals(o)) {
368             return false;
369         }
370         if (type != null ? !type.equals(that.type) : that.type != null) {
371             return false;
372         }
373         if (id != null ? !id.equals(that.id) : that.id != null) {
374             return false;
375         }
376         if (message != null ? !message.equals(that.message) : that.message != null) {
377             return false;
378         }
379 
380         return true;
381     }
382 
383     @Override
384     public int hashCode() {
385         int result = super.hashCode();
386         result = HASHVAL * result + (type != null ? type.hashCode() : 0);
387         result = HASHVAL * result + (id != null ? id.hashCode() : 0);
388         result = HASHVAL * result + (message != null ? message.hashCode() : 0);
389         return result;
390     }
391 }