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.Message;
30  import org.apache.logging.log4j.message.ParameterizedMessage;
31  import org.apache.logging.log4j.message.ReusableMessage;
32  import org.apache.logging.log4j.message.SimpleMessage;
33  import org.apache.logging.log4j.util.Strings;
34  
35  /**
36   * Mutable implementation of the {@code LogEvent} interface.
37   * @since 2.6
38   */
39  public class MutableLogEvent implements LogEvent, ReusableMessage {
40      private static final Message EMPTY = new SimpleMessage(Strings.EMPTY);
41  
42      private int threadPriority;
43      private long threadId;
44      private long timeMillis;
45      private long nanoTime;
46      private short parameterCount;
47      private boolean includeLocation;
48      private boolean endOfBatch = false;
49      private Level level;
50      private String threadName;
51      private String loggerName;
52      private Message message;
53      private StringBuilder messageText;
54      private Object[] parameters;
55      private Throwable thrown;
56      private ThrowableProxy thrownProxy;
57      private Map<String, String> contextMap;
58      private Marker marker;
59      private String loggerFqcn;
60      private StackTraceElement source;
61      private ThreadContext.ContextStack contextStack;
62  
63      public MutableLogEvent() {
64          this(new StringBuilder(Constants.INITIAL_REUSABLE_MESSAGE_SIZE), new Object[10]);
65      }
66  
67      public MutableLogEvent(final StringBuilder msgText, final Object[] replacementParameters) {
68          this.messageText = msgText;
69          this.parameters = replacementParameters;
70      }
71  
72      /**
73       * Initialize the fields of this {@code MutableLogEvent} from another event.
74       * Similar in purpose and usage as {@link org.apache.logging.log4j.core.impl.Log4jLogEvent.LogEventProxy},
75       * but a mutable version.
76       * <p>
77       * This method is used on async logger ringbuffer slots holding MutableLogEvent objects in each slot.
78       * </p>
79       *
80       * @param event the event to copy data from
81       */
82      public void initFrom(final LogEvent event) {
83          this.loggerFqcn = event.getLoggerFqcn();
84          this.marker = event.getMarker();
85          this.level = event.getLevel();
86          this.loggerName = event.getLoggerName();
87          this.timeMillis = event.getTimeMillis();
88          this.thrown = event.getThrown();
89          this.thrownProxy = event.getThrownProxy();
90          this.contextMap = event.getContextMap();
91          this.contextStack = event.getContextStack();
92          this.source = event.isIncludeLocation() ? event.getSource() : null;
93          this.threadId = event.getThreadId();
94          this.threadName = event.getThreadName();
95          this.threadPriority = event.getThreadPriority();
96          this.endOfBatch = event.isEndOfBatch();
97          this.includeLocation = event.isIncludeLocation();
98          this.nanoTime = event.getNanoTime();
99          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 }