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