    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    }