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