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            final RingBufferLogEvent result = new RingBufferLogEvent();
062            if (Constants.ENABLE_THREADLOCALS) {
063                result.messageText = new StringBuilder(Constants.INITIAL_REUSABLE_MESSAGE_SIZE);
064                result.parameters = new Object[10];
065            }
066            return result;
067        }
068    }
069
070    private int threadPriority;
071    private long threadId;
072    private MutableInstant instant = new MutableInstant();
073    private long nanoTime;
074    private short parameterCount;
075    private boolean includeLocation;
076    private boolean endOfBatch = false;
077    private Level level;
078    private String threadName;
079    private String loggerName;
080    private Message message;
081    private String messageFormat;
082    private StringBuilder messageText;
083    private Object[] parameters;
084    private transient Throwable thrown;
085    private ThrowableProxy thrownProxy;
086    private StringMap contextData = ContextDataFactory.createContextData();
087    private Marker marker;
088    private String fqcn;
089    private StackTraceElement location;
090    private ContextStack contextStack;
091
092    private transient AsyncLogger asyncLogger;
093
094    public void setValues(final AsyncLogger anAsyncLogger, final String aLoggerName, final Marker aMarker,
095                          final String theFqcn, final Level aLevel, final Message msg, final Throwable aThrowable,
096                          final StringMap mutableContextData, final ContextStack aContextStack, final long threadId,
097                          final String threadName, final int threadPriority, final StackTraceElement aLocation,
098                          final Clock clock, final NanoClock nanoClock) {
099        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(ParameterConsumer<S> action, 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}