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.impl;
018
019import java.io.InvalidObjectException;
020import java.io.ObjectInputStream;
021import java.util.Arrays;
022import java.util.Map;
023
024import org.apache.logging.log4j.Level;
025import org.apache.logging.log4j.Marker;
026import org.apache.logging.log4j.ThreadContext;
027import org.apache.logging.log4j.core.LogEvent;
028import org.apache.logging.log4j.core.async.InternalAsyncUtil;
029import org.apache.logging.log4j.core.util.*;
030import org.apache.logging.log4j.core.time.Instant;
031import org.apache.logging.log4j.core.time.MutableInstant;
032import org.apache.logging.log4j.message.*;
033import org.apache.logging.log4j.util.ReadOnlyStringMap;
034import org.apache.logging.log4j.util.StackLocatorUtil;
035import org.apache.logging.log4j.util.StringBuilders;
036import org.apache.logging.log4j.util.StringMap;
037import org.apache.logging.log4j.util.Strings;
038
039/**
040 * Mutable implementation of the {@code LogEvent} interface.
041 * @since 2.6
042 */
043public class MutableLogEvent implements LogEvent, ReusableMessage, ParameterVisitable {
044    private static final Message EMPTY = new SimpleMessage(Strings.EMPTY);
045
046    private int threadPriority;
047    private long threadId;
048    private MutableInstant instant = new MutableInstant();
049    private long nanoTime;
050    private short parameterCount;
051    private boolean includeLocation;
052    private boolean endOfBatch = false;
053    private Level level;
054    private String threadName;
055    private String loggerName;
056    private Message message;
057    private String messageFormat;
058    private StringBuilder messageText;
059    private Object[] parameters;
060    private Throwable thrown;
061    private ThrowableProxy thrownProxy;
062    private StringMap contextData = ContextDataFactory.createContextData();
063    private Marker marker;
064    private String loggerFqcn;
065    private StackTraceElement source;
066    private ThreadContext.ContextStack contextStack;
067    transient boolean reserved = false;
068
069    public MutableLogEvent() {
070        this(new StringBuilder(Constants.INITIAL_REUSABLE_MESSAGE_SIZE), new Object[10]);
071    }
072
073    public MutableLogEvent(final StringBuilder msgText, final Object[] replacementParameters) {
074        this.messageText = msgText;
075        this.parameters = replacementParameters;
076    }
077
078    @Override
079    public Log4jLogEvent toImmutable() {
080        return createMemento();
081    }
082
083    /**
084     * Initialize the fields of this {@code MutableLogEvent} from another event.
085     * Similar in purpose and usage as {@link org.apache.logging.log4j.core.impl.Log4jLogEvent.LogEventProxy},
086     * but a mutable version.
087     * <p>
088     * This method is used on async logger ringbuffer slots holding MutableLogEvent objects in each slot.
089     * </p>
090     *
091     * @param event the event to copy data from
092     */
093    public void initFrom(final LogEvent event) {
094        this.loggerFqcn = event.getLoggerFqcn();
095        this.marker = event.getMarker();
096        this.level = event.getLevel();
097        this.loggerName = event.getLoggerName();
098        this.thrown = event.getThrown();
099        this.thrownProxy = event.getThrownProxy();
100
101        this.instant.initFrom(event.getInstant());
102
103        // NOTE: this ringbuffer event SHOULD NOT keep a reference to the specified
104        // thread-local MutableLogEvent's context data, because then two threads would call
105        // ReadOnlyStringMap.clear() on the same shared instance, resulting in data corruption.
106        this.contextData.putAll(event.getContextData());
107
108        this.contextStack = event.getContextStack();
109        this.source = event.isIncludeLocation() ? event.getSource() : null;
110        this.threadId = event.getThreadId();
111        this.threadName = event.getThreadName();
112        this.threadPriority = event.getThreadPriority();
113        this.endOfBatch = event.isEndOfBatch();
114        this.includeLocation = event.isIncludeLocation();
115        this.nanoTime = event.getNanoTime();
116        setMessage(event.getMessage());
117    }
118
119    /**
120     * Clears all references this event has to other objects.
121     */
122    public void clear() {
123        loggerFqcn = null;
124        marker = null;
125        level = null;
126        loggerName = null;
127        message = null;
128        messageFormat = null;
129        thrown = null;
130        thrownProxy = null;
131        source = null;
132        if (contextData != null) {
133            if (contextData.isFrozen()) { // came from CopyOnWrite thread context
134                contextData = null;
135            } else {
136                contextData.clear();
137            }
138        }
139        contextStack = null;
140
141        // ThreadName should not be cleared: this field is set in the ReusableLogEventFactory
142        // where this instance is kept in a ThreadLocal, so it usually does not change.
143        // threadName = null; // no need to clear threadName
144
145        // ensure that excessively long char[] arrays are not kept in memory forever
146        StringBuilders.trimToMaxSize(messageText, Constants.MAX_REUSABLE_MESSAGE_SIZE);
147
148        if (parameters != null) {
149            for (int i = 0; i < parameters.length; i++) {
150                parameters[i] = null;
151            }
152        }
153
154        // primitive fields that cannot be cleared:
155        //timeMillis;
156        //threadId;
157        //threadPriority;
158        //includeLocation;
159        //endOfBatch;
160        //nanoTime;
161    }
162
163    @Override
164    public String getLoggerFqcn() {
165        return loggerFqcn;
166    }
167
168    public void setLoggerFqcn(final String loggerFqcn) {
169        this.loggerFqcn = loggerFqcn;
170    }
171
172    @Override
173    public Marker getMarker() {
174        return marker;
175    }
176
177    public void setMarker(final Marker marker) {
178        this.marker = marker;
179    }
180
181    @Override
182    public Level getLevel() {
183        if (level == null) {
184            level = Level.OFF; // LOG4J2-462, LOG4J2-465
185        }
186        return level;
187    }
188
189    public void setLevel(final Level level) {
190        this.level = level;
191    }
192
193    @Override
194    public String getLoggerName() {
195        return loggerName;
196    }
197
198    public void setLoggerName(final String loggerName) {
199        this.loggerName = loggerName;
200    }
201
202    @Override
203    public Message getMessage() {
204        if (message == null) {
205            return messageText == null ? EMPTY : this;
206        }
207        return message;
208    }
209
210    public void setMessage(final Message msg) {
211        if (msg instanceof ReusableMessage) {
212            final ReusableMessage reusable = (ReusableMessage) msg;
213            reusable.formatTo(getMessageTextForWriting());
214            this.messageFormat = msg.getFormat();
215            if (parameters != null) {
216                parameters = reusable.swapParameters(parameters);
217                parameterCount = reusable.getParameterCount();
218            }
219        } else {
220            this.message = InternalAsyncUtil.makeMessageImmutable(msg);
221        }
222    }
223
224    private StringBuilder getMessageTextForWriting() {
225        if (messageText == null) {
226            // Should never happen:
227            // only happens if user logs a custom reused message when Constants.ENABLE_THREADLOCALS is false
228            messageText = new StringBuilder(Constants.INITIAL_REUSABLE_MESSAGE_SIZE);
229        }
230        messageText.setLength(0);
231        return messageText;
232    }
233
234    /**
235     * @see ReusableMessage#getFormattedMessage()
236     */
237    @Override
238    public String getFormattedMessage() {
239        return messageText.toString();
240    }
241
242    /**
243     * @see ReusableMessage#getFormat()
244     */
245    @Override
246    public String getFormat() {
247        return messageFormat;
248    }
249
250    /**
251     * @see ReusableMessage#getParameters()
252     */
253    @Override
254    public Object[] getParameters() {
255        return parameters == null ? null : Arrays.copyOf(parameters, parameterCount);
256    }
257
258    @Override
259    public <S> void forEachParameter(ParameterConsumer<S> action, S state) {
260        if (parameters != null) {
261            for (short i = 0; i < parameterCount; i++) {
262                action.accept(parameters[i], i, state);
263            }
264        }
265    }
266
267    /**
268     * @see ReusableMessage#getThrowable()
269     */
270    @Override
271    public Throwable getThrowable() {
272        return getThrown();
273    }
274
275    /**
276     * @see ReusableMessage#formatTo(StringBuilder)
277     */
278    @Override
279    public void formatTo(final StringBuilder buffer) {
280        buffer.append(messageText);
281    }
282
283    /**
284     * Replaces this ReusableMessage's parameter array with the specified value and return the original array
285     * @param emptyReplacement the parameter array that can be used for subsequent uses of this reusable message
286     * @return the original parameter array
287     * @see ReusableMessage#swapParameters(Object[])
288     */
289    @Override
290    public Object[] swapParameters(final Object[] emptyReplacement) {
291        final Object[] result = this.parameters;
292        this.parameters = emptyReplacement;
293        return result;
294    }
295
296    /*
297     * @see ReusableMessage#getParameterCount
298     */
299    @Override
300    public short getParameterCount() {
301        return parameterCount;
302    }
303
304    @Override
305    public Message memento() {
306        if (message != null) {
307            return message;
308        }
309        final Object[] params = parameters == null ? new Object[0] : Arrays.copyOf(parameters, parameterCount);
310        return new ParameterizedMessage(messageText.toString(), params);
311    }
312
313    @Override
314    public Throwable getThrown() {
315        return thrown;
316    }
317
318    public void setThrown(final Throwable thrown) {
319        this.thrown = thrown;
320    }
321
322    void initTime(final Clock clock, final NanoClock nanoClock) {
323        if (message instanceof TimestampMessage) {
324            instant.initFromEpochMilli(((TimestampMessage) message).getTimestamp(), 0);
325        } else {
326            instant.initFrom(clock);
327        }
328        nanoTime = nanoClock.nanoTime();
329    }
330
331    @Override
332    public long getTimeMillis() {
333        return instant.getEpochMillisecond();
334    }
335
336    public void setTimeMillis(final long timeMillis) {
337        this.instant.initFromEpochMilli(timeMillis, 0);
338    }
339
340    @Override
341    public Instant getInstant() {
342        return instant;
343    }
344
345    /**
346     * Returns the ThrowableProxy associated with the event, or null.
347     * @return The ThrowableProxy associated with the event.
348     */
349    @Override
350    public ThrowableProxy getThrownProxy() {
351        if (thrownProxy == null && thrown != null) {
352            thrownProxy = new ThrowableProxy(thrown);
353        }
354        return thrownProxy;
355    }
356
357    /**
358     * Returns the StackTraceElement for the caller. This will be the entry that occurs right
359     * before the first occurrence of FQCN as a class name.
360     * @return the StackTraceElement for the caller.
361     */
362    @Override
363    public StackTraceElement getSource() {
364        if (source != null) {
365            return source;
366        }
367        if (loggerFqcn == null || !includeLocation) {
368            return null;
369        }
370        source = StackLocatorUtil.calcLocation(loggerFqcn);
371        return source;
372    }
373
374    @SuppressWarnings("unchecked")
375    @Override
376    public ReadOnlyStringMap getContextData() {
377        return contextData;
378    }
379
380    @Override
381    public Map<String, String> getContextMap() {
382        return contextData.toMap();
383    }
384
385    public void setContextData(final StringMap mutableContextData) {
386        this.contextData = mutableContextData;
387    }
388
389    @Override
390    public ThreadContext.ContextStack getContextStack() {
391        return contextStack;
392    }
393
394    public void setContextStack(final ThreadContext.ContextStack contextStack) {
395        this.contextStack = contextStack;
396    }
397
398    @Override
399    public long getThreadId() {
400        return threadId;
401    }
402
403    public void setThreadId(final long threadId) {
404        this.threadId = threadId;
405    }
406
407    @Override
408    public String getThreadName() {
409        return threadName;
410    }
411
412    public void setThreadName(final String threadName) {
413        this.threadName = threadName;
414    }
415
416    @Override
417    public int getThreadPriority() {
418        return threadPriority;
419    }
420
421    public void setThreadPriority(final int threadPriority) {
422        this.threadPriority = threadPriority;
423    }
424
425    @Override
426    public boolean isIncludeLocation() {
427        return includeLocation;
428    }
429
430    @Override
431    public void setIncludeLocation(final boolean includeLocation) {
432        this.includeLocation = includeLocation;
433    }
434
435    @Override
436    public boolean isEndOfBatch() {
437        return endOfBatch;
438    }
439
440    @Override
441    public void setEndOfBatch(final boolean endOfBatch) {
442        this.endOfBatch = endOfBatch;
443    }
444
445    @Override
446    public long getNanoTime() {
447        return nanoTime;
448    }
449
450    public void setNanoTime(final long nanoTime) {
451        this.nanoTime = nanoTime;
452    }
453
454    /**
455     * Creates a LogEventProxy that can be serialized.
456     * @return a LogEventProxy.
457     */
458    protected Object writeReplace() {
459        return new Log4jLogEvent.LogEventProxy(this, this.includeLocation);
460    }
461
462    private void readObject(final ObjectInputStream stream) throws InvalidObjectException {
463        throw new InvalidObjectException("Proxy required");
464    }
465
466    /**
467     * Creates and returns a new immutable copy of this {@code MutableLogEvent}.
468     * If {@link #isIncludeLocation()} is true, this will obtain caller location information.
469     *
470     * @return a new immutable copy of the data in this {@code MutableLogEvent}
471     */
472    public Log4jLogEvent createMemento() {
473        return Log4jLogEvent.deserialize(Log4jLogEvent.serialize(this, includeLocation));
474    }
475
476    /**
477     * Initializes the specified {@code Log4jLogEvent.Builder} from this {@code MutableLogEvent}.
478     * @param builder the builder whose fields to populate
479     */
480    public void initializeBuilder(final Log4jLogEvent.Builder builder) {
481        builder.setContextData(contextData) //
482                .setContextStack(contextStack) //
483                .setEndOfBatch(endOfBatch) //
484                .setIncludeLocation(includeLocation) //
485                .setLevel(getLevel()) // ensure non-null
486                .setLoggerFqcn(loggerFqcn) //
487                .setLoggerName(loggerName) //
488                .setMarker(marker) //
489                .setMessage(getNonNullImmutableMessage()) // ensure non-null & immutable
490                .setNanoTime(nanoTime) //
491                .setSource(source) //
492                .setThreadId(threadId) //
493                .setThreadName(threadName) //
494                .setThreadPriority(threadPriority) //
495                .setThrown(getThrown()) // may deserialize from thrownProxy
496                .setThrownProxy(thrownProxy) // avoid unnecessarily creating thrownProxy
497                .setInstant(instant) //
498        ;
499    }
500
501    private Message getNonNullImmutableMessage() {
502        return message != null ? message : new SimpleMessage(String.valueOf(messageText));
503    }
504}