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