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.async;
18  
19  import java.io.IOException;
20  import java.util.Arrays;
21  import java.util.Map;
22  
23  import org.apache.logging.log4j.Level;
24  import org.apache.logging.log4j.Marker;
25  import org.apache.logging.log4j.ThreadContext.ContextStack;
26  import org.apache.logging.log4j.message.AsynchronouslyFormattable;
27  import org.apache.logging.log4j.util.ReadOnlyStringMap;
28  import org.apache.logging.log4j.core.LogEvent;
29  import org.apache.logging.log4j.core.impl.ContextDataFactory;
30  import org.apache.logging.log4j.core.impl.Log4jLogEvent;
31  import org.apache.logging.log4j.util.StringMap;
32  import org.apache.logging.log4j.core.impl.ThrowableProxy;
33  import org.apache.logging.log4j.core.util.Constants;
34  import org.apache.logging.log4j.message.Message;
35  import org.apache.logging.log4j.message.ParameterizedMessage;
36  import org.apache.logging.log4j.message.ReusableMessage;
37  import org.apache.logging.log4j.message.SimpleMessage;
38  import org.apache.logging.log4j.message.TimestampMessage;
39  import org.apache.logging.log4j.util.Strings;
40  
41  import com.lmax.disruptor.EventFactory;
42  
43  /**
44   * When the Disruptor is started, the RingBuffer is populated with event objects. These objects are then re-used during
45   * the life of the RingBuffer.
46   */
47  public class RingBufferLogEvent implements LogEvent, ReusableMessage, CharSequence {
48  
49      /** The {@code EventFactory} for {@code RingBufferLogEvent}s. */
50      public static final Factory FACTORY = new Factory();
51  
52      private static final long serialVersionUID = 8462119088943934758L;
53      private static final Message EMPTY = new SimpleMessage(Strings.EMPTY);
54  
55      /**
56       * Creates the events that will be put in the RingBuffer.
57       */
58      private static class Factory implements EventFactory<RingBufferLogEvent> {
59  
60          @Override
61          public RingBufferLogEvent newInstance() {
62              final RingBufferLogEvent result = new RingBufferLogEvent();
63              if (Constants.ENABLE_THREADLOCALS) {
64                  result.messageText = new StringBuilder(Constants.INITIAL_REUSABLE_MESSAGE_SIZE);
65                  result.parameters = new Object[10];
66              }
67              return result;
68          }
69      }
70  
71      private int threadPriority;
72      private long threadId;
73      private long currentTimeMillis;
74      private long nanoTime;
75      private short parameterCount;
76      private boolean includeLocation;
77      private boolean endOfBatch = false;
78      private Level level;
79      private String threadName;
80      private String loggerName;
81      private Message message;
82      private StringBuilder messageText;
83      private Object[] parameters;
84      private transient Throwable thrown;
85      private ThrowableProxy thrownProxy;
86      private StringMap contextData = ContextDataFactory.createContextData();
87      private Marker marker;
88      private String fqcn;
89      private StackTraceElement location;
90      private ContextStack contextStack;
91  
92      private transient AsyncLogger asyncLogger;
93  
94      public void setValues(final AsyncLogger anAsyncLogger, final String aLoggerName, final Marker aMarker,
95              final String theFqcn, final Level aLevel, final Message msg, final Throwable aThrowable,
96              final StringMap mutableContextData, final ContextStack aContextStack, final long threadId,
97              final String threadName, final int threadPriority, final StackTraceElement aLocation,
98              final long aCurrentTimeMillis, final long aNanoTime) {
99          this.threadPriority = threadPriority;
100         this.threadId = threadId;
101         this.currentTimeMillis = aCurrentTimeMillis;
102         this.nanoTime = aNanoTime;
103         this.level = aLevel;
104         this.threadName = threadName;
105         this.loggerName = aLoggerName;
106         setMessage(msg);
107         this.thrown = aThrowable;
108         this.thrownProxy = null;
109         this.marker = aMarker;
110         this.fqcn = theFqcn;
111         this.location = aLocation;
112         this.contextData = mutableContextData;
113         this.contextStack = aContextStack;
114         this.asyncLogger = anAsyncLogger;
115     }
116 
117     private void setMessage(final Message msg) {
118         if (msg instanceof ReusableMessage) {
119             final ReusableMessage reusable = (ReusableMessage) msg;
120             reusable.formatTo(getMessageTextForWriting());
121             if (parameters != null) {
122                 parameters = reusable.swapParameters(parameters);
123                 parameterCount = reusable.getParameterCount();
124             }
125         } else {
126             // if the Message instance is reused, there is no point in freezing its message here
127             if (msg != null && !canFormatMessageInBackground(msg)) {
128                 msg.getFormattedMessage(); // LOG4J2-763: ask message to freeze parameters
129             }
130             this.message = msg;
131         }
132     }
133 
134     private boolean canFormatMessageInBackground(final Message message) {
135         return Constants.FORMAT_MESSAGES_IN_BACKGROUND // LOG4J2-898: user wants to format all msgs in background
136                 || message.getClass().isAnnotationPresent(AsynchronouslyFormattable.class); // LOG4J2-1718
137     }
138 
139     private StringBuilder getMessageTextForWriting() {
140         if (messageText == null) {
141             // Should never happen:
142             // only happens if user logs a custom reused message when Constants.ENABLE_THREADLOCALS is false
143             messageText = new StringBuilder(Constants.INITIAL_REUSABLE_MESSAGE_SIZE);
144         }
145         messageText.setLength(0);
146         return messageText;
147     }
148 
149     /**
150      * Event processor that reads the event from the ringbuffer can call this method.
151      *
152      * @param endOfBatch flag to indicate if this is the last event in a batch from the RingBuffer
153      */
154     public void execute(final boolean endOfBatch) {
155         this.endOfBatch = endOfBatch;
156         asyncLogger.actualAsyncLog(this);
157     }
158 
159     /**
160      * Returns {@code true} if this event is the end of a batch, {@code false} otherwise.
161      *
162      * @return {@code true} if this event is the end of a batch, {@code false} otherwise
163      */
164     @Override
165     public boolean isEndOfBatch() {
166         return endOfBatch;
167     }
168 
169     @Override
170     public void setEndOfBatch(final boolean endOfBatch) {
171         this.endOfBatch = endOfBatch;
172     }
173 
174     @Override
175     public boolean isIncludeLocation() {
176         return includeLocation;
177     }
178 
179     @Override
180     public void setIncludeLocation(final boolean includeLocation) {
181         this.includeLocation = includeLocation;
182     }
183 
184     @Override
185     public String getLoggerName() {
186         return loggerName;
187     }
188 
189     @Override
190     public Marker getMarker() {
191         return marker;
192     }
193 
194     @Override
195     public String getLoggerFqcn() {
196         return fqcn;
197     }
198 
199     @Override
200     public Level getLevel() {
201         if (level == null) {
202             level = Level.OFF; // LOG4J2-462, LOG4J2-465
203         }
204         return level;
205     }
206 
207     @Override
208     public Message getMessage() {
209         if (message == null) {
210             return messageText == null ? EMPTY : this;
211         }
212         return message;
213     }
214 
215     /**
216      * @see ReusableMessage#getFormattedMessage()
217      */
218     @Override
219     public String getFormattedMessage() {
220         return messageText != null // LOG4J2-1527: may be null in web apps
221                 ? messageText.toString() // note: please keep below "redundant" braces for readability
222                 : (message == null ? null : message.getFormattedMessage());
223     }
224 
225     /**
226      * @see ReusableMessage#getFormat()
227      */
228     @Override
229     public String getFormat() {
230         return null;
231     }
232 
233     /**
234      * @see ReusableMessage#getParameters()
235      */
236     @Override
237     public Object[] getParameters() {
238         return parameters == null ? null : Arrays.copyOf(parameters, parameterCount);
239     }
240 
241     /**
242      * @see ReusableMessage#getThrowable()
243      */
244     @Override
245     public Throwable getThrowable() {
246         return getThrown();
247     }
248 
249     /**
250      * @see ReusableMessage#formatTo(StringBuilder)
251      */
252     @Override
253     public void formatTo(final StringBuilder buffer) {
254         buffer.append(messageText);
255     }
256 
257     /**
258      * Replaces this ReusableMessage's parameter array with the specified value and return the original array
259      * @param emptyReplacement the parameter array that can be used for subsequent uses of this reusable message
260      * @return the original parameter array
261      * @see ReusableMessage#swapParameters(Object[])
262      */
263     @Override
264     public Object[] swapParameters(final Object[] emptyReplacement) {
265         final Object[] result = this.parameters;
266         this.parameters = emptyReplacement;
267         return result;
268     }
269 
270     /*
271      * @see ReusableMessage#getParameterCount
272      */
273     @Override
274     public short getParameterCount() {
275         return parameterCount;
276     }
277 
278     @Override
279     public Message memento() {
280         if (message != null) {
281             return message;
282         }
283         final Object[] params = parameters == null ? new Object[0] : Arrays.copyOf(parameters, parameterCount);
284         return new ParameterizedMessage(messageText.toString(), params);
285     }
286 
287     // CharSequence impl
288 
289     @Override
290     public int length() {
291         return messageText.length();
292     }
293 
294     @Override
295     public char charAt(final int index) {
296         return messageText.charAt(index);
297     }
298 
299     @Override
300     public CharSequence subSequence(final int start, final int end) {
301         return messageText.subSequence(start, end);
302     }
303 
304 
305     private Message getNonNullImmutableMessage() {
306         return message != null ? message : new SimpleMessage(String.valueOf(messageText));
307     }
308 
309     @Override
310     public Throwable getThrown() {
311         // after deserialization, thrown is null but thrownProxy may be non-null
312         if (thrown == null) {
313             if (thrownProxy != null) {
314                 thrown = thrownProxy.getThrowable();
315             }
316         }
317         return thrown;
318     }
319 
320     @Override
321     public ThrowableProxy getThrownProxy() {
322         // lazily instantiate the (expensive) ThrowableProxy
323         if (thrownProxy == null) {
324             if (thrown != null) {
325                 thrownProxy = new ThrowableProxy(thrown);
326             }
327         }
328         return this.thrownProxy;
329     }
330 
331     @SuppressWarnings("unchecked")
332     @Override
333     public ReadOnlyStringMap getContextData() {
334         return contextData;
335     }
336 
337     void setContextData(final StringMap contextData) {
338         this.contextData = contextData;
339     }
340 
341     @SuppressWarnings("unchecked")
342     @Override
343     public Map<String, String> getContextMap() {
344         return contextData.toMap();
345     }
346 
347     @Override
348     public ContextStack getContextStack() {
349         return contextStack;
350     }
351 
352     @Override
353     public long getThreadId() {
354         return threadId;
355     }
356 
357     @Override
358     public String getThreadName() {
359         return threadName;
360     }
361 
362     @Override
363     public int getThreadPriority() {
364         return threadPriority;
365     }
366 
367     @Override
368     public StackTraceElement getSource() {
369         return location;
370     }
371 
372     @Override
373     public long getTimeMillis() {
374         return message instanceof TimestampMessage ? ((TimestampMessage) message).getTimestamp() :currentTimeMillis;
375     }
376 
377     @Override
378     public long getNanoTime() {
379         return nanoTime;
380     }
381 
382     /**
383      * Release references held by ring buffer to allow objects to be garbage-collected.
384      */
385     public void clear() {
386         this.asyncLogger = null;
387         this.loggerName = null;
388         this.marker = null;
389         this.fqcn = null;
390         this.level = null;
391         this.message = null;
392         this.thrown = null;
393         this.thrownProxy = null;
394         this.contextStack = null;
395         this.location = null;
396         if (contextData != null) {
397             if (contextData.isFrozen()) { // came from CopyOnWrite thread context
398                 contextData = null;
399             } else {
400                 contextData.clear();
401             }
402         }
403 
404         trimMessageText();
405 
406         if (parameters != null) {
407             for (int i = 0; i < parameters.length; i++) {
408                 parameters[i] = null;
409             }
410         }
411     }
412 
413     // ensure that excessively long char[] arrays are not kept in memory forever
414     private void trimMessageText() {
415         if (messageText != null && messageText.length() > Constants.MAX_REUSABLE_MESSAGE_SIZE) {
416             messageText.setLength(Constants.MAX_REUSABLE_MESSAGE_SIZE);
417             messageText.trimToSize();
418         }
419     }
420 
421     private void writeObject(final java.io.ObjectOutputStream out) throws IOException {
422         getThrownProxy(); // initialize the ThrowableProxy before serializing
423         out.defaultWriteObject();
424     }
425 
426     /**
427      * Creates and returns a new immutable copy of this {@code RingBufferLogEvent}.
428      *
429      * @return a new immutable copy of the data in this {@code RingBufferLogEvent}
430      */
431     public LogEvent createMemento() {
432         final LogEvent result = new Log4jLogEvent.Builder(this).build();
433         return result;
434     }
435 
436     /**
437      * Initializes the specified {@code Log4jLogEvent.Builder} from this {@code RingBufferLogEvent}.
438      * @param builder the builder whose fields to populate
439      */
440     public void initializeBuilder(final Log4jLogEvent.Builder builder) {
441         builder.setContextData(contextData) //
442                 .setContextStack(contextStack) //
443                 .setEndOfBatch(endOfBatch) //
444                 .setIncludeLocation(includeLocation) //
445                 .setLevel(getLevel()) // ensure non-null
446                 .setLoggerFqcn(fqcn) //
447                 .setLoggerName(loggerName) //
448                 .setMarker(marker) //
449                 .setMessage(getNonNullImmutableMessage()) // ensure non-null & immutable
450                 .setNanoTime(nanoTime) //
451                 .setSource(location) //
452                 .setThreadId(threadId) //
453                 .setThreadName(threadName) //
454                 .setThreadPriority(threadPriority) //
455                 .setThrown(getThrown()) // may deserialize from thrownProxy
456                 .setThrownProxy(thrownProxy) // avoid unnecessarily creating thrownProxy
457                 .setTimeMillis(currentTimeMillis);
458     }
459 
460 }