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