/*
* 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.core.impl;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.ThreadContext;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.util.Clock;
import org.apache.logging.log4j.core.util.ClockFactory;
import org.apache.logging.log4j.message.LoggerNameAwareMessage;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.TimestampMessage;
import org.apache.logging.log4j.util.Strings;
/**
* Implementation of a LogEvent.
*/
public class Log4jLogEvent implements LogEvent {
private static final long serialVersionUID = -1351367343806656055L;
private static final Clock clock = ClockFactory.getClock();
private final String loggerFqcn;
private final Marker marker;
private final Level level;
private final String loggerName;
private final Message message;
private final long timeMillis;
private transient final Throwable thrown;
private ThrowableProxy thrownProxy;
private final Map<String, String> contextMap;
private final ThreadContext.ContextStack contextStack;
private String threadName = null;
private StackTraceElement source;
private boolean includeLocation;
private boolean endOfBatch = false;
public Log4jLogEvent() {
this(clock.currentTimeMillis());
}
/**
*
*/
public Log4jLogEvent(final long timestamp) {
this(Strings.EMPTY, null, Strings.EMPTY, null, null, (Throwable) null, null, null, null, null, timestamp);
}
/**
* Constructor.
* @param loggerName The name of the Logger.
* @param marker The Marker or null.
* @param loggerFQCN The fully qualified class name of the caller.
* @param level The logging Level.
* @param message The Message.
* @param t A Throwable or null.
*/
public Log4jLogEvent(final String loggerName, final Marker marker, final String loggerFQCN, final Level level,
final Message message, final Throwable t) {
this(loggerName, marker, loggerFQCN, level, message, null, t);
}
/**
* Constructor.
* @param loggerName The name of the Logger.
* @param marker The Marker or null.
* @param loggerFQCN The fully qualified class name of the caller.
* @param level The logging Level.
* @param message The Message.
* @param properties properties to add to the event.
* @param t A Throwable or null.
*/
public Log4jLogEvent(final String loggerName, final Marker marker, final String loggerFQCN, final Level level,
final Message message, final List<Property> properties, final Throwable t) {
this(loggerName, marker, loggerFQCN, level, message, t,
createMap(properties),
ThreadContext.getDepth() == 0 ? null : ThreadContext.cloneStack(), null,
null,
// LOG4J2-628 use log4j.Clock for timestamps
// LOG4J2-744 unless TimestampMessage already has one
message instanceof TimestampMessage ? ((TimestampMessage) message).getTimestamp() :
clock.currentTimeMillis());
}
/**
* Constructor.
* @param loggerName The name of the Logger.
* @param marker The Marker or null.
* @param loggerFQCN The fully qualified class name of the caller.
* @param level The logging Level.
* @param message The Message.
* @param t A Throwable or null.
* @param mdc The mapped diagnostic context.
* @param ndc the nested diagnostic context.
* @param threadName The name of the thread.
* @param location The locations of the caller.
* @param timestamp The timestamp of the event.
*/
public Log4jLogEvent(final String loggerName, final Marker marker, final String loggerFQCN, final Level level,
final Message message, final Throwable t, final Map<String, String> mdc,
final ThreadContext.ContextStack ndc, final String threadName,
final StackTraceElement location, final long timestamp) {
this(loggerName, marker, loggerFQCN, level, message, t, null, mdc, ndc, threadName,
location, timestamp);
}
/**
* Create a new LogEvent.
* @param loggerName The name of the Logger.
* @param marker The Marker or null.
* @param loggerFQCN The fully qualified class name of the caller.
* @param level The logging Level.
* @param message The Message.
* @param thrown A Throwable or null.
* @param thrownProxy A ThrowableProxy or null.
* @param mdc The mapped diagnostic context.
* @param ndc the nested diagnostic context.
* @param threadName The name of the thread.
* @param location The locations of the caller.
* @param timestamp The timestamp of the event.
*/
public static Log4jLogEvent createEvent(final String loggerName, final Marker marker, final String loggerFQCN,
final Level level, final Message message, final Throwable thrown,
final ThrowableProxy thrownProxy,
final Map<String, String> mdc, final ThreadContext.ContextStack ndc,
final String threadName, final StackTraceElement location,
final long timestamp) {
final Log4jLogEvent result = new Log4jLogEvent(loggerName, marker, loggerFQCN, level, message, thrown,
thrownProxy, mdc, ndc, threadName, location, timestamp);
return result;
}
/**
* Constructor.
* @param loggerName The name of the Logger.
* @param marker The Marker or null.
* @param loggerFQCN The fully qualified class name of the caller.
* @param level The logging Level.
* @param message The Message.
* @param thrown A Throwable or null.
* @param thrownProxy A ThrowableProxy or null.
* @param contextMap The mapped diagnostic context.
* @param contextStack the nested diagnostic context.
* @param threadName The name of the thread.
* @param source The locations of the caller.
* @param timestamp The timestamp of the event.
*/
private Log4jLogEvent(final String loggerName, final Marker marker, final String loggerFQCN, final Level level,
final Message message, final Throwable thrown, final ThrowableProxy thrownProxy,
final Map<String, String> contextMap, final ThreadContext.ContextStack contextStack,
final String threadName, final StackTraceElement source, final long timestamp) {
this.loggerName = loggerName;
this.marker = marker;
this.loggerFqcn = loggerFQCN;
this.level = (level == null) ? Level.OFF : level; // LOG4J2-462, LOG4J2-465
this.message = message;
this.thrown = thrown;
this.thrownProxy = thrownProxy;
this.contextMap = contextMap == null ? ThreadContext.EMPTY_MAP : contextMap;
this.contextStack = contextStack == null ? ThreadContext.EMPTY_STACK : contextStack;
this.timeMillis = message instanceof TimestampMessage ? ((TimestampMessage) message).getTimestamp() : timestamp;
this.threadName = threadName;
this.source = source;
if (message != null && message instanceof LoggerNameAwareMessage) {
((LoggerNameAwareMessage) message).setLoggerName(loggerName);
}
}
private static Map<String, String> createMap(final List<Property> properties) {
final Map<String, String> contextMap = ThreadContext.getImmutableContext();
if (contextMap == null && (properties == null || properties.isEmpty())) {
return null;
}
if (properties == null || properties.isEmpty()) {
return contextMap; // contextMap is not null
}
final Map<String, String> map = new HashMap<String, String>(contextMap);
for (final Property prop : properties) {
if (!map.containsKey(prop.getName())) {
map.put(prop.getName(), prop.getValue());
}
}
return Collections.unmodifiableMap(map);
}
/**
* Returns the logging Level.
* @return the Level associated with this event.
*/
@Override
public Level getLevel() {
return level;
}
/**
* Returns the name of the Logger used to generate the event.
* @return The Logger name.
*/
@Override
public String getLoggerName() {
return loggerName;
}
/**
* Returns the Message associated with the event.
* @return The Message.
*/
@Override
public Message getMessage() {
return message;
}
/**
* Returns the name of the Thread on which the event was generated.
* @return The name of the Thread.
*/
@Override
public String getThreadName() {
if (threadName == null) {
threadName = Thread.currentThread().getName();
}
return threadName;
}
/**
* Returns the time in milliseconds from the epoch when the event occurred.
* @return The time the event occurred.
*/
@Override
public long getTimeMillis() {
return timeMillis;
}
/**
* Returns the Throwable associated with the event, or null.
* @return The Throwable associated with the event.
*/
@Override
public Throwable getThrown() {
return thrown;
}
/**
* Returns the ThrowableProxy associated with the event, or null.
* @return The ThrowableProxy associated with the event.
*/
@Override
public ThrowableProxy getThrownProxy() {
if (thrownProxy == null && thrown != null) {
thrownProxy = new ThrowableProxy(thrown);
}
return thrownProxy;
}
/**
* Returns the Marker associated with the event, or null.
* @return the Marker associated with the event.
*/
@Override
public Marker getMarker() {
return marker;
}
/**
* The fully qualified class name of the class that was called by the caller.
* @return the fully qualified class name of the class that is performing logging.
*/
@Override
public String getLoggerFqcn() {
return loggerFqcn;
}
/**
* Returns the immutable copy of the ThreadContext Map.
* @return The context Map.
*/
@Override
public Map<String, String> getContextMap() {
return contextMap;
}
/**
* Returns an immutable copy of the ThreadContext stack.
* @return The context Stack.
*/
@Override
public ThreadContext.ContextStack getContextStack() {
return contextStack;
}
/**
* Returns the StackTraceElement for the caller. This will be the entry that occurs right
* before the first occurrence of FQCN as a class name.
* @return the StackTraceElement for the caller.
*/
@Override
public StackTraceElement getSource() {
if (source != null) {
return source;
}
if (loggerFqcn == null || !includeLocation) {
return null;
}
source = calcLocation(loggerFqcn);
return source;
}
public static StackTraceElement calcLocation(final String fqcnOfLogger) {
if (fqcnOfLogger == null) {
return null;
}
final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
StackTraceElement last = null;
for (int i = stackTrace.length - 1; i > 0; i--) {
final String className = stackTrace[i].getClassName();
if (fqcnOfLogger.equals(className)) {
return last;
}
last = stackTrace[i];
}
return null;
}
@Override
public boolean isIncludeLocation() {
return includeLocation;
}
@Override
public void setIncludeLocation(final boolean includeLocation) {
this.includeLocation = includeLocation;
}
@Override
public boolean isEndOfBatch() {
return endOfBatch;
}
@Override
public void setEndOfBatch(final boolean endOfBatch) {
this.endOfBatch = endOfBatch;
}
/**
* Creates a LogEventProxy that can be serialized.
* @return a LogEventProxy.
*/
protected Object writeReplace() {
getThrownProxy(); // ensure ThrowableProxy is initialized
return new LogEventProxy(this, this.includeLocation);
}
public static Serializable serialize(final Log4jLogEvent event,
final boolean includeLocation) {
event.getThrownProxy(); // ensure ThrowableProxy is initialized
return new LogEventProxy(event, includeLocation);
}
public static boolean canDeserialize(final Serializable event) {
return event instanceof LogEventProxy;
}
public static Log4jLogEvent deserialize(final Serializable event) {
if (event == null) {
throw new NullPointerException("Event cannot be null");
}
if (event instanceof LogEventProxy) {
final LogEventProxy proxy = (LogEventProxy) event;
final Log4jLogEvent result = new Log4jLogEvent(proxy.loggerName, proxy.marker,
proxy.loggerFQCN, proxy.level, proxy.message,
proxy.thrown, proxy.thrownProxy, proxy.contextMap, proxy.contextStack, proxy.threadName,
proxy.source, proxy.timeMillis);
result.setEndOfBatch(proxy.isEndOfBatch);
result.setIncludeLocation(proxy.isLocationRequired);
return result;
}
throw new IllegalArgumentException("Event is not a serialized LogEvent: " + event.toString());
}
private void readObject(final ObjectInputStream stream) throws InvalidObjectException {
throw new InvalidObjectException("Proxy required");
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
final String n = loggerName.isEmpty() ? "root" : loggerName;
sb.append("Logger=").append(n);
sb.append(" Level=").append(level.name());
sb.append(" Message=").append(message.getFormattedMessage());
return sb.toString();
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final Log4jLogEvent that = (Log4jLogEvent) o;
if (endOfBatch != that.endOfBatch) {
return false;
}
if (includeLocation != that.includeLocation) {
return false;
}
if (timeMillis != that.timeMillis) {
return false;
}
if (loggerFqcn != null ? !loggerFqcn.equals(that.loggerFqcn) : that.loggerFqcn != null) {
return false;
}
if (level != null ? !level.equals(that.level) : that.level != null) {
return false;
}
if (source != null ? !source.equals(that.source) : that.source != null) {
return false;
}
if (marker != null ? !marker.equals(that.marker) : that.marker != null) {
return false;
}
if (contextMap != null ? !contextMap.equals(that.contextMap) : that.contextMap != null) {
return false;
}
if (!message.equals(that.message)) {
return false;
}
if (!loggerName.equals(that.loggerName)) {
return false;
}
if (contextStack != null ? !contextStack.equals(that.contextStack) : that.contextStack != null) {
return false;
}
if (threadName != null ? !threadName.equals(that.threadName) : that.threadName != null) {
return false;
}
if (thrown != null ? !thrown.equals(that.thrown) : that.thrown != null) {
return false;
}
if (thrownProxy != null ? !thrownProxy.equals(that.thrownProxy) : that.thrownProxy != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = loggerFqcn != null ? loggerFqcn.hashCode() : 0;
result = 31 * result + (marker != null ? marker.hashCode() : 0);
result = 31 * result + (level != null ? level.hashCode() : 0);
result = 31 * result + loggerName.hashCode();
result = 31 * result + message.hashCode();
result = 31 * result + (int) (timeMillis ^ (timeMillis >>> 32));
result = 31 * result + (thrown != null ? thrown.hashCode() : 0);
result = 31 * result + (thrownProxy != null ? thrownProxy.hashCode() : 0);
result = 31 * result + (contextMap != null ? contextMap.hashCode() : 0);
result = 31 * result + (contextStack != null ? contextStack.hashCode() : 0);
result = 31 * result + (threadName != null ? threadName.hashCode() : 0);
result = 31 * result + (source != null ? source.hashCode() : 0);
result = 31 * result + (includeLocation ? 1 : 0);
result = 31 * result + (endOfBatch ? 1 : 0);
return result;
}
/**
* Proxy pattern used to serialize the LogEvent.
*/
private static class LogEventProxy implements Serializable {
private static final long serialVersionUID = -7139032940312647146L;
private final String loggerFQCN;
private final Marker marker;
private final Level level;
private final String loggerName;
private final Message message;
private final long timeMillis;
private final transient Throwable thrown;
private final ThrowableProxy thrownProxy;
private final Map<String, String> contextMap;
private final ThreadContext.ContextStack contextStack;
private final String threadName;
private final StackTraceElement source;
private final boolean isLocationRequired;
private final boolean isEndOfBatch;
public LogEventProxy(final Log4jLogEvent event, final boolean includeLocation) {
this.loggerFQCN = event.loggerFqcn;
this.marker = event.marker;
this.level = event.level;
this.loggerName = event.loggerName;
this.message = event.message;
this.timeMillis = event.timeMillis;
this.thrown = event.thrown;
this.thrownProxy = event.thrownProxy;
this.contextMap = event.contextMap;
this.contextStack = event.contextStack;
this.source = includeLocation ? event.getSource() : null;
this.threadName = event.getThreadName();
this.isLocationRequired = includeLocation;
this.isEndOfBatch = event.endOfBatch;
}
/**
* Returns a Log4jLogEvent using the data in the proxy.
* @return Log4jLogEvent.
*/
protected Object readResolve() {
final Log4jLogEvent result = new Log4jLogEvent(loggerName, marker, loggerFQCN, level, message, thrown,
thrownProxy, contextMap, contextStack, threadName, source, timeMillis);
result.setEndOfBatch(isEndOfBatch);
result.setIncludeLocation(isLocationRequired);
return result;
}
}
}