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