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.HashMap;
022import java.util.Map;
023
024import org.apache.logging.log4j.Level;
025import org.apache.logging.log4j.Marker;
026import org.apache.logging.log4j.ThreadContext.ContextStack;
027import org.apache.logging.log4j.core.LogEvent;
028import org.apache.logging.log4j.core.config.Property;
029import org.apache.logging.log4j.core.impl.Log4jLogEvent;
030import org.apache.logging.log4j.core.impl.ThrowableProxy;
031import org.apache.logging.log4j.core.lookup.StrSubstitutor;
032import org.apache.logging.log4j.core.util.Constants;
033import org.apache.logging.log4j.message.Message;
034import org.apache.logging.log4j.message.ParameterizedMessage;
035import org.apache.logging.log4j.message.ReusableMessage;
036import org.apache.logging.log4j.message.SimpleMessage;
037import org.apache.logging.log4j.message.TimestampMessage;
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 {
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            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 long currentTimeMillis;
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 StringBuilder messageText;
082    private Object[] parameters;
083    private transient Throwable thrown;
084    private ThrowableProxy thrownProxy;
085    private Map<String, String> contextMap;
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 Map<String, String> aMap, final ContextStack aContextStack, long threadId,
096            final String threadName, int threadPriority, final StackTraceElement aLocation, final long aCurrentTimeMillis, final long aNanoTime) {
097        this.threadPriority = threadPriority;
098        this.threadId = threadId;
099        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            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        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(int index) {
287        return messageText.charAt(index);
288    }
289
290    @Override
291    public CharSequence subSequence(int start, 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(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}