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 final 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(final ParameterConsumer<S> action, final 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             message = new MementoMessage(String.valueOf(messageText), messageFormat, getParameters());
308         }
309         return message;
310     }
311 
312     @Override
313     public Throwable getThrown() {
314         return thrown;
315     }
316 
317     public void setThrown(final Throwable thrown) {
318         this.thrown = thrown;
319     }
320 
321     void initTime(final Clock clock, final NanoClock nanoClock) {
322         if (message instanceof TimestampMessage) {
323             instant.initFromEpochMilli(((TimestampMessage) message).getTimestamp(), 0);
324         } else {
325             instant.initFrom(clock);
326         }
327         nanoTime = nanoClock.nanoTime();
328     }
329 
330     @Override
331     public long getTimeMillis() {
332         return instant.getEpochMillisecond();
333     }
334 
335     public void setTimeMillis(final long timeMillis) {
336         this.instant.initFromEpochMilli(timeMillis, 0);
337     }
338 
339     @Override
340     public Instant getInstant() {
341         return instant;
342     }
343 
344     /**
345      * Returns the ThrowableProxy associated with the event, or null.
346      * @return The ThrowableProxy associated with the event.
347      */
348     @Override
349     public ThrowableProxy getThrownProxy() {
350         if (thrownProxy == null && thrown != null) {
351             thrownProxy = new ThrowableProxy(thrown);
352         }
353         return thrownProxy;
354     }
355 
356     public void setSource(StackTraceElement source) {
357         this.source = source;
358     }
359 
360     /**
361      * Returns the StackTraceElement for the caller. This will be the entry that occurs right
362      * before the first occurrence of FQCN as a class name.
363      * @return the StackTraceElement for the caller.
364      */
365     @Override
366     public StackTraceElement getSource() {
367         if (source != null) {
368             return source;
369         }
370         if (loggerFqcn == null || !includeLocation) {
371             return null;
372         }
373         source = StackLocatorUtil.calcLocation(loggerFqcn);
374         return source;
375     }
376 
377     @SuppressWarnings("unchecked")
378     @Override
379     public ReadOnlyStringMap getContextData() {
380         return contextData;
381     }
382 
383     @Override
384     public Map<String, String> getContextMap() {
385         return contextData.toMap();
386     }
387 
388     public void setContextData(final StringMap mutableContextData) {
389         this.contextData = mutableContextData;
390     }
391 
392     @Override
393     public ThreadContext.ContextStack getContextStack() {
394         return contextStack;
395     }
396 
397     public void setContextStack(final ThreadContext.ContextStack contextStack) {
398         this.contextStack = contextStack;
399     }
400 
401     @Override
402     public long getThreadId() {
403         return threadId;
404     }
405 
406     public void setThreadId(final long threadId) {
407         this.threadId = threadId;
408     }
409 
410     @Override
411     public String getThreadName() {
412         return threadName;
413     }
414 
415     public void setThreadName(final String threadName) {
416         this.threadName = threadName;
417     }
418 
419     @Override
420     public int getThreadPriority() {
421         return threadPriority;
422     }
423 
424     public void setThreadPriority(final int threadPriority) {
425         this.threadPriority = threadPriority;
426     }
427 
428     @Override
429     public boolean isIncludeLocation() {
430         return includeLocation;
431     }
432 
433     @Override
434     public void setIncludeLocation(final boolean includeLocation) {
435         this.includeLocation = includeLocation;
436     }
437 
438     @Override
439     public boolean isEndOfBatch() {
440         return endOfBatch;
441     }
442 
443     @Override
444     public void setEndOfBatch(final boolean endOfBatch) {
445         this.endOfBatch = endOfBatch;
446     }
447 
448     @Override
449     public long getNanoTime() {
450         return nanoTime;
451     }
452 
453     public void setNanoTime(final long nanoTime) {
454         this.nanoTime = nanoTime;
455     }
456 
457     /**
458      * Creates a LogEventProxy that can be serialized.
459      * @return a LogEventProxy.
460      */
461     protected Object writeReplace() {
462         return new Log4jLogEvent.LogEventProxy(this, this.includeLocation);
463     }
464 
465     private void readObject(final ObjectInputStream stream) throws InvalidObjectException {
466         throw new InvalidObjectException("Proxy required");
467     }
468 
469     /**
470      * Creates and returns a new immutable copy of this {@code MutableLogEvent}.
471      * If {@link #isIncludeLocation()} is true, this will obtain caller location information.
472      *
473      * @return a new immutable copy of the data in this {@code MutableLogEvent}
474      */
475     public Log4jLogEvent createMemento() {
476         return Log4jLogEvent.deserialize(Log4jLogEvent.serialize(this, includeLocation));
477     }
478 
479     /**
480      * Initializes the specified {@code Log4jLogEvent.Builder} from this {@code MutableLogEvent}.
481      * @param builder the builder whose fields to populate
482      */
483     public void initializeBuilder(final Log4jLogEvent.Builder builder) {
484         builder.setContextData(contextData) //
485                 .setContextStack(contextStack) //
486                 .setEndOfBatch(endOfBatch) //
487                 .setIncludeLocation(includeLocation) //
488                 .setLevel(getLevel()) // ensure non-null
489                 .setLoggerFqcn(loggerFqcn) //
490                 .setLoggerName(loggerName) //
491                 .setMarker(marker) //
492                 .setMessage(memento()) // ensure non-null & immutable
493                 .setNanoTime(nanoTime) //
494                 .setSource(source) //
495                 .setThreadId(threadId) //
496                 .setThreadName(threadName) //
497                 .setThreadPriority(threadPriority) //
498                 .setThrown(getThrown()) // may deserialize from thrownProxy
499                 .setThrownProxy(thrownProxy) // avoid unnecessarily creating thrownProxy
500                 .setInstant(instant) //
501         ;
502     }
503 }