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 org.apache.logging.log4j.Level;
20  import org.apache.logging.log4j.ThreadContext;
21  import org.apache.logging.log4j.Marker;
22  import org.apache.logging.log4j.core.LogEvent;
23  import org.apache.logging.log4j.core.config.Property;
24  import org.apache.logging.log4j.message.LoggerNameAwareMessage;
25  import org.apache.logging.log4j.message.Message;
26  import org.apache.logging.log4j.message.TimestampMessage;
27  
28  import java.io.InvalidObjectException;
29  import java.io.ObjectInputStream;
30  import java.io.Serializable;
31  import java.util.Collections;
32  import java.util.HashMap;
33  import java.util.List;
34  import java.util.Map;
35  
36  /**
37   * Implementation of a LogEvent.
38   */
39  public class Log4jLogEvent implements LogEvent, Serializable {
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       * Constructor.
59       * @param loggerName The name of the Logger.
60       * @param marker The Marker or null.
61       * @param fqcn The fully qualified class name of the caller.
62       * @param level The logging Level.
63       * @param message The Message.
64       * @param t A Throwable or null.
65       */
66      public Log4jLogEvent(final String loggerName, final Marker marker, final String fqcn, final Level level,
67                           final Message message, final Throwable t) {
68          this(loggerName, marker, fqcn, level, message, null, t);
69      }
70  
71      /**
72       * Constructor.
73       * @param loggerName The name of the Logger.
74       * @param marker The Marker or null.
75       * @param fqcn The fully qualified class name of the caller.
76       * @param level The logging Level.
77       * @param message The Message.
78       * @param properties properties to add to the event.
79       * @param t A Throwable or null.
80       */
81      public Log4jLogEvent(final String loggerName, final Marker marker, final String fqcn, final Level level,
82                           final Message message, final List<Property> properties, final Throwable t) {
83          this(loggerName, marker, fqcn, level, message, t,
84              createMap(properties),
85              ThreadContext.getDepth() == 0 ? null : ThreadContext.cloneStack(), null,
86              null, System.currentTimeMillis());
87      }
88  
89      /**
90       * Constructor.
91       * @param loggerName The name of the Logger.
92       * @param marker The Marker or null.
93       * @param fqcn The fully qualified class name of the caller.
94       * @param level The logging Level.
95       * @param message The Message.
96       * @param t A Throwable or null.
97       * @param mdc The mapped diagnostic context.
98       * @param ndc the nested diagnostic context.
99       * @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 }