View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.impl;
18  
19  import java.io.InvalidObjectException;
20  import java.io.ObjectInputStream;
21  import java.util.Arrays;
22  import java.util.Map;
23  
24  import org.apache.logging.log4j.Level;
25  import org.apache.logging.log4j.Marker;
26  import org.apache.logging.log4j.ThreadContext;
27  import org.apache.logging.log4j.core.LogEvent;
28  import org.apache.logging.log4j.core.util.Constants;
29  import org.apache.logging.log4j.message.AsynchronouslyFormattable;
30  import org.apache.logging.log4j.message.Message;
31  import org.apache.logging.log4j.message.ParameterizedMessage;
32  import org.apache.logging.log4j.message.ReusableMessage;
33  import org.apache.logging.log4j.message.SimpleMessage;
34  import org.apache.logging.log4j.status.StatusLogger;
35  import org.apache.logging.log4j.util.ReadOnlyStringMap;
36  import org.apache.logging.log4j.util.StackLocatorUtil;
37  import org.apache.logging.log4j.util.StringBuilders;
38  import org.apache.logging.log4j.util.StringMap;
39  import org.apache.logging.log4j.util.Strings;
40  
41  /**
42   * Mutable implementation of the {@code LogEvent} interface.
43   * @since 2.6
44   */
45  public class MutableLogEvent implements LogEvent, ReusableMessage {
46      private static final Message EMPTY = new SimpleMessage(Strings.EMPTY);
47  
48      private int threadPriority;
49      private long threadId;
50      private long timeMillis;
51      private long nanoTime;
52      private short parameterCount;
53      private boolean includeLocation;
54      private boolean endOfBatch = false;
55      private Level level;
56      private String threadName;
57      private String loggerName;
58      private Message message;
59      private StringBuilder messageText;
60      private Object[] parameters;
61      private Throwable thrown;
62      private ThrowableProxy thrownProxy;
63      private StringMap contextData = ContextDataFactory.createContextData();
64      private Marker marker;
65      private String loggerFqcn;
66      private StackTraceElement source;
67      private ThreadContext.ContextStack contextStack;
68      transient boolean reserved = false;
69  
70      public MutableLogEvent() {
71          this(new StringBuilder(Constants.INITIAL_REUSABLE_MESSAGE_SIZE), new Object[10]);
72      }
73  
74      public MutableLogEvent(final StringBuilder msgText, final Object[] replacementParameters) {
75          this.messageText = msgText;
76          this.parameters = replacementParameters;
77      }
78  
79      @Override
80      public Log4jLogEvent toImmutable() {
81          return createMemento();
82      }
83  
84      /**
85       * Initialize the fields of this {@code MutableLogEvent} from another event.
86       * Similar in purpose and usage as {@link org.apache.logging.log4j.core.impl.Log4jLogEvent.LogEventProxy},
87       * but a mutable version.
88       * <p>
89       * This method is used on async logger ringbuffer slots holding MutableLogEvent objects in each slot.
90       * </p>
91       *
92       * @param event the event to copy data from
93       */
94      public void initFrom(final LogEvent event) {
95          this.loggerFqcn = event.getLoggerFqcn();
96          this.marker = event.getMarker();
97          this.level = event.getLevel();
98          this.loggerName = event.getLoggerName();
99          this.timeMillis = event.getTimeMillis();
100         this.thrown = event.getThrown();
101         this.thrownProxy = event.getThrownProxy();
102 
103         // NOTE: this ringbuffer event SHOULD NOT keep a reference to the specified
104         // thread-local MutableLogEvent's context data, because then two threads would call
105         // ReadOnlyStringMap.clear() on the same shared instance, resulting in data corruption.
106         this.contextData.putAll(event.getContextData());
107 
108         this.contextStack = event.getContextStack();
109         this.source = event.isIncludeLocation() ? event.getSource() : null;
110         this.threadId = event.getThreadId();
111         this.threadName = event.getThreadName();
112         this.threadPriority = event.getThreadPriority();
113         this.endOfBatch = event.isEndOfBatch();
114         this.includeLocation = event.isIncludeLocation();
115         this.nanoTime = event.getNanoTime();
116         setMessage(event.getMessage());
117     }
118 
119     /**
120      * Clears all references this event has to other objects.
121      */
122     public void clear() {
123         loggerFqcn = null;
124         marker = null;
125         level = null;
126         loggerName = null;
127         message = null;
128         thrown = null;
129         thrownProxy = null;
130         source = null;
131         if (contextData != null) {
132             if (contextData.isFrozen()) { // came from CopyOnWrite thread context
133                 contextData = null;
134             } else {
135                 contextData.clear();
136             }
137         }
138         contextStack = null;
139 
140         // ThreadName should not be cleared: this field is set in the ReusableLogEventFactory
141         // where this instance is kept in a ThreadLocal, so it usually does not change.
142         // threadName = null; // no need to clear threadName
143 
144         // ensure that excessively long char[] arrays are not kept in memory forever
145         StringBuilders.trimToMaxSize(messageText, Constants.MAX_REUSABLE_MESSAGE_SIZE);
146 
147         if (parameters != null) {
148             for (int i = 0; i < parameters.length; i++) {
149                 parameters[i] = null;
150             }
151         }
152 
153         // primitive fields that cannot be cleared:
154         //timeMillis;
155         //threadId;
156         //threadPriority;
157         //includeLocation;
158         //endOfBatch;
159         //nanoTime;
160     }
161 
162     @Override
163     public String getLoggerFqcn() {
164         return loggerFqcn;
165     }
166 
167     public void setLoggerFqcn(final String loggerFqcn) {
168         this.loggerFqcn = loggerFqcn;
169     }
170 
171     @Override
172     public Marker getMarker() {
173         return marker;
174     }
175 
176     public void setMarker(final Marker marker) {
177         this.marker = marker;
178     }
179 
180     @Override
181     public Level getLevel() {
182         if (level == null) {
183             level = Level.OFF; // LOG4J2-462, LOG4J2-465
184         }
185         return level;
186     }
187 
188     public void setLevel(final Level level) {
189         this.level = level;
190     }
191 
192     @Override
193     public String getLoggerName() {
194         return loggerName;
195     }
196 
197     public void setLoggerName(final String loggerName) {
198         this.loggerName = loggerName;
199     }
200 
201     @Override
202     public Message getMessage() {
203         if (message == null) {
204             return messageText == null ? EMPTY : this;
205         }
206         return message;
207     }
208 
209     public void setMessage(final Message msg) {
210         if (msg instanceof ReusableMessage) {
211             final ReusableMessage reusable = (ReusableMessage) msg;
212             reusable.formatTo(getMessageTextForWriting());
213             if (parameters != null) {
214                 parameters = reusable.swapParameters(parameters);
215                 parameterCount = reusable.getParameterCount();
216             }
217         } else {
218             // if the Message instance is reused, there is no point in freezing its message here
219             if (msg != null && !canFormatMessageInBackground(msg)) {
220                 msg.getFormattedMessage(); // LOG4J2-763: ask message to freeze parameters
221             }
222             this.message = msg;
223         }
224     }
225 
226     private boolean canFormatMessageInBackground(final Message message) {
227         return Constants.FORMAT_MESSAGES_IN_BACKGROUND // LOG4J2-898: user wants to format all msgs in background
228                 || message.getClass().isAnnotationPresent(AsynchronouslyFormattable.class); // LOG4J2-1718
229     }
230 
231     private StringBuilder getMessageTextForWriting() {
232         if (messageText == null) {
233             // Should never happen:
234             // only happens if user logs a custom reused message when Constants.ENABLE_THREADLOCALS is false
235             messageText = new StringBuilder(Constants.INITIAL_REUSABLE_MESSAGE_SIZE);
236         }
237         messageText.setLength(0);
238         return messageText;
239     }
240 
241     /**
242      * @see ReusableMessage#getFormattedMessage()
243      */
244     @Override
245     public String getFormattedMessage() {
246         return messageText.toString();
247     }
248 
249     /**
250      * @see ReusableMessage#getFormat()
251      */
252     @Override
253     public String getFormat() {
254         return null;
255     }
256 
257     /**
258      * @see ReusableMessage#getParameters()
259      */
260     @Override
261     public Object[] getParameters() {
262         return parameters == null ? null : Arrays.copyOf(parameters, parameterCount);
263     }
264 
265     /**
266      * @see ReusableMessage#getThrowable()
267      */
268     @Override
269     public Throwable getThrowable() {
270         return getThrown();
271     }
272 
273     /**
274      * @see ReusableMessage#formatTo(StringBuilder)
275      */
276     @Override
277     public void formatTo(final StringBuilder buffer) {
278         buffer.append(messageText);
279     }
280 
281     /**
282      * Replaces this ReusableMessage's parameter array with the specified value and return the original array
283      * @param emptyReplacement the parameter array that can be used for subsequent uses of this reusable message
284      * @return the original parameter array
285      * @see ReusableMessage#swapParameters(Object[])
286      */
287     @Override
288     public Object[] swapParameters(final Object[] emptyReplacement) {
289         final Object[] result = this.parameters;
290         this.parameters = emptyReplacement;
291         return result;
292     }
293 
294     /*
295      * @see ReusableMessage#getParameterCount
296      */
297     @Override
298     public short getParameterCount() {
299         return parameterCount;
300     }
301 
302     @Override
303     public Message memento() {
304         if (message != null) {
305             return message;
306         }
307         final Object[] params = parameters == null ? new Object[0] : Arrays.copyOf(parameters, parameterCount);
308         return new ParameterizedMessage(messageText.toString(), params);
309     }
310 
311     @Override
312     public Throwable getThrown() {
313         return thrown;
314     }
315 
316     public void setThrown(final Throwable thrown) {
317         this.thrown = thrown;
318     }
319 
320     @Override
321     public long getTimeMillis() {
322         return timeMillis;
323     }
324 
325     public void setTimeMillis(final long timeMillis) {
326         this.timeMillis = timeMillis;
327     }
328 
329     /**
330      * Returns the ThrowableProxy associated with the event, or null.
331      * @return The ThrowableProxy associated with the event.
332      */
333     @Override
334     public ThrowableProxy getThrownProxy() {
335         if (thrownProxy == null && thrown != null) {
336             thrownProxy = new ThrowableProxy(thrown);
337         }
338         return thrownProxy;
339     }
340 
341     /**
342      * Returns the StackTraceElement for the caller. This will be the entry that occurs right
343      * before the first occurrence of FQCN as a class name.
344      * @return the StackTraceElement for the caller.
345      */
346     @Override
347     public StackTraceElement getSource() {
348         if (source != null) {
349             return source;
350         }
351         if (loggerFqcn == null || !includeLocation) {
352             return null;
353         }
354         source = StackLocatorUtil.calcLocation(loggerFqcn);
355         return source;
356     }
357 
358     @SuppressWarnings("unchecked")
359     @Override
360     public ReadOnlyStringMap getContextData() {
361         return contextData;
362     }
363 
364     @Override
365     public Map<String, String> getContextMap() {
366         return contextData.toMap();
367     }
368 
369     public void setContextData(final StringMap mutableContextData) {
370         this.contextData = mutableContextData;
371     }
372 
373     @Override
374     public ThreadContext.ContextStack getContextStack() {
375         return contextStack;
376     }
377 
378     public void setContextStack(final ThreadContext.ContextStack contextStack) {
379         this.contextStack = contextStack;
380     }
381 
382     @Override
383     public long getThreadId() {
384         return threadId;
385     }
386 
387     public void setThreadId(final long threadId) {
388         this.threadId = threadId;
389     }
390 
391     @Override
392     public String getThreadName() {
393         return threadName;
394     }
395 
396     public void setThreadName(final String threadName) {
397         this.threadName = threadName;
398     }
399 
400     @Override
401     public int getThreadPriority() {
402         return threadPriority;
403     }
404 
405     public void setThreadPriority(final int threadPriority) {
406         this.threadPriority = threadPriority;
407     }
408 
409     @Override
410     public boolean isIncludeLocation() {
411         return includeLocation;
412     }
413 
414     @Override
415     public void setIncludeLocation(final boolean includeLocation) {
416         this.includeLocation = includeLocation;
417     }
418 
419     @Override
420     public boolean isEndOfBatch() {
421         return endOfBatch;
422     }
423 
424     @Override
425     public void setEndOfBatch(final boolean endOfBatch) {
426         this.endOfBatch = endOfBatch;
427     }
428 
429     @Override
430     public long getNanoTime() {
431         return nanoTime;
432     }
433 
434     public void setNanoTime(final long nanoTime) {
435         this.nanoTime = nanoTime;
436     }
437 
438     /**
439      * Creates a LogEventProxy that can be serialized.
440      * @return a LogEventProxy.
441      */
442     protected Object writeReplace() {
443         return new Log4jLogEvent.LogEventProxy(this, this.includeLocation);
444     }
445 
446     private void readObject(final ObjectInputStream stream) throws InvalidObjectException {
447         throw new InvalidObjectException("Proxy required");
448     }
449 
450     /**
451      * Creates and returns a new immutable copy of this {@code MutableLogEvent}.
452      * If {@link #isIncludeLocation()} is true, this will obtain caller location information.
453      *
454      * @return a new immutable copy of the data in this {@code MutableLogEvent}
455      */
456     public Log4jLogEvent createMemento() {
457         return Log4jLogEvent.deserialize(Log4jLogEvent.serialize(this, includeLocation));
458     }
459 
460     /**
461      * Initializes the specified {@code Log4jLogEvent.Builder} from this {@code MutableLogEvent}.
462      * @param builder the builder whose fields to populate
463      */
464     public void initializeBuilder(final Log4jLogEvent.Builder builder) {
465         builder.setContextData(contextData) //
466                 .setContextStack(contextStack) //
467                 .setEndOfBatch(endOfBatch) //
468                 .setIncludeLocation(includeLocation) //
469                 .setLevel(getLevel()) // ensure non-null
470                 .setLoggerFqcn(loggerFqcn) //
471                 .setLoggerName(loggerName) //
472                 .setMarker(marker) //
473                 .setMessage(getNonNullImmutableMessage()) // ensure non-null & immutable
474                 .setNanoTime(nanoTime) //
475                 .setSource(source) //
476                 .setThreadId(threadId) //
477                 .setThreadName(threadName) //
478                 .setThreadPriority(threadPriority) //
479                 .setThrown(getThrown()) // may deserialize from thrownProxy
480                 .setThrownProxy(thrownProxy) // avoid unnecessarily creating thrownProxy
481                 .setTimeMillis(timeMillis);
482     }
483 
484     private Message getNonNullImmutableMessage() {
485         return message != null ? message : new SimpleMessage(String.valueOf(messageText));
486     }
487 }