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     @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             // if the Message instance is reused, there is no point in freezing its message here
132             if (msg != null && !canFormatMessageInBackground(msg)) {
133                 msg.getFormattedMessage(); // LOG4J2-763: ask message to freeze parameters
134             }
135             this.message = msg;
136         }
137     }
138 
139     private boolean canFormatMessageInBackground(final Message message) {
140         return Constants.FORMAT_MESSAGES_IN_BACKGROUND // LOG4J2-898: user wants to format all msgs in background
141                 || message.getClass().isAnnotationPresent(AsynchronouslyFormattable.class); // LOG4J2-1718
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 null;
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 Message memento() {
285         if (message != null) {
286             return message;
287         }
288         final Object[] params = parameters == null ? new Object[0] : Arrays.copyOf(parameters, parameterCount);
289         return new ParameterizedMessage(messageText.toString(), params);
290     }
291 
292     // CharSequence impl
293 
294     @Override
295     public int length() {
296         return messageText.length();
297     }
298 
299     @Override
300     public char charAt(final int index) {
301         return messageText.charAt(index);
302     }
303 
304     @Override
305     public CharSequence subSequence(final int start, final int end) {
306         return messageText.subSequence(start, end);
307     }
308 
309 
310     private Message getNonNullImmutableMessage() {
311         return message != null ? message : new SimpleMessage(String.valueOf(messageText));
312     }
313 
314     @Override
315     public Throwable getThrown() {
316         // after deserialization, thrown is null but thrownProxy may be non-null
317         if (thrown == null) {
318             if (thrownProxy != null) {
319                 thrown = thrownProxy.getThrowable();
320             }
321         }
322         return thrown;
323     }
324 
325     @Override
326     public ThrowableProxy getThrownProxy() {
327         // lazily instantiate the (expensive) ThrowableProxy
328         if (thrownProxy == null) {
329             if (thrown != null) {
330                 thrownProxy = new ThrowableProxy(thrown);
331             }
332         }
333         return this.thrownProxy;
334     }
335 
336     @SuppressWarnings("unchecked")
337     @Override
338     public ReadOnlyStringMap getContextData() {
339         return contextData;
340     }
341 
342     void setContextData(final StringMap contextData) {
343         this.contextData = contextData;
344     }
345 
346     @SuppressWarnings("unchecked")
347     @Override
348     public Map<String, String> getContextMap() {
349         return contextData.toMap();
350     }
351 
352     @Override
353     public ContextStack getContextStack() {
354         return contextStack;
355     }
356 
357     @Override
358     public long getThreadId() {
359         return threadId;
360     }
361 
362     @Override
363     public String getThreadName() {
364         return threadName;
365     }
366 
367     @Override
368     public int getThreadPriority() {
369         return threadPriority;
370     }
371 
372     @Override
373     public StackTraceElement getSource() {
374         return location;
375     }
376 
377     @Override
378     public long getTimeMillis() {
379         return message instanceof TimestampMessage ? ((TimestampMessage) message).getTimestamp() :currentTimeMillis;
380     }
381 
382     @Override
383     public long getNanoTime() {
384         return nanoTime;
385     }
386 
387     /**
388      * Release references held by ring buffer to allow objects to be garbage-collected.
389      */
390     public void clear() {
391         this.asyncLogger = null;
392         this.loggerName = null;
393         this.marker = null;
394         this.fqcn = null;
395         this.level = null;
396         this.message = null;
397         this.thrown = null;
398         this.thrownProxy = null;
399         this.contextStack = null;
400         this.location = null;
401         if (contextData != null) {
402             if (contextData.isFrozen()) { // came from CopyOnWrite thread context
403                 contextData = null;
404             } else {
405                 contextData.clear();
406             }
407         }
408 
409         trimMessageText();
410 
411         if (parameters != null) {
412             for (int i = 0; i < parameters.length; i++) {
413                 parameters[i] = null;
414             }
415         }
416     }
417 
418     // ensure that excessively long char[] arrays are not kept in memory forever
419     private void trimMessageText() {
420         if (messageText != null && messageText.length() > Constants.MAX_REUSABLE_MESSAGE_SIZE) {
421             messageText.setLength(Constants.MAX_REUSABLE_MESSAGE_SIZE);
422             messageText.trimToSize();
423         }
424     }
425 
426     private void writeObject(final java.io.ObjectOutputStream out) throws IOException {
427         getThrownProxy(); // initialize the ThrowableProxy before serializing
428         out.defaultWriteObject();
429     }
430 
431     /**
432      * Creates and returns a new immutable copy of this {@code RingBufferLogEvent}.
433      *
434      * @return a new immutable copy of the data in this {@code RingBufferLogEvent}
435      */
436     public LogEvent createMemento() {
437         return new Log4jLogEvent.Builder(this).build();
438         
439     }
440 
441     /**
442      * Initializes the specified {@code Log4jLogEvent.Builder} from this {@code RingBufferLogEvent}.
443      * @param builder the builder whose fields to populate
444      */
445     public void initializeBuilder(final Log4jLogEvent.Builder builder) {
446         builder.setContextData(contextData) //
447                 .setContextStack(contextStack) //
448                 .setEndOfBatch(endOfBatch) //
449                 .setIncludeLocation(includeLocation) //
450                 .setLevel(getLevel()) // ensure non-null
451                 .setLoggerFqcn(fqcn) //
452                 .setLoggerName(loggerName) //
453                 .setMarker(marker) //
454                 .setMessage(getNonNullImmutableMessage()) // ensure non-null & immutable
455                 .setNanoTime(nanoTime) //
456                 .setSource(location) //
457                 .setThreadId(threadId) //
458                 .setThreadName(threadName) //
459                 .setThreadPriority(threadPriority) //
460                 .setThrown(getThrown()) // may deserialize from thrownProxy
461                 .setThrownProxy(thrownProxy) // avoid unnecessarily creating thrownProxy
462                 .setTimeMillis(currentTimeMillis);
463     }
464 
465 }