View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.impl;
18  
19  import java.io.InvalidObjectException;
20  import java.io.ObjectInputStream;
21  import java.io.Serializable;
22  import java.util.Collections;
23  import java.util.HashMap;
24  import java.util.List;
25  import java.util.Map;
26  
27  import org.apache.logging.log4j.Level;
28  import org.apache.logging.log4j.Marker;
29  import org.apache.logging.log4j.ThreadContext;
30  import org.apache.logging.log4j.core.LogEvent;
31  import org.apache.logging.log4j.core.config.Property;
32  import org.apache.logging.log4j.message.LoggerNameAwareMessage;
33  import org.apache.logging.log4j.message.Message;
34  import org.apache.logging.log4j.message.TimestampMessage;
35  
36  /**
37   * Implementation of a LogEvent.
38   */
39  public class Log4jLogEvent implements LogEvent {
40  
41      private static final long serialVersionUID = -1351367343806656055L;
42      private static final String NOT_AVAIL = "?";
43      private final String fqcnOfLogger;
44      private final Marker marker;
45      private final Level level;
46      private final String name;
47      private final Message message;
48      private final long timestamp;
49      private final ThrowableProxy throwable;
50      private final Map<String, String> mdc;
51      private final ThreadContext.ContextStack ndc;
52      private String threadName = null;
53      private StackTraceElement location;
54      private boolean includeLocation;
55      private boolean endOfBatch = false;
56  
57      /**
58       *
59       */
60      public Log4jLogEvent(final long timestamp) {
61          this("", null, "", null, null, (ThrowableProxy) null, null, null, null, null, timestamp);
62      }
63  
64      /**
65       * Constructor.
66       * @param loggerName The name of the Logger.
67       * @param marker The Marker or null.
68       * @param fqcn The fully qualified class name of the caller.
69       * @param level The logging Level.
70       * @param message The Message.
71       * @param t A Throwable or null.
72       */
73      public Log4jLogEvent(final String loggerName, final Marker marker, final String fqcn, final Level level,
74                           final Message message, final Throwable t) {
75          this(loggerName, marker, fqcn, level, message, null, t);
76      }
77  
78      /**
79       * Constructor.
80       * @param loggerName The name of the Logger.
81       * @param marker The Marker or null.
82       * @param fqcn The fully qualified class name of the caller.
83       * @param level The logging Level.
84       * @param message The Message.
85       * @param properties properties to add to the event.
86       * @param t A Throwable or null.
87       */
88      public Log4jLogEvent(final String loggerName, final Marker marker, final String fqcn, final Level level,
89                           final Message message, final List<Property> properties, final Throwable t) {
90          this(loggerName, marker, fqcn, level, message, t,
91              createMap(properties),
92              ThreadContext.getDepth() == 0 ? null : ThreadContext.cloneStack(), null,
93              null, System.currentTimeMillis());
94      }
95  
96      /**
97       * Constructor.
98       * @param loggerName The name of the Logger.
99       * @param marker The Marker or null.
100      * @param fqcn The fully qualified class name of the caller.
101      * @param level The logging Level.
102      * @param message The Message.
103      * @param t A Throwable or null.
104      * @param mdc The mapped diagnostic context.
105      * @param ndc the nested diagnostic context.
106      * @param threadName The name of the thread.
107      * @param location The locations of the caller.
108      * @param timestamp The timestamp of the event.
109      */
110     public Log4jLogEvent(final String loggerName, final Marker marker, final String fqcn, final Level level,
111                          final Message message, final Throwable t,
112                          final Map<String, String> mdc, final ThreadContext.ContextStack ndc, final String threadName,
113                          final StackTraceElement location, final long timestamp) {
114         this(loggerName, marker, fqcn, level, message, t == null ? null : new ThrowableProxy(t), mdc, ndc, threadName,
115             location, timestamp);
116     }
117 
118     /**
119      * Create a new LogEvent.
120      * @param loggerName The name of the Logger.
121      * @param marker The Marker or null.
122      * @param fqcn The fully qualified class name of the caller.
123      * @param level The logging Level.
124      * @param message The Message.
125      * @param t A ThrowableProxy or null.
126      * @param mdc The mapped diagnostic context.
127      * @param ndc the nested diagnostic context.
128      * @param threadName The name of the thread.
129      * @param location The locations of the caller.
130      * @param timestamp The timestamp of the event.
131      */
132     public static Log4jLogEvent createEvent(final String loggerName, final Marker marker, final String fqcn,
133                                             final Level level, final Message message, final ThrowableProxy t,
134                                             final Map<String, String> mdc, final ThreadContext.ContextStack ndc,
135                                             final String threadName, final StackTraceElement location,
136                                             final long timestamp) {
137      return new Log4jLogEvent(loggerName, marker, fqcn, level, message, t, mdc, ndc, threadName, location, timestamp);
138     }
139 
140     /**
141      * Constructor.
142      * @param loggerName The name of the Logger.
143      * @param marker The Marker or null.
144      * @param fqcn The fully qualified class name of the caller.
145      * @param level The logging Level.
146      * @param message The Message.
147      * @param t A ThrowableProxy or null.
148      * @param mdc The mapped diagnostic context.
149      * @param ndc the nested diagnostic context.
150      * @param threadName The name of the thread.
151      * @param location The locations of the caller.
152      * @param timestamp The timestamp of the event.
153      */
154     private Log4jLogEvent(final String loggerName, final Marker marker, final String fqcn, final Level level,
155                          final Message message, final ThrowableProxy t,
156                          final Map<String, String> mdc, final ThreadContext.ContextStack ndc, final String threadName,
157                          final StackTraceElement location, final long timestamp) {
158         name = loggerName;
159         this.marker = marker;
160         this.fqcnOfLogger = fqcn;
161         this.level = level;
162         this.message = message;
163         this.throwable = t;
164         this.mdc = mdc;
165         this.ndc = ndc;
166         this.timestamp = message instanceof TimestampMessage ? ((TimestampMessage) message).getTimestamp() : timestamp;
167         this.threadName = threadName;
168         this.location = location;
169         if (message != null && message instanceof LoggerNameAwareMessage) {
170             ((LoggerNameAwareMessage) message).setLoggerName(name);
171         }
172     }
173 
174     private static Map<String, String> createMap(final List<Property> properties) {
175         final Map<String, String> contextMap = ThreadContext.getImmutableContext();
176         if (contextMap == null && (properties == null || properties.size() == 0)) {
177             return null;
178         }
179         if (properties == null || properties.size() == 0) {
180             return contextMap; // contextMap is not null
181         }
182         final Map<String, String> map = new HashMap<String, String>(contextMap);
183 
184         for (final Property prop : properties) {
185             if (!map.containsKey(prop.getName())) {
186                 map.put(prop.getName(), prop.getValue());
187             }
188         }
189         return Collections.unmodifiableMap(map);
190     }
191 
192     /**
193      * Returns the logging Level.
194      * @return the Level associated with this event.
195      */
196     @Override
197     public Level getLevel() {
198         return level;
199     }
200 
201     /**
202      * Returns the name of the Logger used to generate the event.
203      * @return The Logger name.
204      */
205     @Override
206     public String getLoggerName() {
207         return name;
208     }
209 
210     /**
211      * Returns the Message associated with the event.
212      * @return The Message.
213      */
214     @Override
215     public Message getMessage() {
216         return message;
217     }
218 
219     /**
220      * Returns the name of the Thread on which the event was generated.
221      * @return The name of the Thread.
222      */
223     @Override
224     public String getThreadName() {
225         if (threadName == null) {
226             threadName = Thread.currentThread().getName();
227         }
228         return threadName;
229     }
230 
231     /**
232      * Returns the time in milliseconds from the epoch when the event occurred.
233      * @return The time the event occurred.
234      */
235     @Override
236     public long getMillis() {
237         return timestamp;
238     }
239 
240     /**
241      * Returns the Throwable associated with the event, or null.
242      * @return The Throwable associated with the event.
243      */
244     @Override
245     public Throwable getThrown() {
246         return throwable == null ? null : throwable.getThrowable();
247     }
248 
249     /**
250      * Returns the ThrowableProxy associated with the event, or null.
251      * @return The ThrowableProxy associated with the event.
252      */
253     public ThrowableProxy getThrownProxy() {
254         return throwable;
255     }
256 
257 
258     /**
259      * Returns the Marker associated with the event, or null.
260      * @return the Marker associated with the event.
261      */
262     @Override
263     public Marker getMarker() {
264         return marker;
265     }
266 
267     /**
268      * The fully qualified class name of the class that was called by the caller.
269      * @return the fully qualified class name of the class that is performing logging.
270      */
271     @Override
272     public String getFQCN() {
273         return fqcnOfLogger;
274     }
275 
276     /**
277      * Returns the immutable copy of the ThreadContext Map.
278      * @return The context Map.
279      */
280     @Override
281     public Map<String, String> getContextMap() {
282         return mdc == null ? ThreadContext.EMPTY_MAP : mdc;
283     }
284 
285     /**
286      * Returns an immutable copy of the ThreadContext stack.
287      * @return The context Stack.
288      */
289     @Override
290     public ThreadContext.ContextStack getContextStack() {
291         return ndc == null ? ThreadContext.EMPTY_STACK : ndc;
292     }
293 
294     /**
295      * Returns the StackTraceElement for the caller. This will be the entry that occurs right
296      * before the first occurrence of FQCN as a class name.
297      * @return the StackTraceElement for the caller.
298      */
299     @Override
300     public StackTraceElement getSource() {
301         if (location != null) {
302             return location;
303         }
304         if (fqcnOfLogger == null || !includeLocation) {
305             return null;
306         }
307         location = calcLocation(fqcnOfLogger);
308         return location;
309     }
310 
311     public static StackTraceElement calcLocation(final String fqcnOfLogger) {
312         if (fqcnOfLogger == null) {
313             return null;
314         }
315         final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
316         boolean next = false;
317         for (final StackTraceElement element : stackTrace) {
318             final String className = element.getClassName();
319             if (next) {
320                 if (fqcnOfLogger.equals(className)) {
321                     continue;
322                 }
323                 return element;
324             }
325 
326             if (fqcnOfLogger.equals(className)) {
327                 next = true;
328             } else if (NOT_AVAIL.equals(className)) {
329                 break;
330             }
331         }
332         return null;
333     }
334 
335     @Override
336     public boolean isIncludeLocation() {
337         return includeLocation;
338     }
339 
340     @Override
341     public void setIncludeLocation(final boolean includeLocation) {
342         this.includeLocation = includeLocation;
343     }
344 
345     @Override
346     public boolean isEndOfBatch() {
347         return endOfBatch;
348     }
349 
350     @Override
351     public void setEndOfBatch(final boolean endOfBatch) {
352         this.endOfBatch = endOfBatch;
353     }
354 
355     /**
356      * Creates a LogEventProxy that can be serialized.
357      * @return a LogEventProxy.
358      */
359     protected Object writeReplace() {
360         return new LogEventProxy(this, this.includeLocation);
361     }
362 
363     public static Serializable serialize(final Log4jLogEvent event,
364             final boolean includeLocation) {
365         return new LogEventProxy(event, includeLocation);
366     }
367 
368     public static Log4jLogEvent deserialize(final Serializable event) {
369         if (event == null) {
370             throw new NullPointerException("Event cannot be null");
371         }
372         if (event instanceof LogEventProxy) {
373             final LogEventProxy proxy = (LogEventProxy) event;
374             final Log4jLogEvent result = new Log4jLogEvent(proxy.name, proxy.marker,
375                     proxy.fqcnOfLogger, proxy.level, proxy.message,
376                     proxy.throwable, proxy.mdc, proxy.ndc, proxy.threadName,
377                     proxy.location, proxy.timestamp);
378             result.setEndOfBatch(proxy.isEndOfBatch);
379             result.setIncludeLocation(proxy.isLocationRequired);
380             return result;
381         }
382         throw new IllegalArgumentException("Event is not a serialized LogEvent: " + event.toString());
383     }
384 
385     private void readObject(final ObjectInputStream stream) throws InvalidObjectException {
386         throw new InvalidObjectException("Proxy required");
387     }
388 
389     @Override
390     public String toString() {
391         final StringBuilder sb = new StringBuilder();
392         final String n = name.isEmpty() ? "root" : name;
393         sb.append("Logger=").append(n);
394         sb.append(" Level=").append(level.name());
395         sb.append(" Message=").append(message.getFormattedMessage());
396         return sb.toString();
397     }
398 
399     /**
400      * Proxy pattern used to serialize the LogEvent.
401      */
402     private static class LogEventProxy implements Serializable {
403 
404         private static final long serialVersionUID = -7139032940312647146L;
405         private final String fqcnOfLogger;
406         private final Marker marker;
407         private final Level level;
408         private final String name;
409         private final Message message;
410         private final long timestamp;
411         private final ThrowableProxy throwable;
412         private final Map<String, String> mdc;
413         private final ThreadContext.ContextStack ndc;
414         private final String threadName;
415         private final StackTraceElement location;
416         private final boolean isLocationRequired;
417         private final boolean isEndOfBatch;
418 
419         public LogEventProxy(final Log4jLogEvent event, final boolean includeLocation) {
420             this.fqcnOfLogger = event.fqcnOfLogger;
421             this.marker = event.marker;
422             this.level = event.level;
423             this.name = event.name;
424             this.message = event.message;
425             this.timestamp = event.timestamp;
426             this.throwable = event.throwable;
427             this.mdc = event.mdc;
428             this.ndc = event.ndc;
429             this.location = includeLocation ? event.getSource() : null;
430             this.threadName = event.getThreadName();
431             this.isLocationRequired = includeLocation;
432             this.isEndOfBatch = event.endOfBatch;
433         }
434 
435         /**
436          * Returns a Log4jLogEvent using the data in the proxy.
437          * @return Log4jLogEvent.
438          */
439         protected Object readResolve() {
440             final Log4jLogEvent result = new Log4jLogEvent(name, marker, fqcnOfLogger,
441                     level, message, throwable, mdc, ndc, threadName, location,
442                     timestamp);
443             result.setEndOfBatch(isEndOfBatch);
444             result.setIncludeLocation(isLocationRequired);
445             return result;
446         }
447     }
448 }