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 }