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