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