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 * 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}