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