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