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