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