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