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.async.InternalAsyncUtil;
29  import org.apache.logging.log4j.core.util.*;
30  import org.apache.logging.log4j.core.time.Instant;
31  import org.apache.logging.log4j.core.time.MutableInstant;
32  import org.apache.logging.log4j.message.*;
33  import org.apache.logging.log4j.util.ReadOnlyStringMap;
34  import org.apache.logging.log4j.util.StackLocatorUtil;
35  import org.apache.logging.log4j.util.StringBuilders;
36  import org.apache.logging.log4j.util.StringMap;
37  import org.apache.logging.log4j.util.Strings;
38  
39  /**
40   * Mutable implementation of the {@code LogEvent} interface.
41   * @since 2.6
42   */
43  public class MutableLogEvent implements LogEvent, ReusableMessage, ParameterVisitable {
44      private static final Message EMPTY = new SimpleMessage(Strings.EMPTY);
45  
46      private int threadPriority;
47      private long threadId;
48      private MutableInstant instant = new MutableInstant();
49      private long nanoTime;
50      private short parameterCount;
51      private boolean includeLocation;
52      private boolean endOfBatch = false;
53      private Level level;
54      private String threadName;
55      private String loggerName;
56      private Message message;
57      private String messageFormat;
58      private StringBuilder messageText;
59      private Object[] parameters;
60      private Throwable thrown;
61      private ThrowableProxy thrownProxy;
62      private StringMap contextData = ContextDataFactory.createContextData();
63      private Marker marker;
64      private String loggerFqcn;
65      private StackTraceElement source;
66      private ThreadContext.ContextStack contextStack;
67      transient boolean reserved = false;
68  
69      public MutableLogEvent() {
70          this(new StringBuilder(Constants.INITIAL_REUSABLE_MESSAGE_SIZE), new Object[10]);
71      }
72  
73      public MutableLogEvent(final StringBuilder msgText, final Object[] replacementParameters) {
74          this.messageText = msgText;
75          this.parameters = replacementParameters;
76      }
77  
78      @Override
79      public Log4jLogEvent toImmutable() {
80          return createMemento();
81      }
82  
83      /**
84       * Initialize the fields of this {@code MutableLogEvent} from another event.
85       * Similar in purpose and usage as {@link org.apache.logging.log4j.core.impl.Log4jLogEvent.LogEventProxy},
86       * but a mutable version.
87       * <p>
88       * This method is used on async logger ringbuffer slots holding MutableLogEvent objects in each slot.
89       * </p>
90       *
91       * @param event the event to copy data from
92       */
93      public void initFrom(final LogEvent event) {
94          this.loggerFqcn = event.getLoggerFqcn();
95          this.marker = event.getMarker();
96          this.level = event.getLevel();
97          this.loggerName = event.getLoggerName();
98          this.thrown = event.getThrown();
99          this.thrownProxy = event.getThrownProxy();
100 
101         this.instant.initFrom(event.getInstant());
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         messageFormat = null;
129         thrown = null;
130         thrownProxy = null;
131         source = null;
132         if (contextData != null) {
133             if (contextData.isFrozen()) { // came from CopyOnWrite thread context
134                 contextData = null;
135             } else {
136                 contextData.clear();
137             }
138         }
139         contextStack = null;
140 
141         // ThreadName should not be cleared: this field is set in the ReusableLogEventFactory
142         // where this instance is kept in a ThreadLocal, so it usually does not change.
143         // threadName = null; // no need to clear threadName
144 
145         // ensure that excessively long char[] arrays are not kept in memory forever
146         StringBuilders.trimToMaxSize(messageText, Constants.MAX_REUSABLE_MESSAGE_SIZE);
147 
148         if (parameters != null) {
149             for (int i = 0; i < parameters.length; i++) {
150                 parameters[i] = null;
151             }
152         }
153 
154         // primitive fields that cannot be cleared:
155         //timeMillis;
156         //threadId;
157         //threadPriority;
158         //includeLocation;
159         //endOfBatch;
160         //nanoTime;
161     }
162 
163     @Override
164     public String getLoggerFqcn() {
165         return loggerFqcn;
166     }
167 
168     public void setLoggerFqcn(final String loggerFqcn) {
169         this.loggerFqcn = loggerFqcn;
170     }
171 
172     @Override
173     public Marker getMarker() {
174         return marker;
175     }
176 
177     public void setMarker(final Marker marker) {
178         this.marker = marker;
179     }
180 
181     @Override
182     public Level getLevel() {
183         if (level == null) {
184             level = Level.OFF; // LOG4J2-462, LOG4J2-465
185         }
186         return level;
187     }
188 
189     public void setLevel(final Level level) {
190         this.level = level;
191     }
192 
193     @Override
194     public String getLoggerName() {
195         return loggerName;
196     }
197 
198     public void setLoggerName(final String loggerName) {
199         this.loggerName = loggerName;
200     }
201 
202     @Override
203     public Message getMessage() {
204         if (message == null) {
205             return messageText == null ? EMPTY : this;
206         }
207         return message;
208     }
209 
210     public void setMessage(final Message msg) {
211         if (msg instanceof ReusableMessage) {
212             final ReusableMessage reusable = (ReusableMessage) msg;
213             reusable.formatTo(getMessageTextForWriting());
214             this.messageFormat = msg.getFormat();
215             if (parameters != null) {
216                 parameters = reusable.swapParameters(parameters);
217                 parameterCount = reusable.getParameterCount();
218             }
219         } else {
220             this.message = InternalAsyncUtil.makeMessageImmutable(msg);
221         }
222     }
223 
224     private StringBuilder getMessageTextForWriting() {
225         if (messageText == null) {
226             // Should never happen:
227             // only happens if user logs a custom reused message when Constants.ENABLE_THREADLOCALS is false
228             messageText = new StringBuilder(Constants.INITIAL_REUSABLE_MESSAGE_SIZE);
229         }
230         messageText.setLength(0);
231         return messageText;
232     }
233 
234     /**
235      * @see ReusableMessage#getFormattedMessage()
236      */
237     @Override
238     public String getFormattedMessage() {
239         return messageText.toString();
240     }
241 
242     /**
243      * @see ReusableMessage#getFormat()
244      */
245     @Override
246     public String getFormat() {
247         return messageFormat;
248     }
249 
250     /**
251      * @see ReusableMessage#getParameters()
252      */
253     @Override
254     public Object[] getParameters() {
255         return parameters == null ? null : Arrays.copyOf(parameters, parameterCount);
256     }
257 
258     @Override
259     public <S> void forEachParameter(ParameterConsumer<S> action, S state) {
260         if (parameters != null) {
261             for (short i = 0; i < parameterCount; i++) {
262                 action.accept(parameters[i], i, state);
263             }
264         }
265     }
266 
267     /**
268      * @see ReusableMessage#getThrowable()
269      */
270     @Override
271     public Throwable getThrowable() {
272         return getThrown();
273     }
274 
275     /**
276      * @see ReusableMessage#formatTo(StringBuilder)
277      */
278     @Override
279     public void formatTo(final StringBuilder buffer) {
280         buffer.append(messageText);
281     }
282 
283     /**
284      * Replaces this ReusableMessage's parameter array with the specified value and return the original array
285      * @param emptyReplacement the parameter array that can be used for subsequent uses of this reusable message
286      * @return the original parameter array
287      * @see ReusableMessage#swapParameters(Object[])
288      */
289     @Override
290     public Object[] swapParameters(final Object[] emptyReplacement) {
291         final Object[] result = this.parameters;
292         this.parameters = emptyReplacement;
293         return result;
294     }
295 
296     /*
297      * @see ReusableMessage#getParameterCount
298      */
299     @Override
300     public short getParameterCount() {
301         return parameterCount;
302     }
303 
304     @Override
305     public Message memento() {
306         if (message != null) {
307             return message;
308         }
309         final Object[] params = parameters == null ? new Object[0] : Arrays.copyOf(parameters, parameterCount);
310         return new ParameterizedMessage(messageText.toString(), params);
311     }
312 
313     @Override
314     public Throwable getThrown() {
315         return thrown;
316     }
317 
318     public void setThrown(final Throwable thrown) {
319         this.thrown = thrown;
320     }
321 
322     void initTime(final Clock clock, final NanoClock nanoClock) {
323         if (message instanceof TimestampMessage) {
324             instant.initFromEpochMilli(((TimestampMessage) message).getTimestamp(), 0);
325         } else {
326             instant.initFrom(clock);
327         }
328         nanoTime = nanoClock.nanoTime();
329     }
330 
331     @Override
332     public long getTimeMillis() {
333         return instant.getEpochMillisecond();
334     }
335 
336     public void setTimeMillis(final long timeMillis) {
337         this.instant.initFromEpochMilli(timeMillis, 0);
338     }
339 
340     @Override
341     public Instant getInstant() {
342         return instant;
343     }
344 
345     /**
346      * Returns the ThrowableProxy associated with the event, or null.
347      * @return The ThrowableProxy associated with the event.
348      */
349     @Override
350     public ThrowableProxy getThrownProxy() {
351         if (thrownProxy == null && thrown != null) {
352             thrownProxy = new ThrowableProxy(thrown);
353         }
354         return thrownProxy;
355     }
356 
357     /**
358      * Returns the StackTraceElement for the caller. This will be the entry that occurs right
359      * before the first occurrence of FQCN as a class name.
360      * @return the StackTraceElement for the caller.
361      */
362     @Override
363     public StackTraceElement getSource() {
364         if (source != null) {
365             return source;
366         }
367         if (loggerFqcn == null || !includeLocation) {
368             return null;
369         }
370         source = StackLocatorUtil.calcLocation(loggerFqcn);
371         return source;
372     }
373 
374     @SuppressWarnings("unchecked")
375     @Override
376     public ReadOnlyStringMap getContextData() {
377         return contextData;
378     }
379 
380     @Override
381     public Map<String, String> getContextMap() {
382         return contextData.toMap();
383     }
384 
385     public void setContextData(final StringMap mutableContextData) {
386         this.contextData = mutableContextData;
387     }
388 
389     @Override
390     public ThreadContext.ContextStack getContextStack() {
391         return contextStack;
392     }
393 
394     public void setContextStack(final ThreadContext.ContextStack contextStack) {
395         this.contextStack = contextStack;
396     }
397 
398     @Override
399     public long getThreadId() {
400         return threadId;
401     }
402 
403     public void setThreadId(final long threadId) {
404         this.threadId = threadId;
405     }
406 
407     @Override
408     public String getThreadName() {
409         return threadName;
410     }
411 
412     public void setThreadName(final String threadName) {
413         this.threadName = threadName;
414     }
415 
416     @Override
417     public int getThreadPriority() {
418         return threadPriority;
419     }
420 
421     public void setThreadPriority(final int threadPriority) {
422         this.threadPriority = threadPriority;
423     }
424 
425     @Override
426     public boolean isIncludeLocation() {
427         return includeLocation;
428     }
429 
430     @Override
431     public void setIncludeLocation(final boolean includeLocation) {
432         this.includeLocation = includeLocation;
433     }
434 
435     @Override
436     public boolean isEndOfBatch() {
437         return endOfBatch;
438     }
439 
440     @Override
441     public void setEndOfBatch(final boolean endOfBatch) {
442         this.endOfBatch = endOfBatch;
443     }
444 
445     @Override
446     public long getNanoTime() {
447         return nanoTime;
448     }
449 
450     public void setNanoTime(final long nanoTime) {
451         this.nanoTime = nanoTime;
452     }
453 
454     /**
455      * Creates a LogEventProxy that can be serialized.
456      * @return a LogEventProxy.
457      */
458     protected Object writeReplace() {
459         return new Log4jLogEvent.LogEventProxy(this, this.includeLocation);
460     }
461 
462     private void readObject(final ObjectInputStream stream) throws InvalidObjectException {
463         throw new InvalidObjectException("Proxy required");
464     }
465 
466     /**
467      * Creates and returns a new immutable copy of this {@code MutableLogEvent}.
468      * If {@link #isIncludeLocation()} is true, this will obtain caller location information.
469      *
470      * @return a new immutable copy of the data in this {@code MutableLogEvent}
471      */
472     public Log4jLogEvent createMemento() {
473         return Log4jLogEvent.deserialize(Log4jLogEvent.serialize(this, includeLocation));
474     }
475 
476     /**
477      * Initializes the specified {@code Log4jLogEvent.Builder} from this {@code MutableLogEvent}.
478      * @param builder the builder whose fields to populate
479      */
480     public void initializeBuilder(final Log4jLogEvent.Builder builder) {
481         builder.setContextData(contextData) //
482                 .setContextStack(contextStack) //
483                 .setEndOfBatch(endOfBatch) //
484                 .setIncludeLocation(includeLocation) //
485                 .setLevel(getLevel()) // ensure non-null
486                 .setLoggerFqcn(loggerFqcn) //
487                 .setLoggerName(loggerName) //
488                 .setMarker(marker) //
489                 .setMessage(getNonNullImmutableMessage()) // ensure non-null & immutable
490                 .setNanoTime(nanoTime) //
491                 .setSource(source) //
492                 .setThreadId(threadId) //
493                 .setThreadName(threadName) //
494                 .setThreadPriority(threadPriority) //
495                 .setThrown(getThrown()) // may deserialize from thrownProxy
496                 .setThrownProxy(thrownProxy) // avoid unnecessarily creating thrownProxy
497                 .setInstant(instant) //
498         ;
499     }
500 
501     private Message getNonNullImmutableMessage() {
502         return message != null ? message : new SimpleMessage(String.valueOf(messageText));
503     }
504 }