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; 023import org.apache.logging.log4j.util.MultiFormatStringBuilderFormattable; 024import org.apache.logging.log4j.util.StringBuilders; 025 026/** 027 * Represents a Message that conforms to an RFC 5424 StructuredData element along with the syslog message. 028 * <p> 029 * Thread-safety note: the contents of this message can be modified after construction. 030 * When using asynchronous loggers and appenders it is not recommended to modify this message after the message is 031 * logged, because it is undefined whether the logged message string will contain the old values or the modified 032 * values. 033 * </p> 034 * 035 * @see <a href="https://tools.ietf.org/html/rfc5424">RFC 5424</a> 036 */ 037@AsynchronouslyFormattable 038public class StructuredDataMessage extends MapMessage<StructuredDataMessage, String> implements MultiFormatStringBuilderFormattable { 039 040 private static final long serialVersionUID = 1703221292892071920L; 041 private static final int MAX_LENGTH = 32; 042 private static final int HASHVAL = 31; 043 044 private StructuredDataId id; 045 046 private String message; 047 048 private String type; 049 050 private final int maxLength; 051 052 /** 053 * Supported formats. 054 */ 055 public enum Format { 056 /** The map should be formatted as XML. */ 057 XML, 058 /** Full message format includes the type and message. */ 059 FULL 060 } 061 062 /** 063 * Creates a StructuredDataMessage using an ID (max 32 characters), message, and type (max 32 characters). 064 * @param id The String id. 065 * @param msg The message. 066 * @param type The message type. 067 */ 068 public StructuredDataMessage(final String id, final String msg, final String type) { 069 this(id, msg, type, MAX_LENGTH); 070 } 071 072 /** 073 * Creates a StructuredDataMessage using an ID (user specified max characters), message, and type (user specified 074 * maximum number of characters). 075 * @param id The String id. 076 * @param msg The message. 077 * @param type The message type. 078 * @param maxLength The maximum length of keys; 079 * @since 2.9 080 */ 081 public StructuredDataMessage(final String id, final String msg, final String type, final int maxLength) { 082 this.id = new StructuredDataId(id, null, null, maxLength); 083 this.message = msg; 084 this.type = type; 085 this.maxLength = maxLength; 086 } 087 /** 088 * Creates a StructuredDataMessage using an ID (max 32 characters), message, type (max 32 characters), and an 089 * initial map of structured data to include. 090 * @param id The String id. 091 * @param msg The message. 092 * @param type The message type. 093 * @param data The StructuredData map. 094 */ 095 public StructuredDataMessage(final String id, final String msg, final String type, 096 final Map<String, String> data) { 097 this(id, msg, type, data, MAX_LENGTH); 098 } 099 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}