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.Message;
32  import org.apache.logging.log4j.message.ParameterizedMessage;
33  import org.apache.logging.log4j.message.ReusableMessage;
34  import org.apache.logging.log4j.message.SimpleMessage;
35  import org.apache.logging.log4j.message.TimestampMessage;
36  import org.apache.logging.log4j.util.ReadOnlyStringMap;
37  import org.apache.logging.log4j.util.StringBuilders;
38  import org.apache.logging.log4j.util.StringMap;
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     @Override
118     public LogEvent toImmutable() {
119         return createMemento();
120     }
121 
122     private void setMessage(final Message msg) {
123         if (msg instanceof ReusableMessage) {
124             final ReusableMessage reusable = (ReusableMessage) msg;
125             reusable.formatTo(getMessageTextForWriting());
126             if (parameters != null) {
127                 parameters = reusable.swapParameters(parameters);
128                 parameterCount = reusable.getParameterCount();
129             }
130         } else {
131             this.message = InternalAsyncUtil.makeMessageImmutable(msg);
132         }
133     }
134 
135     private StringBuilder getMessageTextForWriting() {
136         if (messageText == null) {
137             // Should never happen:
138             // only happens if user logs a custom reused message when Constants.ENABLE_THREADLOCALS is false
139             messageText = new StringBuilder(Constants.INITIAL_REUSABLE_MESSAGE_SIZE);
140         }
141         messageText.setLength(0);
142         return messageText;
143     }
144 
145     /**
146      * Event processor that reads the event from the ringbuffer can call this method.
147      *
148      * @param endOfBatch flag to indicate if this is the last event in a batch from the RingBuffer
149      */
150     public void execute(final boolean endOfBatch) {
151         this.endOfBatch = endOfBatch;
152         asyncLogger.actualAsyncLog(this);
153     }
154 
155     /**
156      * Returns {@code true} if this event is the end of a batch, {@code false} otherwise.
157      *
158      * @return {@code true} if this event is the end of a batch, {@code false} otherwise
159      */
160     @Override
161     public boolean isEndOfBatch() {
162         return endOfBatch;
163     }
164 
165     @Override
166     public void setEndOfBatch(final boolean endOfBatch) {
167         this.endOfBatch = endOfBatch;
168     }
169 
170     @Override
171     public boolean isIncludeLocation() {
172         return includeLocation;
173     }
174 
175     @Override
176     public void setIncludeLocation(final boolean includeLocation) {
177         this.includeLocation = includeLocation;
178     }
179 
180     @Override
181     public String getLoggerName() {
182         return loggerName;
183     }
184 
185     @Override
186     public Marker getMarker() {
187         return marker;
188     }
189 
190     @Override
191     public String getLoggerFqcn() {
192         return fqcn;
193     }
194 
195     @Override
196     public Level getLevel() {
197         if (level == null) {
198             level = Level.OFF; // LOG4J2-462, LOG4J2-465
199         }
200         return level;
201     }
202 
203     @Override
204     public Message getMessage() {
205         if (message == null) {
206             return messageText == null ? EMPTY : this;
207         }
208         return message;
209     }
210 
211     /**
212      * @see ReusableMessage#getFormattedMessage()
213      */
214     @Override
215     public String getFormattedMessage() {
216         return messageText != null // LOG4J2-1527: may be null in web apps
217                 ? messageText.toString() // note: please keep below "redundant" braces for readability
218                 : (message == null ? null : message.getFormattedMessage());
219     }
220 
221     /**
222      * @see ReusableMessage#getFormat()
223      */
224     @Override
225     public String getFormat() {
226         return null;
227     }
228 
229     /**
230      * @see ReusableMessage#getParameters()
231      */
232     @Override
233     public Object[] getParameters() {
234         return parameters == null ? null : Arrays.copyOf(parameters, parameterCount);
235     }
236 
237     /**
238      * @see ReusableMessage#getThrowable()
239      */
240     @Override
241     public Throwable getThrowable() {
242         return getThrown();
243     }
244 
245     /**
246      * @see ReusableMessage#formatTo(StringBuilder)
247      */
248     @Override
249     public void formatTo(final StringBuilder buffer) {
250         buffer.append(messageText);
251     }
252 
253     /**
254      * Replaces this ReusableMessage's parameter array with the specified value and return the original array
255      * @param emptyReplacement the parameter array that can be used for subsequent uses of this reusable message
256      * @return the original parameter array
257      * @see ReusableMessage#swapParameters(Object[])
258      */
259     @Override
260     public Object[] swapParameters(final Object[] emptyReplacement) {
261         final Object[] result = this.parameters;
262         this.parameters = emptyReplacement;
263         return result;
264     }
265 
266     /*
267      * @see ReusableMessage#getParameterCount
268      */
269     @Override
270     public short getParameterCount() {
271         return parameterCount;
272     }
273 
274     @Override
275     public Message memento() {
276         if (message != null) {
277             return message;
278         }
279         final Object[] params = parameters == null ? new Object[0] : Arrays.copyOf(parameters, parameterCount);
280         return new ParameterizedMessage(messageText.toString(), params);
281     }
282 
283     // CharSequence impl
284 
285     @Override
286     public int length() {
287         return messageText.length();
288     }
289 
290     @Override
291     public char charAt(final int index) {
292         return messageText.charAt(index);
293     }
294 
295     @Override
296     public CharSequence subSequence(final int start, final int end) {
297         return messageText.subSequence(start, end);
298     }
299 
300 
301     private Message getNonNullImmutableMessage() {
302         return message != null ? message : new SimpleMessage(String.valueOf(messageText));
303     }
304 
305     @Override
306     public Throwable getThrown() {
307         // after deserialization, thrown is null but thrownProxy may be non-null
308         if (thrown == null) {
309             if (thrownProxy != null) {
310                 thrown = thrownProxy.getThrowable();
311             }
312         }
313         return thrown;
314     }
315 
316     @Override
317     public ThrowableProxy getThrownProxy() {
318         // lazily instantiate the (expensive) ThrowableProxy
319         if (thrownProxy == null) {
320             if (thrown != null) {
321                 thrownProxy = new ThrowableProxy(thrown);
322             }
323         }
324         return this.thrownProxy;
325     }
326 
327     @SuppressWarnings("unchecked")
328     @Override
329     public ReadOnlyStringMap getContextData() {
330         return contextData;
331     }
332 
333     void setContextData(final StringMap contextData) {
334         this.contextData = contextData;
335     }
336 
337     @SuppressWarnings("unchecked")
338     @Override
339     public Map<String, String> getContextMap() {
340         return contextData.toMap();
341     }
342 
343     @Override
344     public ContextStack getContextStack() {
345         return contextStack;
346     }
347 
348     @Override
349     public long getThreadId() {
350         return threadId;
351     }
352 
353     @Override
354     public String getThreadName() {
355         return threadName;
356     }
357 
358     @Override
359     public int getThreadPriority() {
360         return threadPriority;
361     }
362 
363     @Override
364     public StackTraceElement getSource() {
365         return location;
366     }
367 
368     @Override
369     public long getTimeMillis() {
370         return message instanceof TimestampMessage ? ((TimestampMessage) message).getTimestamp() :currentTimeMillis;
371     }
372 
373     @Override
374     public long getNanoTime() {
375         return nanoTime;
376     }
377 
378     /**
379      * Release references held by ring buffer to allow objects to be garbage-collected.
380      */
381     public void clear() {
382         this.asyncLogger = null;
383         this.loggerName = null;
384         this.marker = null;
385         this.fqcn = null;
386         this.level = null;
387         this.message = null;
388         this.thrown = null;
389         this.thrownProxy = null;
390         this.contextStack = null;
391         this.location = null;
392         if (contextData != null) {
393             if (contextData.isFrozen()) { // came from CopyOnWrite thread context
394                 contextData = null;
395             } else {
396                 contextData.clear();
397             }
398         }
399 
400         // ensure that excessively long char[] arrays are not kept in memory forever
401         StringBuilders.trimToMaxSize(messageText, Constants.MAX_REUSABLE_MESSAGE_SIZE);
402 
403         if (parameters != null) {
404             for (int i = 0; i < parameters.length; i++) {
405                 parameters[i] = null;
406             }
407         }
408     }
409 
410     private void writeObject(final java.io.ObjectOutputStream out) throws IOException {
411         getThrownProxy(); // initialize the ThrowableProxy before serializing
412         out.defaultWriteObject();
413     }
414 
415     /**
416      * Creates and returns a new immutable copy of this {@code RingBufferLogEvent}.
417      *
418      * @return a new immutable copy of the data in this {@code RingBufferLogEvent}
419      */
420     public LogEvent createMemento() {
421         return new Log4jLogEvent.Builder(this).build();
422 
423     }
424 
425     /**
426      * Initializes the specified {@code Log4jLogEvent.Builder} from this {@code RingBufferLogEvent}.
427      * @param builder the builder whose fields to populate
428      */
429     public void initializeBuilder(final Log4jLogEvent.Builder builder) {
430         builder.setContextData(contextData) //
431                 .setContextStack(contextStack) //
432                 .setEndOfBatch(endOfBatch) //
433                 .setIncludeLocation(includeLocation) //
434                 .setLevel(getLevel()) // ensure non-null
435                 .setLoggerFqcn(fqcn) //
436                 .setLoggerName(loggerName) //
437                 .setMarker(marker) //
438                 .setMessage(getNonNullImmutableMessage()) // ensure non-null & immutable
439                 .setNanoTime(nanoTime) //
440                 .setSource(location) //
441                 .setThreadId(threadId) //
442                 .setThreadName(threadName) //
443                 .setThreadPriority(threadPriority) //
444                 .setThrown(getThrown()) // may deserialize from thrownProxy
445                 .setThrownProxy(thrownProxy) // avoid unnecessarily creating thrownProxy
446                 .setTimeMillis(currentTimeMillis);
447     }
448 
449 }