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.StringBuilders; 024 025/** 026 * Represents a Message that conforms to an RFC 5424 StructuredData element along with the syslog message. 027 * <p> 028 * Thread-safety note: the contents of this message can be modified after construction. 029 * When using asynchronous loggers and appenders it is not recommended to modify this message after the message is 030 * logged, because it is undefined whether the logged message string will contain the old values or the modified 031 * values. 032 * </p> 033 * 034 * @see <a href="https://tools.ietf.org/html/rfc5424">RFC 5424</a> 035 */ 036@AsynchronouslyFormattable 037public class StructuredDataMessage extends MapMessage<StructuredDataMessage, String> { 038 039 private static final long serialVersionUID = 1703221292892071920L; 040 private static final int MAX_LENGTH = 32; 041 private static final int HASHVAL = 31; 042 043 private StructuredDataId id; 044 045 private String message; 046 047 private String type; 048 049 private final int maxLength; 050 051 /** 052 * Supported formats. 053 */ 054 public enum Format { 055 /** The map should be formatted as XML. */ 056 XML, 057 /** Full message format includes the type and message. */ 058 FULL 059 } 060 061 /** 062 * Creates a StructuredDataMessage using an ID (max 32 characters), message, and type (max 32 characters). 063 * @param id The String id. 064 * @param msg The message. 065 * @param type The message type. 066 */ 067 public StructuredDataMessage(final String id, final String msg, final String type) { 068 this(id, msg, type, MAX_LENGTH); 069 } 070 071 /** 072 * Creates a StructuredDataMessage using an ID (user specified max characters), message, and type (user specified 073 * maximum number of characters). 074 * @param id The String id. 075 * @param msg The message. 076 * @param type The message type. 077 * @param maxLength The maximum length of keys; 078 * @since 2.9 079 */ 080 public StructuredDataMessage(final String id, final String msg, final String type, final int maxLength) { 081 this.id = new StructuredDataId(id, null, null, maxLength); 082 this.message = msg; 083 this.type = type; 084 this.maxLength = maxLength; 085 } 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 * Basic constructor. 192 */ 193 protected StructuredDataMessage() { 194 maxLength = MAX_LENGTH; 195 } 196 197 /** 198 * Returns the supported formats. 199 * @return An array of the supported format names. 200 */ 201 @Override 202 public String[] getFormats() { 203 final String[] formats = new String[Format.values().length]; 204 int i = 0; 205 for (final Format format : Format.values()) { 206 formats[i++] = format.name(); 207 } 208 return formats; 209 } 210 211 /** 212 * Returns this message id. 213 * @return the StructuredDataId. 214 */ 215 public StructuredDataId getId() { 216 return id; 217 } 218 219 /** 220 * Sets the id from a String. This ID can be at most 32 characters long. 221 * @param id The String id. 222 */ 223 protected void setId(final String id) { 224 this.id = new StructuredDataId(id, null, null); 225 } 226 227 /** 228 * Sets the id. 229 * @param id The StructuredDataId. 230 */ 231 protected void setId(final StructuredDataId id) { 232 this.id = id; 233 } 234 235 /** 236 * Returns this message type. 237 * @return the type. 238 */ 239 public String getType() { 240 return type; 241 } 242 243 protected void setType(final String type) { 244 if (type.length() > MAX_LENGTH) { 245 throw new IllegalArgumentException("structured data type exceeds maximum length of 32 characters: " + type); 246 } 247 this.type = type; 248 } 249 250 @Override 251 public void formatTo(final StringBuilder buffer) { 252 asString(Format.FULL, null, buffer); 253 } 254 255 @Override 256 public void formatTo(final String[] formats, final StringBuilder buffer) { 257 asString(getFormat(formats), null, buffer); 258 } 259 260 /** 261 * Returns the message. 262 * @return the message. 263 */ 264 @Override 265 public String getFormat() { 266 return message; 267 } 268 269 protected void setMessageFormat(final String msg) { 270 this.message = msg; 271 } 272 273 /** 274 * Formats the structured data as described in RFC 5424. 275 * 276 * @return The formatted String. 277 */ 278 @Override 279 public String asString() { 280 return asString(Format.FULL, null); 281 } 282 283 /** 284 * Formats the structured data as described in RFC 5424. 285 * 286 * @param format The format identifier. Ignored in this implementation. 287 * @return The formatted String. 288 */ 289 290 @Override 291 public String asString(final String format) { 292 try { 293 return asString(EnglishEnums.valueOf(Format.class, format), null); 294 } catch (final IllegalArgumentException ex) { 295 return asString(); 296 } 297 } 298 299 /** 300 * Formats the structured data as described in RFC 5424. 301 * 302 * @param format "full" will include the type and message. null will return only the STRUCTURED-DATA as 303 * described in RFC 5424 304 * @param structuredDataId The SD-ID as described in RFC 5424. If null the value in the StructuredData 305 * will be used. 306 * @return The formatted String. 307 */ 308 public final String asString(final Format format, final StructuredDataId structuredDataId) { 309 final StringBuilder sb = new StringBuilder(); 310 asString(format, structuredDataId, sb); 311 return sb.toString(); 312 } 313 314 /** 315 * Formats the structured data as described in RFC 5424. 316 * 317 * @param format "full" will include the type and message. null will return only the STRUCTURED-DATA as 318 * described in RFC 5424 319 * @param structuredDataId The SD-ID as described in RFC 5424. If null the value in the StructuredData 320 * will be used. 321 * @param sb The StringBuilder to append the formatted message to. 322 */ 323 public final void asString(final Format format, final StructuredDataId structuredDataId, final StringBuilder sb) { 324 final boolean full = Format.FULL.equals(format); 325 if (full) { 326 final String myType = getType(); 327 if (myType == null) { 328 return; 329 } 330 sb.append(getType()).append(' '); 331 } 332 StructuredDataId sdId = getId(); 333 if (sdId != null) { 334 sdId = sdId.makeId(structuredDataId); // returns sdId if structuredDataId is null 335 } else { 336 sdId = structuredDataId; 337 } 338 if (sdId == null || sdId.getName() == null) { 339 return; 340 } 341 if (Format.XML.equals(format)) { 342 asXml(sdId, sb); 343 return; 344 } 345 sb.append('['); 346 StringBuilders.appendValue(sb, sdId); // avoids toString if implements StringBuilderFormattable 347 sb.append(' '); 348 appendMap(sb); 349 sb.append(']'); 350 if (full) { 351 final String msg = getFormat(); 352 if (msg != null) { 353 sb.append(' ').append(msg); 354 } 355 } 356 } 357 358 private void asXml(final StructuredDataId structuredDataId, final StringBuilder sb) { 359 sb.append("<StructuredData>\n"); 360 sb.append("<type>").append(type).append("</type>\n"); 361 sb.append("<id>").append(structuredDataId).append("</id>\n"); 362 super.asXml(sb); 363 sb.append("\n</StructuredData>\n"); 364 } 365 366 /** 367 * Formats the message and return it. 368 * @return the formatted message. 369 */ 370 @Override 371 public String getFormattedMessage() { 372 return asString(Format.FULL, null); 373 } 374 375 /** 376 * Formats the message according to the specified format. 377 * @param formats An array of Strings that provide extra information about how to format the message. 378 * StructuredDataMessage accepts only a format of "FULL" which will cause the event type to be 379 * prepended and the event message to be appended. Specifying any other value will cause only the 380 * StructuredData to be included. The default is "FULL". 381 * 382 * @return the formatted message. 383 */ 384 @Override 385 public String getFormattedMessage(final String[] formats) { 386 return asString(getFormat(formats), null); 387 } 388 389 private Format getFormat(final String[] formats) { 390 if (formats != null && formats.length > 0) { 391 for (int i = 0; i < formats.length; i++) { 392 final String format = formats[i]; 393 if (Format.XML.name().equalsIgnoreCase(format)) { 394 return Format.XML; 395 } else if (Format.FULL.name().equalsIgnoreCase(format)) { 396 return Format.FULL; 397 } 398 } 399 return null; 400 } 401 return Format.FULL; 402 } 403 404 @Override 405 public String toString() { 406 return asString(null, null); 407 } 408 409 410 @Override 411 public StructuredDataMessage newInstance(final Map<String, String> map) { 412 return new StructuredDataMessage(this, map); 413 } 414 415 @Override 416 public boolean equals(final Object o) { 417 if (this == o) { 418 return true; 419 } 420 if (o == null || getClass() != o.getClass()) { 421 return false; 422 } 423 424 final StructuredDataMessage that = (StructuredDataMessage) o; 425 426 if (!super.equals(o)) { 427 return false; 428 } 429 if (type != null ? !type.equals(that.type) : that.type != null) { 430 return false; 431 } 432 if (id != null ? !id.equals(that.id) : that.id != null) { 433 return false; 434 } 435 if (message != null ? !message.equals(that.message) : that.message != null) { 436 return false; 437 } 438 439 return true; 440 } 441 442 @Override 443 public int hashCode() { 444 int result = super.hashCode(); 445 result = HASHVAL * result + (type != null ? type.hashCode() : 0); 446 result = HASHVAL * result + (id != null ? id.hashCode() : 0); 447 result = HASHVAL * result + (message != null ? message.hashCode() : 0); 448 return result; 449 } 450 451 @Override 452 protected void validate(final String key, final boolean value) { 453 validateKey(key); 454 } 455 456 /** 457 * @since 2.9 458 */ 459 @Override 460 protected void validate(final String key, final byte value) { 461 validateKey(key); 462 } 463 464 /** 465 * @since 2.9 466 */ 467 @Override 468 protected void validate(final String key, final char value) { 469 validateKey(key); 470 } 471 472 /** 473 * @since 2.9 474 */ 475 @Override 476 protected void validate(final String key, final double value) { 477 validateKey(key); 478 } 479 480 /** 481 * @since 2.9 482 */ 483 @Override 484 protected void validate(final String key, final float value) { 485 validateKey(key); 486 } 487 488 /** 489 * @since 2.9 490 */ 491 @Override 492 protected void validate(final String key, final int value) { 493 validateKey(key); 494 } 495 496 /** 497 * @since 2.9 498 */ 499 @Override 500 protected void validate(final String key, final long value) { 501 validateKey(key); 502 } 503 504 /** 505 * @since 2.9 506 */ 507 @Override 508 protected void validate(final String key, final Object value) { 509 validateKey(key); 510 } 511 512 /** 513 * @since 2.9 514 */ 515 @Override 516 protected void validate(final String key, final short value) { 517 validateKey(key); 518 } 519 520 @Override 521 protected void validate(final String key, final String value) { 522 validateKey(key); 523 } 524 525 protected void validateKey(final String key) { 526 if (maxLength > 0 && key.length() > maxLength) { 527 throw new IllegalArgumentException("Structured data keys are limited to " + maxLength + 528 " characters. key: " + key); 529 } 530 for (int i = 0; i < key.length(); i++) { 531 final char c = key.charAt(i); 532 if (c < '!' || c > '~' || c == '=' || c == ']' || c == '"') { 533 throw new IllegalArgumentException("Structured data keys must contain printable US ASCII characters" + 534 "and may not contain a space, =, ], or \""); 535 } 536 } 537 } 538 539}