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 package org.apache.logging.log4j.message; 018 019 import java.util.Map; 020 021 import org.apache.logging.log4j.util.EnglishEnums; 022 023 /** 024 * Represents a Message that conforms to an RFC 5424 StructuredData element along with the syslog message. 025 * <p> 026 * Thread-safety note: the contents of this message can be modified after construction. 027 * When using asynchronous loggers and appenders it is not recommended to modify this message after the message is 028 * logged, because it is undefined whether the logged message string will contain the old values or the modified 029 * values. 030 * 031 * @see <a href="https://tools.ietf.org/html/rfc5424">RFC 5424</a> 032 */ 033 public class StructuredDataMessage extends MapMessage { 034 035 private static final long serialVersionUID = 1703221292892071920L; 036 private static final int MAX_LENGTH = 32; 037 private static final int HASHVAL = 31; 038 039 private StructuredDataId id; 040 041 private String message; 042 043 private String type; 044 045 /** 046 * Supported formats. 047 */ 048 public enum Format { 049 /** The map should be formatted as XML. */ 050 XML, 051 /** Full message format includes the type and message. */ 052 FULL 053 } 054 055 /** 056 * Constructor based on a String id. 057 * @param id The String id. 058 * @param msg The message. 059 * @param type The message type. 060 */ 061 public StructuredDataMessage(final String id, final String msg, final String type) { 062 this.id = new StructuredDataId(id, null, null); 063 this.message = msg; 064 this.type = type; 065 } 066 /** 067 * Constructor based on a String id. 068 * @param id The String id. 069 * @param msg The message. 070 * @param type The message type. 071 * @param data The StructuredData map. 072 */ 073 public StructuredDataMessage(final String id, final String msg, final String type, 074 final Map<String, String> data) { 075 super(data); 076 this.id = new StructuredDataId(id, null, null); 077 this.message = msg; 078 this.type = type; 079 } 080 081 /** 082 * Constructor based on a StructuredDataId. 083 * @param id The StructuredDataId. 084 * @param msg The message. 085 * @param type The message type. 086 */ 087 public StructuredDataMessage(final StructuredDataId id, final String msg, final String type) { 088 this.id = id; 089 this.message = msg; 090 this.type = type; 091 } 092 093 /** 094 * Constructor based on a StructuredDataId. 095 * @param id The StructuredDataId. 096 * @param msg The message. 097 * @param type The message type. 098 * @param data The StructuredData map. 099 */ 100 public StructuredDataMessage(final StructuredDataId id, final String msg, final String type, 101 final Map<String, String> data) { 102 super(data); 103 this.id = id; 104 this.message = msg; 105 this.type = type; 106 } 107 108 109 /** 110 * Constructor based on a StructuredDataMessage. 111 * @param msg The StructuredDataMessage. 112 * @param map The StructuredData map. 113 */ 114 private StructuredDataMessage(final StructuredDataMessage msg, final Map<String, String> map) { 115 super(map); 116 this.id = msg.id; 117 this.message = msg.message; 118 this.type = msg.type; 119 } 120 121 122 /** 123 * Basic constructor. 124 */ 125 protected StructuredDataMessage() { 126 127 } 128 129 /** 130 * Returns the supported formats. 131 * @return An array of the supported format names. 132 */ 133 @Override 134 public String[] getFormats() { 135 final String[] formats = new String[Format.values().length]; 136 int i = 0; 137 for (final Format format : Format.values()) { 138 formats[i++] = format.name(); 139 } 140 return formats; 141 } 142 143 /** 144 * Returns the id. 145 * @return the StructuredDataId. 146 */ 147 public StructuredDataId getId() { 148 return id; 149 } 150 151 /** 152 * Sets the id from a String. 153 * @param id The String id. 154 */ 155 protected void setId(final String id) { 156 this.id = new StructuredDataId(id, null, null); 157 } 158 159 /** 160 * Sets the id. 161 * @param id The StructuredDataId. 162 */ 163 protected void setId(final StructuredDataId id) { 164 this.id = id; 165 } 166 167 /** 168 * Sets the type. 169 * @return the type. 170 */ 171 public String getType() { 172 return type; 173 } 174 175 protected void setType(final String type) { 176 if (type.length() > MAX_LENGTH) { 177 throw new IllegalArgumentException("structured data type exceeds maximum length of 32 characters: " + type); 178 } 179 this.type = type; 180 } 181 182 /** 183 * Returns the message. 184 * @return the message. 185 */ 186 @Override 187 public String getFormat() { 188 return message; 189 } 190 191 protected void setMessageFormat(final String msg) { 192 this.message = msg; 193 } 194 195 196 @Override 197 protected void validate(final String key, final String value) { 198 validateKey(key); 199 } 200 201 private void validateKey(final String key) { 202 if (key.length() > MAX_LENGTH) { 203 throw new IllegalArgumentException("Structured data keys are limited to 32 characters. key: " + key); 204 } 205 final char[] chars = key.toCharArray(); 206 for (final char c : chars) { 207 if (c < '!' || c > '~' || c == '=' || c == ']' || c == '"') { 208 throw new IllegalArgumentException("Structured data keys must contain printable US ASCII characters" + 209 "and may not contain a space, =, ], or \""); 210 } 211 } 212 } 213 214 /** 215 * Formats the structured data as described in RFC 5424. 216 * 217 * @return The formatted String. 218 */ 219 @Override 220 public String asString() { 221 return asString(Format.FULL, null); 222 } 223 224 /** 225 * Formats the structured data as described in RFC 5424. 226 * 227 * @param format The format identifier. Ignored in this implementation. 228 * @return The formatted String. 229 */ 230 231 @Override 232 public String asString(final String format) { 233 try { 234 return asString(EnglishEnums.valueOf(Format.class, format), null); 235 } catch (final IllegalArgumentException ex) { 236 return asString(); 237 } 238 } 239 240 /** 241 * Formats the structured data as described in RFC 5424. 242 * 243 * @param format "full" will include the type and message. null will return only the STRUCTURED-DATA as 244 * described in RFC 5424 245 * @param structuredDataId The SD-ID as described in RFC 5424. If null the value in the StructuredData 246 * will be used. 247 * @return The formatted String. 248 */ 249 public final String asString(final Format format, final StructuredDataId structuredDataId) { 250 final StringBuilder sb = new StringBuilder(); 251 final boolean full = Format.FULL.equals(format); 252 if (full) { 253 final String type = getType(); 254 if (type == null) { 255 return sb.toString(); 256 } 257 sb.append(getType()).append(' '); 258 } 259 StructuredDataId id = getId(); 260 if (id != null) { 261 id = id.makeId(structuredDataId); 262 } else { 263 id = structuredDataId; 264 } 265 if (id == null || id.getName() == null) { 266 return sb.toString(); 267 } 268 sb.append('['); 269 sb.append(id); 270 sb.append(' '); 271 appendMap(sb); 272 sb.append(']'); 273 if (full) { 274 final String msg = getFormat(); 275 if (msg != null) { 276 sb.append(' ').append(msg); 277 } 278 } 279 return sb.toString(); 280 } 281 282 /** 283 * Formats the message and return it. 284 * @return the formatted message. 285 */ 286 @Override 287 public String getFormattedMessage() { 288 return asString(Format.FULL, null); 289 } 290 291 /** 292 * Formats the message according the the specified format. 293 * @param formats An array of Strings that provide extra information about how to format the message. 294 * StructuredDataMessage accepts only a format of "FULL" which will cause the event type to be 295 * prepended and the event message to be appended. Specifying any other value will cause only the 296 * StructuredData to be included. The default is "FULL". 297 * 298 * @return the formatted message. 299 */ 300 @Override 301 public String getFormattedMessage(final String[] formats) { 302 if (formats != null && formats.length > 0) { 303 for (final String format : formats) { 304 if (Format.XML.name().equalsIgnoreCase(format)) { 305 return asXML(); 306 } else if (Format.FULL.name().equalsIgnoreCase(format)) { 307 return asString(Format.FULL, null); 308 } 309 } 310 return asString(null, null); 311 } 312 return asString(Format.FULL, null); 313 } 314 315 private String asXML() { 316 final StringBuilder sb = new StringBuilder(); 317 final StructuredDataId id = getId(); 318 if (id == null || id.getName() == null || type == null) { 319 return sb.toString(); 320 } 321 sb.append("<StructuredData>\n"); 322 sb.append("<type>").append(type).append("</type>\n"); 323 sb.append("<id>").append(id).append("</id>\n"); 324 super.asXml(sb); 325 sb.append("</StructuredData>\n"); 326 return sb.toString(); 327 } 328 329 @Override 330 public String toString() { 331 return asString(null, null); 332 } 333 334 335 @Override 336 public MapMessage newInstance(final Map<String, String> map) { 337 return new StructuredDataMessage(this, map); 338 } 339 340 @Override 341 public boolean equals(final Object o) { 342 if (this == o) { 343 return true; 344 } 345 if (o == null || getClass() != o.getClass()) { 346 return false; 347 } 348 349 final StructuredDataMessage that = (StructuredDataMessage) o; 350 351 if (!super.equals(o)) { 352 return false; 353 } 354 if (type != null ? !type.equals(that.type) : that.type != null) { 355 return false; 356 } 357 if (id != null ? !id.equals(that.id) : that.id != null) { 358 return false; 359 } 360 if (message != null ? !message.equals(that.message) : that.message != null) { 361 return false; 362 } 363 364 return true; 365 } 366 367 @Override 368 public int hashCode() { 369 int result = super.hashCode(); 370 result = HASHVAL * result + (type != null ? type.hashCode() : 0); 371 result = HASHVAL * result + (id != null ? id.hashCode() : 0); 372 result = HASHVAL * result + (message != null ? message.hashCode() : 0); 373 return result; 374 } 375 }