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