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