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 */ 017package org.apache.logging.log4j.core.impl; 018 019import java.io.InvalidObjectException; 020import java.io.ObjectInputStream; 021import java.util.Arrays; 022import java.util.Map; 023 024import org.apache.logging.log4j.Level; 025import org.apache.logging.log4j.Marker; 026import org.apache.logging.log4j.ThreadContext; 027import org.apache.logging.log4j.core.LogEvent; 028import org.apache.logging.log4j.core.async.InternalAsyncUtil; 029import org.apache.logging.log4j.core.util.Constants; 030import org.apache.logging.log4j.message.Message; 031import org.apache.logging.log4j.message.ParameterizedMessage; 032import org.apache.logging.log4j.message.ReusableMessage; 033import org.apache.logging.log4j.message.SimpleMessage; 034import org.apache.logging.log4j.util.ReadOnlyStringMap; 035import org.apache.logging.log4j.util.StackLocatorUtil; 036import org.apache.logging.log4j.util.StringBuilders; 037import org.apache.logging.log4j.util.StringMap; 038import org.apache.logging.log4j.util.Strings; 039 040/** 041 * Mutable implementation of the {@code LogEvent} interface. 042 * @since 2.6 043 */ 044public class MutableLogEvent implements LogEvent, ReusableMessage { 045 private static final Message EMPTY = new SimpleMessage(Strings.EMPTY); 046 047 private int threadPriority; 048 private long threadId; 049 private long timeMillis; 050 private long nanoTime; 051 private short parameterCount; 052 private boolean includeLocation; 053 private boolean endOfBatch = false; 054 private Level level; 055 private String threadName; 056 private String loggerName; 057 private Message message; 058 private StringBuilder messageText; 059 private Object[] parameters; 060 private Throwable thrown; 061 private ThrowableProxy thrownProxy; 062 private StringMap contextData = ContextDataFactory.createContextData(); 063 private Marker marker; 064 private String loggerFqcn; 065 private StackTraceElement source; 066 private ThreadContext.ContextStack contextStack; 067 transient boolean reserved = false; 068 069 public MutableLogEvent() { 070 this(new StringBuilder(Constants.INITIAL_REUSABLE_MESSAGE_SIZE), new Object[10]); 071 } 072 073 public MutableLogEvent(final StringBuilder msgText, final Object[] replacementParameters) { 074 this.messageText = msgText; 075 this.parameters = replacementParameters; 076 } 077 078 @Override 079 public Log4jLogEvent toImmutable() { 080 return createMemento(); 081 } 082 083 /** 084 * Initialize the fields of this {@code MutableLogEvent} from another event. 085 * Similar in purpose and usage as {@link org.apache.logging.log4j.core.impl.Log4jLogEvent.LogEventProxy}, 086 * but a mutable version. 087 * <p> 088 * This method is used on async logger ringbuffer slots holding MutableLogEvent objects in each slot. 089 * </p> 090 * 091 * @param event the event to copy data from 092 */ 093 public void initFrom(final LogEvent event) { 094 this.loggerFqcn = event.getLoggerFqcn(); 095 this.marker = event.getMarker(); 096 this.level = event.getLevel(); 097 this.loggerName = event.getLoggerName(); 098 this.timeMillis = event.getTimeMillis(); 099 this.thrown = event.getThrown(); 100 this.thrownProxy = event.getThrownProxy(); 101 102 // NOTE: this ringbuffer event SHOULD NOT keep a reference to the specified 103 // thread-local MutableLogEvent's context data, because then two threads would call 104 // ReadOnlyStringMap.clear() on the same shared instance, resulting in data corruption. 105 this.contextData.putAll(event.getContextData()); 106 107 this.contextStack = event.getContextStack(); 108 this.source = event.isIncludeLocation() ? event.getSource() : null; 109 this.threadId = event.getThreadId(); 110 this.threadName = event.getThreadName(); 111 this.threadPriority = event.getThreadPriority(); 112 this.endOfBatch = event.isEndOfBatch(); 113 this.includeLocation = event.isIncludeLocation(); 114 this.nanoTime = event.getNanoTime(); 115 setMessage(event.getMessage()); 116 } 117 118 /** 119 * Clears all references this event has to other objects. 120 */ 121 public void clear() { 122 loggerFqcn = null; 123 marker = null; 124 level = null; 125 loggerName = null; 126 message = null; 127 thrown = null; 128 thrownProxy = null; 129 source = null; 130 if (contextData != null) { 131 if (contextData.isFrozen()) { // came from CopyOnWrite thread context 132 contextData = null; 133 } else { 134 contextData.clear(); 135 } 136 } 137 contextStack = null; 138 139 // ThreadName should not be cleared: this field is set in the ReusableLogEventFactory 140 // where this instance is kept in a ThreadLocal, so it usually does not change. 141 // threadName = null; // no need to clear threadName 142 143 // ensure that excessively long char[] arrays are not kept in memory forever 144 StringBuilders.trimToMaxSize(messageText, Constants.MAX_REUSABLE_MESSAGE_SIZE); 145 146 if (parameters != null) { 147 for (int i = 0; i < parameters.length; i++) { 148 parameters[i] = null; 149 } 150 } 151 152 // primitive fields that cannot be cleared: 153 //timeMillis; 154 //threadId; 155 //threadPriority; 156 //includeLocation; 157 //endOfBatch; 158 //nanoTime; 159 } 160 161 @Override 162 public String getLoggerFqcn() { 163 return loggerFqcn; 164 } 165 166 public void setLoggerFqcn(final String loggerFqcn) { 167 this.loggerFqcn = loggerFqcn; 168 } 169 170 @Override 171 public Marker getMarker() { 172 return marker; 173 } 174 175 public void setMarker(final Marker marker) { 176 this.marker = marker; 177 } 178 179 @Override 180 public Level getLevel() { 181 if (level == null) { 182 level = Level.OFF; // LOG4J2-462, LOG4J2-465 183 } 184 return level; 185 } 186 187 public void setLevel(final Level level) { 188 this.level = level; 189 } 190 191 @Override 192 public String getLoggerName() { 193 return loggerName; 194 } 195 196 public void setLoggerName(final String loggerName) { 197 this.loggerName = loggerName; 198 } 199 200 @Override 201 public Message getMessage() { 202 if (message == null) { 203 return messageText == null ? EMPTY : this; 204 } 205 return message; 206 } 207 208 public void setMessage(final Message msg) { 209 if (msg instanceof ReusableMessage) { 210 final ReusableMessage reusable = (ReusableMessage) msg; 211 reusable.formatTo(getMessageTextForWriting()); 212 if (parameters != null) { 213 parameters = reusable.swapParameters(parameters); 214 parameterCount = reusable.getParameterCount(); 215 } 216 } else { 217 this.message = InternalAsyncUtil.makeMessageImmutable(msg); 218 } 219 } 220 221 private StringBuilder getMessageTextForWriting() { 222 if (messageText == null) { 223 // Should never happen: 224 // only happens if user logs a custom reused message when Constants.ENABLE_THREADLOCALS is false 225 messageText = new StringBuilder(Constants.INITIAL_REUSABLE_MESSAGE_SIZE); 226 } 227 messageText.setLength(0); 228 return messageText; 229 } 230 231 /** 232 * @see ReusableMessage#getFormattedMessage() 233 */ 234 @Override 235 public String getFormattedMessage() { 236 return messageText.toString(); 237 } 238 239 /** 240 * @see ReusableMessage#getFormat() 241 */ 242 @Override 243 public String getFormat() { 244 return null; 245 } 246 247 /** 248 * @see ReusableMessage#getParameters() 249 */ 250 @Override 251 public Object[] getParameters() { 252 return parameters == null ? null : Arrays.copyOf(parameters, parameterCount); 253 } 254 255 /** 256 * @see ReusableMessage#getThrowable() 257 */ 258 @Override 259 public Throwable getThrowable() { 260 return getThrown(); 261 } 262 263 /** 264 * @see ReusableMessage#formatTo(StringBuilder) 265 */ 266 @Override 267 public void formatTo(final StringBuilder buffer) { 268 buffer.append(messageText); 269 } 270 271 /** 272 * Replaces this ReusableMessage's parameter array with the specified value and return the original array 273 * @param emptyReplacement the parameter array that can be used for subsequent uses of this reusable message 274 * @return the original parameter array 275 * @see ReusableMessage#swapParameters(Object[]) 276 */ 277 @Override 278 public Object[] swapParameters(final Object[] emptyReplacement) { 279 final Object[] result = this.parameters; 280 this.parameters = emptyReplacement; 281 return result; 282 } 283 284 /* 285 * @see ReusableMessage#getParameterCount 286 */ 287 @Override 288 public short getParameterCount() { 289 return parameterCount; 290 } 291 292 @Override 293 public Message memento() { 294 if (message != null) { 295 return message; 296 } 297 final Object[] params = parameters == null ? new Object[0] : Arrays.copyOf(parameters, parameterCount); 298 return new ParameterizedMessage(messageText.toString(), params); 299 } 300 301 @Override 302 public Throwable getThrown() { 303 return thrown; 304 } 305 306 public void setThrown(final Throwable thrown) { 307 this.thrown = thrown; 308 } 309 310 @Override 311 public long getTimeMillis() { 312 return timeMillis; 313 } 314 315 public void setTimeMillis(final long timeMillis) { 316 this.timeMillis = timeMillis; 317 } 318 319 /** 320 * Returns the ThrowableProxy associated with the event, or null. 321 * @return The ThrowableProxy associated with the event. 322 */ 323 @Override 324 public ThrowableProxy getThrownProxy() { 325 if (thrownProxy == null && thrown != null) { 326 thrownProxy = new ThrowableProxy(thrown); 327 } 328 return thrownProxy; 329 } 330 331 /** 332 * Returns the StackTraceElement for the caller. This will be the entry that occurs right 333 * before the first occurrence of FQCN as a class name. 334 * @return the StackTraceElement for the caller. 335 */ 336 @Override 337 public StackTraceElement getSource() { 338 if (source != null) { 339 return source; 340 } 341 if (loggerFqcn == null || !includeLocation) { 342 return null; 343 } 344 source = StackLocatorUtil.calcLocation(loggerFqcn); 345 return source; 346 } 347 348 @SuppressWarnings("unchecked") 349 @Override 350 public ReadOnlyStringMap getContextData() { 351 return contextData; 352 } 353 354 @Override 355 public Map<String, String> getContextMap() { 356 return contextData.toMap(); 357 } 358 359 public void setContextData(final StringMap mutableContextData) { 360 this.contextData = mutableContextData; 361 } 362 363 @Override 364 public ThreadContext.ContextStack getContextStack() { 365 return contextStack; 366 } 367 368 public void setContextStack(final ThreadContext.ContextStack contextStack) { 369 this.contextStack = contextStack; 370 } 371 372 @Override 373 public long getThreadId() { 374 return threadId; 375 } 376 377 public void setThreadId(final long threadId) { 378 this.threadId = threadId; 379 } 380 381 @Override 382 public String getThreadName() { 383 return threadName; 384 } 385 386 public void setThreadName(final String threadName) { 387 this.threadName = threadName; 388 } 389 390 @Override 391 public int getThreadPriority() { 392 return threadPriority; 393 } 394 395 public void setThreadPriority(final int threadPriority) { 396 this.threadPriority = threadPriority; 397 } 398 399 @Override 400 public boolean isIncludeLocation() { 401 return includeLocation; 402 } 403 404 @Override 405 public void setIncludeLocation(final boolean includeLocation) { 406 this.includeLocation = includeLocation; 407 } 408 409 @Override 410 public boolean isEndOfBatch() { 411 return endOfBatch; 412 } 413 414 @Override 415 public void setEndOfBatch(final boolean endOfBatch) { 416 this.endOfBatch = endOfBatch; 417 } 418 419 @Override 420 public long getNanoTime() { 421 return nanoTime; 422 } 423 424 public void setNanoTime(final long nanoTime) { 425 this.nanoTime = nanoTime; 426 } 427 428 /** 429 * Creates a LogEventProxy that can be serialized. 430 * @return a LogEventProxy. 431 */ 432 protected Object writeReplace() { 433 return new Log4jLogEvent.LogEventProxy(this, this.includeLocation); 434 } 435 436 private void readObject(final ObjectInputStream stream) throws InvalidObjectException { 437 throw new InvalidObjectException("Proxy required"); 438 } 439 440 /** 441 * Creates and returns a new immutable copy of this {@code MutableLogEvent}. 442 * If {@link #isIncludeLocation()} is true, this will obtain caller location information. 443 * 444 * @return a new immutable copy of the data in this {@code MutableLogEvent} 445 */ 446 public Log4jLogEvent createMemento() { 447 return Log4jLogEvent.deserialize(Log4jLogEvent.serialize(this, includeLocation)); 448 } 449 450 /** 451 * Initializes the specified {@code Log4jLogEvent.Builder} from this {@code MutableLogEvent}. 452 * @param builder the builder whose fields to populate 453 */ 454 public void initializeBuilder(final Log4jLogEvent.Builder builder) { 455 builder.setContextData(contextData) // 456 .setContextStack(contextStack) // 457 .setEndOfBatch(endOfBatch) // 458 .setIncludeLocation(includeLocation) // 459 .setLevel(getLevel()) // ensure non-null 460 .setLoggerFqcn(loggerFqcn) // 461 .setLoggerName(loggerName) // 462 .setMarker(marker) // 463 .setMessage(getNonNullImmutableMessage()) // ensure non-null & immutable 464 .setNanoTime(nanoTime) // 465 .setSource(source) // 466 .setThreadId(threadId) // 467 .setThreadName(threadName) // 468 .setThreadPriority(threadPriority) // 469 .setThrown(getThrown()) // may deserialize from thrownProxy 470 .setThrownProxy(thrownProxy) // avoid unnecessarily creating thrownProxy 471 .setTimeMillis(timeMillis); 472 } 473 474 private Message getNonNullImmutableMessage() { 475 return message != null ? message : new SimpleMessage(String.valueOf(messageText)); 476 } 477}