blob: a2ba89a96af6d3a07a5e6024b468cca60141df90 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache license, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the license for the specific language governing permissions and
* limitations under the license.
*/
package org.apache.logging.log4j.message;
import java.util.Map;
import org.apache.logging.log4j.util.EnglishEnums;
import org.apache.logging.log4j.util.StringBuilders;
/**
* Represents a Message that conforms to an RFC 5424 StructuredData element along with the syslog message.
* <p>
* Thread-safety note: the contents of this message can be modified after construction.
* When using asynchronous loggers and appenders it is not recommended to modify this message after the message is
* logged, because it is undefined whether the logged message string will contain the old values or the modified
* values.
* </p>
*
* @see <a href="https://tools.ietf.org/html/rfc5424">RFC 5424</a>
*/
@AsynchronouslyFormattable
public class StructuredDataMessage extends MapMessage<StructuredDataMessage, String> {
private static final long serialVersionUID = 1703221292892071920L;
private static final int MAX_LENGTH = 32;
private static final int HASHVAL = 31;
private StructuredDataId id;
private String message;
private String type;
private final int maxLength;
/**
* Supported formats.
*/
public enum Format {
/** The map should be formatted as XML. */
XML,
/** Full message format includes the type and message. */
FULL
}
/**
* Creates a StructuredDataMessage using an ID (max 32 characters), message, and type (max 32 characters).
* @param id The String id.
* @param msg The message.
* @param type The message type.
*/
public StructuredDataMessage(final String id, final String msg, final String type) {
this(id, msg, type, MAX_LENGTH);
}
/**
* Creates a StructuredDataMessage using an ID (user specified max characters), message, and type (user specified
* maximum number of characters).
* @param id The String id.
* @param msg The message.
* @param type The message type.
* @param maxLength The maximum length of keys;
* @since 2.9
*/
public StructuredDataMessage(final String id, final String msg, final String type, final int maxLength) {
this.id = new StructuredDataId(id, null, null, maxLength);
this.message = msg;
this.type = type;
this.maxLength = maxLength;
}
/**
* Creates a StructuredDataMessage using an ID (max 32 characters), message, type (max 32 characters), and an
* initial map of structured data to include.
* @param id The String id.
* @param msg The message.
* @param type The message type.
* @param data The StructuredData map.
*/
public StructuredDataMessage(final String id, final String msg, final String type,
final Map<String, String> data) {
this(id, msg, type, data, MAX_LENGTH);
}
/**
* Creates a StructuredDataMessage using an (user specified max characters), message, and type (user specified
* maximum number of characters, and an initial map of structured data to include.
* @param id The String id.
* @param msg The message.
* @param type The message type.
* @param data The StructuredData map.
* @param maxLength The maximum length of keys;
* @since 2.9
*/
public StructuredDataMessage(final String id, final String msg, final String type,
final Map<String, String> data, final int maxLength) {
super(data);
this.id = new StructuredDataId(id, null, null, maxLength);
this.message = msg;
this.type = type;
this.maxLength = maxLength;
}
/**
* Creates a StructuredDataMessage using a StructuredDataId, message, and type (max 32 characters).
* @param id The StructuredDataId.
* @param msg The message.
* @param type The message type.
*/
public StructuredDataMessage(final StructuredDataId id, final String msg, final String type) {
this(id, msg, type, MAX_LENGTH);
}
/**
* Creates a StructuredDataMessage using a StructuredDataId, message, and type (max 32 characters).
* @param id The StructuredDataId.
* @param msg The message.
* @param type The message type.
* @param maxLength The maximum length of keys;
* @since 2.9
*/
public StructuredDataMessage(final StructuredDataId id, final String msg, final String type, final int maxLength) {
this.id = id;
this.message = msg;
this.type = type;
this.maxLength = maxLength;
}
/**
* Creates a StructuredDataMessage using a StructuredDataId, message, type (max 32 characters), and an initial map
* of structured data to include.
* @param id The StructuredDataId.
* @param msg The message.
* @param type The message type.
* @param data The StructuredData map.
*/
public StructuredDataMessage(final StructuredDataId id, final String msg, final String type,
final Map<String, String> data) {
this(id, msg, type, data, MAX_LENGTH);
}
/**
* Creates a StructuredDataMessage using a StructuredDataId, message, type (max 32 characters), and an initial map
* of structured data to include.
* @param id The StructuredDataId.
* @param msg The message.
* @param type The message type.
* @param data The StructuredData map.
* @param maxLength The maximum length of keys;
* @since 2.9
*/
public StructuredDataMessage(final StructuredDataId id, final String msg, final String type,
final Map<String, String> data, final int maxLength) {
super(data);
this.id = id;
this.message = msg;
this.type = type;
this.maxLength = maxLength;
}
/**
* Constructor based on a StructuredDataMessage.
* @param msg The StructuredDataMessage.
* @param map The StructuredData map.
*/
private StructuredDataMessage(final StructuredDataMessage msg, final Map<String, String> map) {
super(map);
this.id = msg.id;
this.message = msg.message;
this.type = msg.type;
this.maxLength = MAX_LENGTH;
}
/**
* Basic constructor.
*/
protected StructuredDataMessage() {
maxLength = MAX_LENGTH;
}
/**
* Returns the supported formats.
* @return An array of the supported format names.
*/
@Override
public String[] getFormats() {
final String[] formats = new String[Format.values().length];
int i = 0;
for (final Format format : Format.values()) {
formats[i++] = format.name();
}
return formats;
}
/**
* Returns this message id.
* @return the StructuredDataId.
*/
public StructuredDataId getId() {
return id;
}
/**
* Sets the id from a String. This ID can be at most 32 characters long.
* @param id The String id.
*/
protected void setId(final String id) {
this.id = new StructuredDataId(id, null, null);
}
/**
* Sets the id.
* @param id The StructuredDataId.
*/
protected void setId(final StructuredDataId id) {
this.id = id;
}
/**
* Returns this message type.
* @return the type.
*/
public String getType() {
return type;
}
protected void setType(final String type) {
if (type.length() > MAX_LENGTH) {
throw new IllegalArgumentException("structured data type exceeds maximum length of 32 characters: " + type);
}
this.type = type;
}
@Override
public void formatTo(final StringBuilder buffer) {
asString(Format.FULL, null, buffer);
}
@Override
public void formatTo(final String[] formats, final StringBuilder buffer) {
asString(getFormat(formats), null, buffer);
}
/**
* Returns the message.
* @return the message.
*/
@Override
public String getFormat() {
return message;
}
protected void setMessageFormat(final String msg) {
this.message = msg;
}
/**
* Formats the structured data as described in RFC 5424.
*
* @return The formatted String.
*/
@Override
public String asString() {
return asString(Format.FULL, null);
}
/**
* Formats the structured data as described in RFC 5424.
*
* @param format The format identifier. Ignored in this implementation.
* @return The formatted String.
*/
@Override
public String asString(final String format) {
try {
return asString(EnglishEnums.valueOf(Format.class, format), null);
} catch (final IllegalArgumentException ex) {
return asString();
}
}
/**
* Formats the structured data as described in RFC 5424.
*
* @param format "full" will include the type and message. null will return only the STRUCTURED-DATA as
* described in RFC 5424
* @param structuredDataId The SD-ID as described in RFC 5424. If null the value in the StructuredData
* will be used.
* @return The formatted String.
*/
public final String asString(final Format format, final StructuredDataId structuredDataId) {
final StringBuilder sb = new StringBuilder();
asString(format, structuredDataId, sb);
return sb.toString();
}
/**
* Formats the structured data as described in RFC 5424.
*
* @param format "full" will include the type and message. null will return only the STRUCTURED-DATA as
* described in RFC 5424
* @param structuredDataId The SD-ID as described in RFC 5424. If null the value in the StructuredData
* will be used.
* @param sb The StringBuilder to append the formatted message to.
*/
public final void asString(final Format format, final StructuredDataId structuredDataId, final StringBuilder sb) {
final boolean full = Format.FULL.equals(format);
if (full) {
final String myType = getType();
if (myType == null) {
return;
}
sb.append(getType()).append(' ');
}
StructuredDataId sdId = getId();
if (sdId != null) {
sdId = sdId.makeId(structuredDataId); // returns sdId if structuredDataId is null
} else {
sdId = structuredDataId;
}
if (sdId == null || sdId.getName() == null) {
return;
}
if (Format.XML.equals(format)) {
asXml(sdId, sb);
return;
}
sb.append('[');
StringBuilders.appendValue(sb, sdId); // avoids toString if implements StringBuilderFormattable
sb.append(' ');
appendMap(sb);
sb.append(']');
if (full) {
final String msg = getFormat();
if (msg != null) {
sb.append(' ').append(msg);
}
}
}
private void asXml(final StructuredDataId structuredDataId, final StringBuilder sb) {
sb.append("<StructuredData>\n");
sb.append("<type>").append(type).append("</type>\n");
sb.append("<id>").append(structuredDataId).append("</id>\n");
super.asXml(sb);
sb.append("\n</StructuredData>\n");
}
/**
* Formats the message and return it.
* @return the formatted message.
*/
@Override
public String getFormattedMessage() {
return asString(Format.FULL, null);
}
/**
* Formats the message according to the specified format.
* @param formats An array of Strings that provide extra information about how to format the message.
* StructuredDataMessage accepts only a format of "FULL" which will cause the event type to be
* prepended and the event message to be appended. Specifying any other value will cause only the
* StructuredData to be included. The default is "FULL".
*
* @return the formatted message.
*/
@Override
public String getFormattedMessage(final String[] formats) {
return asString(getFormat(formats), null);
}
private Format getFormat(final String[] formats) {
if (formats != null && formats.length > 0) {
for (int i = 0; i < formats.length; i++) {
final String format = formats[i];
if (Format.XML.name().equalsIgnoreCase(format)) {
return Format.XML;
} else if (Format.FULL.name().equalsIgnoreCase(format)) {
return Format.FULL;
}
}
return null;
}
return Format.FULL;
}
@Override
public String toString() {
return asString(null, null);
}
@Override
public StructuredDataMessage newInstance(final Map<String, String> map) {
return new StructuredDataMessage(this, map);
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final StructuredDataMessage that = (StructuredDataMessage) o;
if (!super.equals(o)) {
return false;
}
if (type != null ? !type.equals(that.type) : that.type != null) {
return false;
}
if (id != null ? !id.equals(that.id) : that.id != null) {
return false;
}
return message != null ? message.equals(that.message) : that.message == null;
}
@Override
public int hashCode() {
int result = super.hashCode();
result = HASHVAL * result + (type != null ? type.hashCode() : 0);
result = HASHVAL * result + (id != null ? id.hashCode() : 0);
result = HASHVAL * result + (message != null ? message.hashCode() : 0);
return result;
}
@Override
protected void validate(final String key, final boolean value) {
validateKey(key);
}
/**
* @since 2.9
*/
@Override
protected void validate(final String key, final byte value) {
validateKey(key);
}
/**
* @since 2.9
*/
@Override
protected void validate(final String key, final char value) {
validateKey(key);
}
/**
* @since 2.9
*/
@Override
protected void validate(final String key, final double value) {
validateKey(key);
}
/**
* @since 2.9
*/
@Override
protected void validate(final String key, final float value) {
validateKey(key);
}
/**
* @since 2.9
*/
@Override
protected void validate(final String key, final int value) {
validateKey(key);
}
/**
* @since 2.9
*/
@Override
protected void validate(final String key, final long value) {
validateKey(key);
}
/**
* @since 2.9
*/
@Override
protected void validate(final String key, final Object value) {
validateKey(key);
}
/**
* @since 2.9
*/
@Override
protected void validate(final String key, final short value) {
validateKey(key);
}
@Override
protected void validate(final String key, final String value) {
validateKey(key);
}
protected void validateKey(final String key) {
if (maxLength > 0 && key.length() > maxLength) {
throw new IllegalArgumentException("Structured data keys are limited to " + maxLength +
" characters. key: " + key);
}
for (int i = 0; i < key.length(); i++) {
final char c = key.charAt(i);
if (c < '!' || c > '~' || c == '=' || c == ']' || c == '"') {
throw new IllegalArgumentException("Structured data keys must contain printable US ASCII characters" +
"and may not contain a space, =, ], or \"");
}
}
}
}