001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017 018package org.apache.logging.log4j.message; 019 020import java.util.Map; 021 022import org.apache.logging.log4j.util.EnglishEnums; 023 024/** 025 * Represents a Message that conforms to an RFC 5424 StructuredData element along with the syslog message. 026 * <p> 027 * Thread-safety note: the contents of this message can be modified after construction. 028 * When using asynchronous loggers and appenders it is not recommended to modify this message after the message is 029 * logged, because it is undefined whether the logged message string will contain the old values or the modified 030 * values. 031 * </p> 032 * 033 * @see <a href="https://tools.ietf.org/html/rfc5424">RFC 5424</a> 034 */ 035public class StructuredDataMessage extends MapMessage { 036 037 private static final long serialVersionUID = 1703221292892071920L; 038 private static final int MAX_LENGTH = 32; 039 private static final int HASHVAL = 31; 040 041 private StructuredDataId id; 042 043 private String message; 044 045 private String type; 046 047 /** 048 * Supported formats. 049 */ 050 public enum Format { 051 /** The map should be formatted as XML. */ 052 XML, 053 /** Full message format includes the type and message. */ 054 FULL 055 } 056 057 /** 058 * Creates a StructuredDataMessage using an ID (max 32 characters), message, and type (max 32 characters). 059 * @param id The String id. 060 * @param msg The message. 061 * @param type The message type. 062 */ 063 public StructuredDataMessage(final String id, final String msg, final String type) { 064 this.id = new StructuredDataId(id, null, null); 065 this.message = msg; 066 this.type = type; 067 } 068 /** 069 * Creates a StructuredDataMessage using an ID (max 32 characters), message, type (max 32 characters), and an 070 * initial map of structured data to include. 071 * @param id The String id. 072 * @param msg The message. 073 * @param type The message type. 074 * @param data The StructuredData map. 075 */ 076 public StructuredDataMessage(final String id, final String msg, final String type, 077 final Map<String, String> data) { 078 super(data); 079 this.id = new StructuredDataId(id, null, null); 080 this.message = msg; 081 this.type = type; 082 } 083 084 /** 085 * Creates a StructuredDataMessage using a StructuredDataId, message, and type (max 32 characters). 086 * @param id The StructuredDataId. 087 * @param msg The message. 088 * @param type The message type. 089 */ 090 public StructuredDataMessage(final StructuredDataId id, final String msg, final String type) { 091 this.id = id; 092 this.message = msg; 093 this.type = type; 094 } 095 096 /** 097 * Creates a StructuredDataMessage using a StructuredDataId, message, type (max 32 characters), and an initial map 098 * of structured data to include. 099 * @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}