001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017package org.apache.logging.log4j.core.async;
018
019import java.io.IOException;
020import java.util.Arrays;
021import java.util.Map;
022
023import org.apache.logging.log4j.Level;
024import org.apache.logging.log4j.Marker;
025import org.apache.logging.log4j.ThreadContext.ContextStack;
026import org.apache.logging.log4j.core.LogEvent;
027import org.apache.logging.log4j.core.impl.ContextDataFactory;
028import org.apache.logging.log4j.core.impl.Log4jLogEvent;
029import org.apache.logging.log4j.core.impl.MementoMessage;
030import org.apache.logging.log4j.core.impl.ThrowableProxy;
031import org.apache.logging.log4j.core.util.*;
032import org.apache.logging.log4j.core.time.Instant;
033import org.apache.logging.log4j.core.time.MutableInstant;
034import org.apache.logging.log4j.message.*;
035import org.apache.logging.log4j.util.ReadOnlyStringMap;
036import org.apache.logging.log4j.util.StringBuilders;
037import org.apache.logging.log4j.util.StringMap;
038import org.apache.logging.log4j.util.Strings;
039
040import com.lmax.disruptor.EventFactory;
041
042/**
043 * When the Disruptor is started, the RingBuffer is populated with event objects. These objects are then re-used during
044 * the life of the RingBuffer.
045 */
046public class RingBufferLogEvent implements LogEvent, ReusableMessage, CharSequence, ParameterVisitable {
047
048    /** The {@code EventFactory} for {@code RingBufferLogEvent}s. */
049    public static final Factory FACTORY = new Factory();
050
051    private static final long serialVersionUID = 8462119088943934758L;
052    private static final Message EMPTY = new SimpleMessage(Strings.EMPTY);
053
054    /**
055     * Creates the events that will be put in the RingBuffer.
056     */
057    private static class Factory implements EventFactory<RingBufferLogEvent> {
058
059        @Override
060        public RingBufferLogEvent newInstance() {
061            return new RingBufferLogEvent();
062        }
063    }
064
065    private boolean populated;
066    private int threadPriority;
067    private long threadId;
068    private final MutableInstant instant = new MutableInstant();
069    private long nanoTime;
070    private short parameterCount;
071    private boolean includeLocation;
072    private boolean endOfBatch = false;
073    private Level level;
074    private String threadName;
075    private String loggerName;
076    private Message message;
077    private String messageFormat;
078    private StringBuilder messageText;
079    private Object[] parameters;
080    private transient Throwable thrown;
081    private ThrowableProxy thrownProxy;
082    private StringMap contextData = ContextDataFactory.createContextData();
083    private Marker marker;
084    private String fqcn;
085    private StackTraceElement location;
086    private ContextStack contextStack;
087
088    private transient AsyncLogger asyncLogger;
089
090    public void setValues(final AsyncLogger anAsyncLogger, final String aLoggerName, final Marker aMarker,
091                          final String theFqcn, final Level aLevel, final Message msg, final Throwable aThrowable,
092                          final StringMap mutableContextData, final ContextStack aContextStack, final long threadId,
093                          final String threadName, final int threadPriority, final StackTraceElement aLocation,
094                          final Clock clock, final NanoClock nanoClock) {
095        this.threadPriority = threadPriority;
096        this.threadId = threadId;
097        this.level = aLevel;
098        this.threadName = threadName;
099        this.loggerName = aLoggerName;
100        setMessage(msg);
101        initTime(clock);
102        this.nanoTime = nanoClock.nanoTime();
103        this.thrown = aThrowable;
104        this.thrownProxy = null;
105        this.marker = aMarker;
106        this.fqcn = theFqcn;
107        this.location = aLocation;
108        this.contextData = mutableContextData;
109        this.contextStack = aContextStack;
110        this.asyncLogger = anAsyncLogger;
111        this.populated = true;
112    }
113
114    private void initTime(final Clock clock) {
115        if (message instanceof TimestampMessage) {
116            instant.initFromEpochMilli(((TimestampMessage) message).getTimestamp(), 0);
117        } else {
118            instant.initFrom(clock);
119        }
120    }
121
122    @Override
123    public LogEvent toImmutable() {
124        return createMemento();
125    }
126
127    private void setMessage(final Message msg) {
128        if (msg instanceof ReusableMessage) {
129            final ReusableMessage reusable = (ReusableMessage) msg;
130            reusable.formatTo(getMessageTextForWriting());
131            messageFormat = reusable.getFormat();
132            parameters = reusable.swapParameters(parameters == null ? new Object[10] : parameters);
133            parameterCount = reusable.getParameterCount();
134        } else {
135            this.message = InternalAsyncUtil.makeMessageImmutable(msg);
136        }
137    }
138
139    private StringBuilder getMessageTextForWriting() {
140        if (messageText == null) {
141            // Happens the first time messageText is requested or if a user logs
142            // a custom reused message when Constants.ENABLE_THREADLOCALS is false
143            messageText = new StringBuilder(Constants.INITIAL_REUSABLE_MESSAGE_SIZE);
144        }
145        messageText.setLength(0);
146        return messageText;
147    }
148
149    /**
150     * Event processor that reads the event from the ringbuffer can call this method.
151     *
152     * @param endOfBatch flag to indicate if this is the last event in a batch from the RingBuffer
153     */
154    public void execute(final boolean endOfBatch) {
155        this.endOfBatch = endOfBatch;
156        asyncLogger.actualAsyncLog(this);
157    }
158
159    /**
160     * @return {@code true} if this event is populated with data, {@code false} otherwise
161     */
162    public boolean isPopulated() {
163        return populated;
164    }
165
166    /**
167     * Returns {@code true} if this event is the end of a batch, {@code false} otherwise.
168     *
169     * @return {@code true} if this event is the end of a batch, {@code false} otherwise
170     */
171    @Override
172    public boolean isEndOfBatch() {
173        return endOfBatch;
174    }
175
176    @Override
177    public void setEndOfBatch(final boolean endOfBatch) {
178        this.endOfBatch = endOfBatch;
179    }
180
181    @Override
182    public boolean isIncludeLocation() {
183        return includeLocation;
184    }
185
186    @Override
187    public void setIncludeLocation(final boolean includeLocation) {
188        this.includeLocation = includeLocation;
189    }
190
191    @Override
192    public String getLoggerName() {
193        return loggerName;
194    }
195
196    @Override
197    public Marker getMarker() {
198        return marker;
199    }
200
201    @Override
202    public String getLoggerFqcn() {
203        return fqcn;
204    }
205
206    @Override
207    public Level getLevel() {
208        if (level == null) {
209            level = Level.OFF; // LOG4J2-462, LOG4J2-465
210        }
211        return level;
212    }
213
214    @Override
215    public Message getMessage() {
216        if (message == null) {
217            return messageText == null ? EMPTY : this;
218        }
219        return message;
220    }
221
222    /**
223     * @see ReusableMessage#getFormattedMessage()
224     */
225    @Override
226    public String getFormattedMessage() {
227        return messageText != null // LOG4J2-1527: may be null in web apps
228                ? messageText.toString() // note: please keep below "redundant" braces for readability
229                : (message == null ? null : message.getFormattedMessage());
230    }
231
232    /**
233     * @see ReusableMessage#getFormat()
234     */
235    @Override
236    public String getFormat() {
237        return messageFormat;
238    }
239
240    /**
241     * @see ReusableMessage#getParameters()
242     */
243    @Override
244    public Object[] getParameters() {
245        return parameters == null ? null : Arrays.copyOf(parameters, parameterCount);
246    }
247
248    /**
249     * @see ReusableMessage#getThrowable()
250     */
251    @Override
252    public Throwable getThrowable() {
253        return getThrown();
254    }
255
256    /**
257     * @see ReusableMessage#formatTo(StringBuilder)
258     */
259    @Override
260    public void formatTo(final StringBuilder buffer) {
261        buffer.append(messageText);
262    }
263
264    /**
265     * Replaces this ReusableMessage's parameter array with the specified value and return the original array
266     * @param emptyReplacement the parameter array that can be used for subsequent uses of this reusable message
267     * @return the original parameter array
268     * @see ReusableMessage#swapParameters(Object[])
269     */
270    @Override
271    public Object[] swapParameters(final Object[] emptyReplacement) {
272        final Object[] result = this.parameters;
273        this.parameters = emptyReplacement;
274        return result;
275    }
276
277    /*
278     * @see ReusableMessage#getParameterCount
279     */
280    @Override
281    public short getParameterCount() {
282        return parameterCount;
283    }
284
285    @Override
286    public <S> void forEachParameter(final ParameterConsumer<S> action, final S state) {
287        if (parameters != null) {
288            for (short i = 0; i < parameterCount; i++) {
289                action.accept(parameters[i], i, state);
290            }
291        }
292    }
293
294    @Override
295    public Message memento() {
296        if (message == null) {
297            message = new MementoMessage(String.valueOf(messageText), messageFormat, getParameters());
298        }
299        return message;
300    }
301
302    // CharSequence impl
303
304    @Override
305    public int length() {
306        return messageText.length();
307    }
308
309    @Override
310    public char charAt(final int index) {
311        return messageText.charAt(index);
312    }
313
314    @Override
315    public CharSequence subSequence(final int start, final int end) {
316        return messageText.subSequence(start, end);
317    }
318
319    @Override
320    public Throwable getThrown() {
321        // after deserialization, thrown is null but thrownProxy may be non-null
322        if (thrown == null) {
323            if (thrownProxy != null) {
324                thrown = thrownProxy.getThrowable();
325            }
326        }
327        return thrown;
328    }
329
330    @Override
331    public ThrowableProxy getThrownProxy() {
332        // lazily instantiate the (expensive) ThrowableProxy
333        if (thrownProxy == null) {
334            if (thrown != null) {
335                thrownProxy = new ThrowableProxy(thrown);
336            }
337        }
338        return this.thrownProxy;
339    }
340
341    @SuppressWarnings("unchecked")
342    @Override
343    public ReadOnlyStringMap getContextData() {
344        return contextData;
345    }
346
347    void setContextData(final StringMap contextData) {
348        this.contextData = contextData;
349    }
350
351    @SuppressWarnings("unchecked")
352    @Override
353    public Map<String, String> getContextMap() {
354        return contextData.toMap();
355    }
356
357    @Override
358    public ContextStack getContextStack() {
359        return contextStack;
360    }
361
362    @Override
363    public long getThreadId() {
364        return threadId;
365    }
366
367    @Override
368    public String getThreadName() {
369        return threadName;
370    }
371
372    @Override
373    public int getThreadPriority() {
374        return threadPriority;
375    }
376
377    @Override
378    public StackTraceElement getSource() {
379        return location;
380    }
381
382    @Override
383    public long getTimeMillis() {
384        return message instanceof TimestampMessage ? ((TimestampMessage) message).getTimestamp() : instant.getEpochMillisecond();
385    }
386
387    @Override
388    public Instant getInstant() {
389        return instant;
390    }
391
392    @Override
393    public long getNanoTime() {
394        return nanoTime;
395    }
396
397    /**
398     * Release references held by ring buffer to allow objects to be garbage-collected.
399     */
400    public void clear() {
401        this.populated = false;
402
403        this.asyncLogger = null;
404        this.loggerName = null;
405        this.marker = null;
406        this.fqcn = null;
407        this.level = null;
408        this.message = null;
409        this.messageFormat = null;
410        this.thrown = null;
411        this.thrownProxy = null;
412        this.contextStack = null;
413        this.location = null;
414        if (contextData != null) {
415            if (contextData.isFrozen()) { // came from CopyOnWrite thread context
416                contextData = null;
417            } else {
418                contextData.clear();
419            }
420        }
421
422        // ensure that excessively long char[] arrays are not kept in memory forever
423        if (Constants.ENABLE_THREADLOCALS) {
424            StringBuilders.trimToMaxSize(messageText, Constants.MAX_REUSABLE_MESSAGE_SIZE);
425
426            if (parameters != null) {
427                Arrays.fill(parameters, null);
428            }
429        } else {
430            // A user may have manually logged a ReusableMessage implementation, when thread locals are
431            // disabled we remove the reference in order to avoid permanently holding references to these
432            // buffers.
433            messageText = null;
434            parameters = null;
435        }
436    }
437
438    private void writeObject(final java.io.ObjectOutputStream out) throws IOException {
439        getThrownProxy(); // initialize the ThrowableProxy before serializing
440        out.defaultWriteObject();
441    }
442
443    /**
444     * Creates and returns a new immutable copy of this {@code RingBufferLogEvent}.
445     *
446     * @return a new immutable copy of the data in this {@code RingBufferLogEvent}
447     */
448    public LogEvent createMemento() {
449        return new Log4jLogEvent.Builder(this).build();
450
451    }
452
453    /**
454     * Initializes the specified {@code Log4jLogEvent.Builder} from this {@code RingBufferLogEvent}.
455     * @param builder the builder whose fields to populate
456     */
457    public void initializeBuilder(final Log4jLogEvent.Builder builder) {
458        builder.setContextData(contextData) //
459                .setContextStack(contextStack) //
460                .setEndOfBatch(endOfBatch) //
461                .setIncludeLocation(includeLocation) //
462                .setLevel(getLevel()) // ensure non-null
463                .setLoggerFqcn(fqcn) //
464                .setLoggerName(loggerName) //
465                .setMarker(marker) //
466                .setMessage(memento()) // ensure non-null & immutable
467                .setNanoTime(nanoTime) //
468                .setSource(location) //
469                .setThreadId(threadId) //
470                .setThreadName(threadName) //
471                .setThreadPriority(threadPriority) //
472                .setThrown(getThrown()) // may deserialize from thrownProxy
473                .setThrownProxy(thrownProxy) // avoid unnecessarily creating thrownProxy
474                .setInstant(instant) //
475        ;
476    }
477
478}