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