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