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.core.impl; 018 019 import org.apache.logging.log4j.Level; 020 import org.apache.logging.log4j.ThreadContext; 021 import org.apache.logging.log4j.Marker; 022 import org.apache.logging.log4j.core.LogEvent; 023 import org.apache.logging.log4j.core.config.Property; 024 import org.apache.logging.log4j.message.LoggerNameAwareMessage; 025 import org.apache.logging.log4j.message.Message; 026 import org.apache.logging.log4j.message.TimestampMessage; 027 028 import java.io.InvalidObjectException; 029 import java.io.ObjectInputStream; 030 import java.io.Serializable; 031 import java.util.List; 032 import java.util.Map; 033 034 /** 035 * Implementation of a LogEvent. 036 */ 037 public class Log4jLogEvent implements LogEvent, Serializable { 038 039 private static final long serialVersionUID = -1351367343806656055L; 040 private static final String NOT_AVAIL = "?"; 041 private final String fqcnOfLogger; 042 private final Marker marker; 043 private final Level level; 044 private final String name; 045 private final Message message; 046 private final long timestamp; 047 private final ThrowableProxy throwable; 048 private final Map<String, String> mdc; 049 private final ThreadContext.ContextStack ndc; 050 private String threadName = null; 051 private StackTraceElement location; 052 private boolean includeLocation; 053 private boolean endOfBatch = false; 054 055 /** 056 * Constructor. 057 * @param loggerName The name of the Logger. 058 * @param marker The Marker or null. 059 * @param fqcn The fully qualified class name of the caller. 060 * @param level The logging Level. 061 * @param message The Message. 062 * @param t A Throwable or null. 063 */ 064 public Log4jLogEvent(final String loggerName, final Marker marker, final String fqcn, final Level level, 065 final Message message, final Throwable t) { 066 this(loggerName, marker, fqcn, level, message, null, t); 067 } 068 069 /** 070 * Constructor. 071 * @param loggerName The name of the Logger. 072 * @param marker The Marker or null. 073 * @param fqcn The fully qualified class name of the caller. 074 * @param level The logging Level. 075 * @param message The Message. 076 * @param properties properties to add to the event. 077 * @param t A Throwable or null. 078 */ 079 public Log4jLogEvent(final String loggerName, final Marker marker, final String fqcn, final Level level, 080 final Message message, final List<Property> properties, final Throwable t) { 081 this(loggerName, marker, fqcn, level, message, t, 082 createMap(properties), 083 ThreadContext.getDepth() == 0 ? null : ThreadContext.cloneStack(), null, 084 null, System.currentTimeMillis()); 085 } 086 087 /** 088 * Constructor. 089 * @param loggerName The name of the Logger. 090 * @param marker The Marker or null. 091 * @param fqcn The fully qualified class name of the caller. 092 * @param level The logging Level. 093 * @param message The Message. 094 * @param t A Throwable or null. 095 * @param mdc The mapped diagnostic context. 096 * @param ndc the nested diagnostic context. 097 * @param threadName The name of the thread. 098 * @param location The locations of the caller. 099 * @param timestamp The timestamp of the event. 100 */ 101 public Log4jLogEvent(final String loggerName, final Marker marker, final String fqcn, final Level level, 102 final Message message, final Throwable t, 103 final Map<String, String> mdc, final ThreadContext.ContextStack ndc, final String threadName, 104 final StackTraceElement location, final long timestamp) { 105 name = loggerName; 106 this.marker = marker; 107 this.fqcnOfLogger = fqcn; 108 this.level = level; 109 this.message = message; 110 this.throwable = t == null ? null : t instanceof ThrowableProxy ? (ThrowableProxy) t : new ThrowableProxy(t); 111 this.mdc = mdc; 112 this.ndc = ndc; 113 this.timestamp = message instanceof TimestampMessage ? ((TimestampMessage) message).getTimestamp() : timestamp; 114 this.threadName = threadName; 115 this.location = location; 116 if (message != null && message instanceof LoggerNameAwareMessage) { 117 ((LoggerNameAwareMessage) message).setLoggerName(name); 118 } 119 } 120 121 private static Map<String, String> createMap(final List<Property> properties) { 122 if (ThreadContext.isEmpty() && (properties == null || properties.size() == 0)) { 123 return null; 124 } 125 if (properties == null || properties.size() == 0) { 126 return ThreadContext.getImmutableContext(); 127 } 128 final Map<String, String> map = ThreadContext.getContext(); 129 130 for (final Property prop : properties) { 131 if (!map.containsKey(prop.getName())) { 132 map.put(prop.getName(), prop.getValue()); 133 } 134 } 135 return new ThreadContext.ImmutableMap(map); 136 } 137 138 /** 139 * Returns the logging Level. 140 * @return the Level associated with this event. 141 */ 142 public Level getLevel() { 143 return level; 144 } 145 146 /** 147 * Returns the name of the Logger used to generate the event. 148 * @return The Logger name. 149 */ 150 public String getLoggerName() { 151 return name; 152 } 153 154 /** 155 * Returns the Message associated with the event. 156 * @return The Message. 157 */ 158 public Message getMessage() { 159 return message; 160 } 161 162 /** 163 * Returns the name of the Thread on which the event was generated. 164 * @return The name of the Thread. 165 */ 166 public String getThreadName() { 167 if (threadName == null) { 168 threadName = Thread.currentThread().getName(); 169 } 170 return threadName; 171 } 172 173 /** 174 * Returns the time in milliseconds from the epoch when the event occurred. 175 * @return The time the event occurred. 176 */ 177 public long getMillis() { 178 return timestamp; 179 } 180 181 /** 182 * Returns the Throwable associated with the event, or null. 183 * @return The Throwable associated with the event. 184 */ 185 public Throwable getThrown() { 186 return throwable; 187 } 188 189 /** 190 * Returns the Marker associated with the event, or null. 191 * @return the Marker associated with the event. 192 */ 193 public Marker getMarker() { 194 return marker; 195 } 196 197 /** 198 * The fully qualified class name of the class that was called by the caller. 199 * @return the fully qualified class name of the class that is performing logging. 200 */ 201 public String getFQCN() { 202 return fqcnOfLogger; 203 } 204 205 /** 206 * Returns the immutable copy of the ThreadContext Map. 207 * @return The context Map. 208 */ 209 public Map<String, String> getContextMap() { 210 return mdc == null ? ThreadContext.EMPTY_MAP : mdc; 211 } 212 213 /** 214 * Returns an immutable copy of the ThreadContext stack. 215 * @return The context Stack. 216 */ 217 public ThreadContext.ContextStack getContextStack() { 218 return ndc == null ? ThreadContext.EMPTY_STACK : ndc; 219 } 220 221 /** 222 * Returns the StackTraceElement for the caller. This will be the entry that occurs right 223 * before the first occurrence of FQCN as a class name. 224 * @return the StackTraceElement for the caller. 225 */ 226 public StackTraceElement getSource() { 227 if (location != null) { 228 return location; 229 } 230 if (fqcnOfLogger == null || !includeLocation) { 231 return null; 232 } 233 location = calcLocation(fqcnOfLogger); 234 return location; 235 } 236 237 public static StackTraceElement calcLocation(String fqcnOfLogger) { 238 if (fqcnOfLogger == null) { 239 return null; 240 } 241 final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); 242 boolean next = false; 243 for (final StackTraceElement element : stackTrace) { 244 final String className = element.getClassName(); 245 if (next) { 246 if (fqcnOfLogger.equals(className)) { 247 continue; 248 } 249 return element; 250 } 251 252 if (fqcnOfLogger.equals(className)) { 253 next = true; 254 } else if (NOT_AVAIL.equals(className)) { 255 break; 256 } 257 } 258 return null; 259 } 260 261 public boolean isIncludeLocation() { 262 return includeLocation; 263 } 264 265 public void setIncludeLocation(boolean includeLocation) { 266 this.includeLocation = includeLocation; 267 } 268 269 public boolean isEndOfBatch() { 270 return endOfBatch; 271 } 272 273 public void setEndOfBatch(boolean endOfBatch) { 274 this.endOfBatch = endOfBatch; 275 } 276 277 /** 278 * Creates a LogEventProxy that can be serialized. 279 * @return a LogEventProxy. 280 */ 281 protected Object writeReplace() { 282 return new LogEventProxy(this, this.includeLocation); 283 } 284 285 public static Serializable serialize(final Log4jLogEvent event, 286 final boolean includeLocation) { 287 return new LogEventProxy(event, includeLocation); 288 } 289 290 public static Log4jLogEvent deserialize(final Serializable event) { 291 if (event == null) { 292 throw new NullPointerException("Event cannot be null"); 293 } 294 if (event instanceof LogEventProxy) { 295 final LogEventProxy proxy = (LogEventProxy) event; 296 Log4jLogEvent result = new Log4jLogEvent(proxy.name, proxy.marker, 297 proxy.fqcnOfLogger, proxy.level, proxy.message, 298 proxy.throwable, proxy.mdc, proxy.ndc, proxy.threadName, 299 proxy.location, proxy.timestamp); 300 result.setEndOfBatch(proxy.isEndOfBatch); 301 result.setIncludeLocation(proxy.isLocationRequired); 302 return result; 303 } 304 throw new IllegalArgumentException("Event is not a serialized LogEvent: " + event.toString()); 305 } 306 307 private void readObject(final ObjectInputStream stream) throws InvalidObjectException { 308 throw new InvalidObjectException("Proxy required"); 309 } 310 311 @Override 312 public String toString() { 313 final StringBuilder sb = new StringBuilder(); 314 final String n = name.length() == 0 ? "root" : name; 315 sb.append("Logger=").append(n); 316 sb.append(" Level=").append(level.name()); 317 sb.append(" Message=").append(message.getFormattedMessage()); 318 return sb.toString(); 319 } 320 321 /** 322 * Proxy pattern used to serialize the LogEvent. 323 */ 324 private static class LogEventProxy implements Serializable { 325 326 private static final long serialVersionUID = -7139032940312647146L; 327 private final String fqcnOfLogger; 328 private final Marker marker; 329 private final Level level; 330 private final String name; 331 private final Message message; 332 private final long timestamp; 333 private final Throwable throwable; 334 private final Map<String, String> mdc; 335 private final ThreadContext.ContextStack ndc; 336 private final String threadName; 337 private final StackTraceElement location; 338 private final boolean isLocationRequired; 339 private final boolean isEndOfBatch; 340 341 public LogEventProxy(final Log4jLogEvent event, boolean includeLocation) { 342 this.fqcnOfLogger = event.fqcnOfLogger; 343 this.marker = event.marker; 344 this.level = event.level; 345 this.name = event.name; 346 this.message = event.message; 347 this.timestamp = event.timestamp; 348 this.throwable = event.throwable; 349 this.mdc = event.mdc; 350 this.ndc = event.ndc; 351 this.location = includeLocation ? event.getSource() : null; 352 this.threadName = event.getThreadName(); 353 this.isLocationRequired = includeLocation; 354 this.isEndOfBatch = event.endOfBatch; 355 } 356 357 /** 358 * Returns a Log4jLogEvent using the data in the proxy. 359 * @return Log4jLogEvent. 360 */ 361 protected Object readResolve() { 362 Log4jLogEvent result = new Log4jLogEvent(name, marker, fqcnOfLogger, 363 level, message, throwable, mdc, ndc, threadName, location, 364 timestamp); 365 result.setEndOfBatch(isEndOfBatch); 366 result.setIncludeLocation(isLocationRequired); 367 return result; 368 } 369 } 370 }