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