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.io.Serializable;
022import java.util.Collections;
023import java.util.HashMap;
024import java.util.List;
025import java.util.Map;
026
027import org.apache.logging.log4j.Level;
028import org.apache.logging.log4j.Marker;
029import org.apache.logging.log4j.ThreadContext;
030import org.apache.logging.log4j.core.LogEvent;
031import org.apache.logging.log4j.core.config.Property;
032import org.apache.logging.log4j.message.LoggerNameAwareMessage;
033import org.apache.logging.log4j.message.Message;
034import org.apache.logging.log4j.message.TimestampMessage;
035
036/**
037 * Implementation of a LogEvent.
038 */
039public class Log4jLogEvent implements LogEvent {
040
041    private static final long serialVersionUID = -1351367343806656055L;
042    private static final String NOT_AVAIL = "?";
043    private final String fqcnOfLogger;
044    private final Marker marker;
045    private final Level level;
046    private final String name;
047    private final Message message;
048    private final long timestamp;
049    private final ThrowableProxy throwable;
050    private final Map<String, String> mdc;
051    private final ThreadContext.ContextStack ndc;
052    private String threadName = null;
053    private StackTraceElement location;
054    private boolean includeLocation;
055    private boolean endOfBatch = false;
056
057    /**
058     *
059     */
060    public Log4jLogEvent(final long timestamp) {
061        this("", null, "", null, null, (ThrowableProxy) null, null, null, null, null, timestamp);
062    }
063
064    /**
065     * Constructor.
066     * @param loggerName The name of the Logger.
067     * @param marker The Marker or null.
068     * @param fqcn The fully qualified class name of the caller.
069     * @param level The logging Level.
070     * @param message The Message.
071     * @param t A Throwable or null.
072     */
073    public Log4jLogEvent(final String loggerName, final Marker marker, final String fqcn, final Level level,
074                         final Message message, final Throwable t) {
075        this(loggerName, marker, fqcn, level, message, null, t);
076    }
077
078    /**
079     * Constructor.
080     * @param loggerName The name of the Logger.
081     * @param marker The Marker or null.
082     * @param fqcn The fully qualified class name of the caller.
083     * @param level The logging Level.
084     * @param message The Message.
085     * @param properties properties to add to the event.
086     * @param t A Throwable or null.
087     */
088    public Log4jLogEvent(final String loggerName, final Marker marker, final String fqcn, final Level level,
089                         final Message message, final List<Property> properties, final Throwable t) {
090        this(loggerName, marker, fqcn, level, message, t,
091            createMap(properties),
092            ThreadContext.getDepth() == 0 ? null : ThreadContext.cloneStack(), null,
093            null, System.currentTimeMillis());
094    }
095
096    /**
097     * Constructor.
098     * @param loggerName The name of the Logger.
099     * @param marker The Marker or null.
100     * @param fqcn The fully qualified class name of the caller.
101     * @param level The logging Level.
102     * @param message The Message.
103     * @param t A Throwable or null.
104     * @param mdc The mapped diagnostic context.
105     * @param ndc the nested diagnostic context.
106     * @param threadName The name of the thread.
107     * @param location The locations of the caller.
108     * @param timestamp The timestamp of the event.
109     */
110    public Log4jLogEvent(final String loggerName, final Marker marker, final String fqcn, final Level level,
111                         final Message message, final Throwable t,
112                         final Map<String, String> mdc, final ThreadContext.ContextStack ndc, final String threadName,
113                         final StackTraceElement location, final long timestamp) {
114        this(loggerName, marker, fqcn, level, message, t == null ? null : new ThrowableProxy(t), mdc, ndc, threadName,
115            location, timestamp);
116    }
117
118    /**
119     * Create a new LogEvent.
120     * @param loggerName The name of the Logger.
121     * @param marker The Marker or null.
122     * @param fqcn The fully qualified class name of the caller.
123     * @param level The logging Level.
124     * @param message The Message.
125     * @param t A ThrowableProxy or null.
126     * @param mdc The mapped diagnostic context.
127     * @param ndc the nested diagnostic context.
128     * @param threadName The name of the thread.
129     * @param location The locations of the caller.
130     * @param timestamp The timestamp of the event.
131     */
132    public static Log4jLogEvent createEvent(final String loggerName, final Marker marker, final String fqcn,
133                                            final Level level, final Message message, final ThrowableProxy t,
134                                            final Map<String, String> mdc, final ThreadContext.ContextStack ndc,
135                                            final String threadName, final StackTraceElement location,
136                                            final long timestamp) {
137     return new Log4jLogEvent(loggerName, marker, fqcn, level, message, t, mdc, ndc, threadName, location, timestamp);
138    }
139
140    /**
141     * Constructor.
142     * @param loggerName The name of the Logger.
143     * @param marker The Marker or null.
144     * @param fqcn The fully qualified class name of the caller.
145     * @param level The logging Level.
146     * @param message The Message.
147     * @param t A ThrowableProxy or null.
148     * @param mdc The mapped diagnostic context.
149     * @param ndc the nested diagnostic context.
150     * @param threadName The name of the thread.
151     * @param location The locations of the caller.
152     * @param timestamp The timestamp of the event.
153     */
154    private Log4jLogEvent(final String loggerName, final Marker marker, final String fqcn, final Level level,
155                         final Message message, final ThrowableProxy t,
156                         final Map<String, String> mdc, final ThreadContext.ContextStack ndc, final String threadName,
157                         final StackTraceElement location, final long timestamp) {
158        name = loggerName;
159        this.marker = marker;
160        this.fqcnOfLogger = fqcn;
161        this.level = (level == null) ? Level.OFF : level; // LOG4J2-462, LOG4J2-465
162        this.message = message;
163        this.throwable = t;
164        this.mdc = mdc;
165        this.ndc = ndc;
166        this.timestamp = message instanceof TimestampMessage ? ((TimestampMessage) message).getTimestamp() : timestamp;
167        this.threadName = threadName;
168        this.location = location;
169        if (message != null && message instanceof LoggerNameAwareMessage) {
170            ((LoggerNameAwareMessage) message).setLoggerName(name);
171        }
172    }
173
174    private static Map<String, String> createMap(final List<Property> properties) {
175        final Map<String, String> contextMap = ThreadContext.getImmutableContext();
176        if (contextMap == null && (properties == null || properties.size() == 0)) {
177            return null;
178        }
179        if (properties == null || properties.size() == 0) {
180            return contextMap; // contextMap is not null
181        }
182        final Map<String, String> map = new HashMap<String, String>(contextMap);
183
184        for (final Property prop : properties) {
185            if (!map.containsKey(prop.getName())) {
186                map.put(prop.getName(), prop.getValue());
187            }
188        }
189        return Collections.unmodifiableMap(map);
190    }
191
192    /**
193     * Returns the logging Level.
194     * @return the Level associated with this event.
195     */
196    @Override
197    public Level getLevel() {
198        return level;
199    }
200
201    /**
202     * Returns the name of the Logger used to generate the event.
203     * @return The Logger name.
204     */
205    @Override
206    public String getLoggerName() {
207        return name;
208    }
209
210    /**
211     * Returns the Message associated with the event.
212     * @return The Message.
213     */
214    @Override
215    public Message getMessage() {
216        return message;
217    }
218
219    /**
220     * Returns the name of the Thread on which the event was generated.
221     * @return The name of the Thread.
222     */
223    @Override
224    public String getThreadName() {
225        if (threadName == null) {
226            threadName = Thread.currentThread().getName();
227        }
228        return threadName;
229    }
230
231    /**
232     * Returns the time in milliseconds from the epoch when the event occurred.
233     * @return The time the event occurred.
234     */
235    @Override
236    public long getMillis() {
237        return timestamp;
238    }
239
240    /**
241     * Returns the Throwable associated with the event, or null.
242     * @return The Throwable associated with the event.
243     */
244    @Override
245    public Throwable getThrown() {
246        return throwable == null ? null : throwable.getThrowable();
247    }
248
249    /**
250     * Returns the ThrowableProxy associated with the event, or null.
251     * @return The ThrowableProxy associated with the event.
252     */
253    public ThrowableProxy getThrownProxy() {
254        return throwable;
255    }
256
257
258    /**
259     * Returns the Marker associated with the event, or null.
260     * @return the Marker associated with the event.
261     */
262    @Override
263    public Marker getMarker() {
264        return marker;
265    }
266
267    /**
268     * The fully qualified class name of the class that was called by the caller.
269     * @return the fully qualified class name of the class that is performing logging.
270     */
271    @Override
272    public String getFQCN() {
273        return fqcnOfLogger;
274    }
275
276    /**
277     * Returns the immutable copy of the ThreadContext Map.
278     * @return The context Map.
279     */
280    @Override
281    public Map<String, String> getContextMap() {
282        return mdc == null ? ThreadContext.EMPTY_MAP : mdc;
283    }
284
285    /**
286     * Returns an immutable copy of the ThreadContext stack.
287     * @return The context Stack.
288     */
289    @Override
290    public ThreadContext.ContextStack getContextStack() {
291        return ndc == null ? ThreadContext.EMPTY_STACK : ndc;
292    }
293
294    /**
295     * Returns the StackTraceElement for the caller. This will be the entry that occurs right
296     * before the first occurrence of FQCN as a class name.
297     * @return the StackTraceElement for the caller.
298     */
299    @Override
300    public StackTraceElement getSource() {
301        if (location != null) {
302            return location;
303        }
304        if (fqcnOfLogger == null || !includeLocation) {
305            return null;
306        }
307        location = calcLocation(fqcnOfLogger);
308        return location;
309    }
310
311    public static StackTraceElement calcLocation(final String fqcnOfLogger) {
312        if (fqcnOfLogger == null) {
313            return null;
314        }
315        final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
316        boolean next = false;
317        for (final StackTraceElement element : stackTrace) {
318            final String className = element.getClassName();
319            if (next) {
320                if (fqcnOfLogger.equals(className)) {
321                    continue;
322                }
323                return element;
324            }
325
326            if (fqcnOfLogger.equals(className)) {
327                next = true;
328            } else if (NOT_AVAIL.equals(className)) {
329                break;
330            }
331        }
332        return null;
333    }
334
335    @Override
336    public boolean isIncludeLocation() {
337        return includeLocation;
338    }
339
340    @Override
341    public void setIncludeLocation(final boolean includeLocation) {
342        this.includeLocation = includeLocation;
343    }
344
345    @Override
346    public boolean isEndOfBatch() {
347        return endOfBatch;
348    }
349
350    @Override
351    public void setEndOfBatch(final boolean endOfBatch) {
352        this.endOfBatch = endOfBatch;
353    }
354
355    /**
356     * Creates a LogEventProxy that can be serialized.
357     * @return a LogEventProxy.
358     */
359    protected Object writeReplace() {
360        return new LogEventProxy(this, this.includeLocation);
361    }
362
363    public static Serializable serialize(final Log4jLogEvent event,
364            final boolean includeLocation) {
365        return new LogEventProxy(event, includeLocation);
366    }
367
368    public static Log4jLogEvent deserialize(final Serializable event) {
369        if (event == null) {
370            throw new NullPointerException("Event cannot be null");
371        }
372        if (event instanceof LogEventProxy) {
373            final LogEventProxy proxy = (LogEventProxy) event;
374            final Log4jLogEvent result = new Log4jLogEvent(proxy.name, proxy.marker,
375                    proxy.fqcnOfLogger, proxy.level, proxy.message,
376                    proxy.throwable, proxy.mdc, proxy.ndc, proxy.threadName,
377                    proxy.location, proxy.timestamp);
378            result.setEndOfBatch(proxy.isEndOfBatch);
379            result.setIncludeLocation(proxy.isLocationRequired);
380            return result;
381        }
382        throw new IllegalArgumentException("Event is not a serialized LogEvent: " + event.toString());
383    }
384
385    private void readObject(final ObjectInputStream stream) throws InvalidObjectException {
386        throw new InvalidObjectException("Proxy required");
387    }
388
389    @Override
390    public String toString() {
391        final StringBuilder sb = new StringBuilder();
392        final String n = name.isEmpty() ? "root" : name;
393        sb.append("Logger=").append(n);
394        sb.append(" Level=").append(level.name());
395        sb.append(" Message=").append(message.getFormattedMessage());
396        return sb.toString();
397    }
398
399    /**
400     * Proxy pattern used to serialize the LogEvent.
401     */
402    private static class LogEventProxy implements Serializable {
403
404        private static final long serialVersionUID = -7139032940312647146L;
405        private final String fqcnOfLogger;
406        private final Marker marker;
407        private final Level level;
408        private final String name;
409        private final Message message;
410        private final long timestamp;
411        private final ThrowableProxy throwable;
412        private final Map<String, String> mdc;
413        private final ThreadContext.ContextStack ndc;
414        private final String threadName;
415        private final StackTraceElement location;
416        private final boolean isLocationRequired;
417        private final boolean isEndOfBatch;
418
419        public LogEventProxy(final Log4jLogEvent event, final boolean includeLocation) {
420            this.fqcnOfLogger = event.fqcnOfLogger;
421            this.marker = event.marker;
422            this.level = event.level;
423            this.name = event.name;
424            this.message = event.message;
425            this.timestamp = event.timestamp;
426            this.throwable = event.throwable;
427            this.mdc = event.mdc;
428            this.ndc = event.ndc;
429            this.location = includeLocation ? event.getSource() : null;
430            this.threadName = event.getThreadName();
431            this.isLocationRequired = includeLocation;
432            this.isEndOfBatch = event.endOfBatch;
433        }
434
435        /**
436         * Returns a Log4jLogEvent using the data in the proxy.
437         * @return Log4jLogEvent.
438         */
439        protected Object readResolve() {
440            final Log4jLogEvent result = new Log4jLogEvent(name, marker, fqcnOfLogger,
441                    level, message, throwable, mdc, ndc, threadName, location,
442                    timestamp);
443            result.setEndOfBatch(isEndOfBatch);
444            result.setIncludeLocation(isLocationRequired);
445            return result;
446        }
447    }
448}