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  import java.util.Objects;
27  
28  import org.apache.logging.log4j.Level;
29  import org.apache.logging.log4j.Marker;
30  import org.apache.logging.log4j.ThreadContext;
31  import org.apache.logging.log4j.core.LogEvent;
32  import org.apache.logging.log4j.core.async.RingBufferLogEvent;
33  import org.apache.logging.log4j.core.config.Property;
34  import org.apache.logging.log4j.core.util.Clock;
35  import org.apache.logging.log4j.core.util.ClockFactory;
36  import org.apache.logging.log4j.core.util.DummyNanoClock;
37  import org.apache.logging.log4j.core.util.NanoClock;
38  import org.apache.logging.log4j.message.LoggerNameAwareMessage;
39  import org.apache.logging.log4j.message.Message;
40  import org.apache.logging.log4j.message.TimestampMessage;
41  import org.apache.logging.log4j.util.Strings;
42  
43  /**
44   * Implementation of a LogEvent.
45   */
46  public class Log4jLogEvent implements LogEvent {
47  
48      private static final long serialVersionUID = -1351367343806656055L;
49      private static final Clock CLOCK = ClockFactory.getClock();
50      private static volatile NanoClock nanoClock = new DummyNanoClock();
51      private final String loggerFqcn;
52      private final Marker marker;
53      private final Level level;
54      private final String loggerName;
55      private final Message message;
56      private final long timeMillis;
57      private final transient Throwable thrown;
58      private ThrowableProxy thrownProxy;
59      private final Map<String, String> contextMap;
60      private final ThreadContext.ContextStack contextStack;
61      private String threadName;
62      private StackTraceElement source;
63      private boolean includeLocation;
64      private boolean endOfBatch = false;
65      /** @since Log4J 2.4 */
66      private final transient long nanoTime;
67  
68      /** LogEvent Builder helper class. */
69      public static class Builder implements org.apache.logging.log4j.core.util.Builder<LogEvent> {
70  
71          private String loggerFqcn;
72          private Marker marker;
73          private Level level;
74          private String loggerName;
75          private Message message;
76          private Throwable thrown;
77          private long timeMillis = CLOCK.currentTimeMillis();
78          private ThrowableProxy thrownProxy;
79          private Map<String, String> contextMap = ThreadContext.getImmutableContext();
80          private ThreadContext.ContextStack contextStack = ThreadContext.getImmutableStack();
81          private String threadName = null;
82          private StackTraceElement source;
83          private boolean includeLocation;
84          private boolean endOfBatch = false;
85          private long nanoTime;
86          
87          public Builder() {
88          }
89          
90          public Builder(LogEvent other) {
91              Objects.requireNonNull(other);
92              if (other instanceof RingBufferLogEvent) {
93                  RingBufferLogEvent evt = (RingBufferLogEvent) other;
94                  evt.initializeBuilder(this);
95                  return;
96              }
97              this.loggerFqcn = other.getLoggerFqcn();
98              this.marker = other.getMarker();
99              this.level = other.getLevel();
100             this.loggerName = other.getLoggerName();
101             this.message = other.getMessage();
102             this.timeMillis = other.getTimeMillis();
103             this.thrown = other.getThrown();
104             this.contextMap = other.getContextMap();
105             this.contextStack = other.getContextStack();
106             this.includeLocation = other.isIncludeLocation();
107             this.endOfBatch = other.isEndOfBatch();
108             this.nanoTime = other.getNanoTime();
109             
110             // Avoid unnecessarily initializing thrownProxy, threadName and source if possible
111             if (other instanceof Log4jLogEvent) {
112                 Log4jLogEvent evt = (Log4jLogEvent) other;
113                 this.thrownProxy = evt.thrownProxy;
114                 this.source = evt.source;
115                 this.threadName = evt.threadName;
116             } else {
117                 this.thrownProxy = other.getThrownProxy();
118                 this.source = other.getSource();
119                 this.threadName = other.getThreadName();
120             }
121         }
122 
123         public Builder setLevel(final Level level) {
124             this.level = level;
125             return this;
126         }
127 
128         public Builder setLoggerFqcn(final String loggerFqcn) {
129             this.loggerFqcn = loggerFqcn;
130             return this;
131         }
132 
133         public Builder setLoggerName(final String loggerName) {
134             this.loggerName = loggerName;
135             return this;
136         }
137 
138         public Builder setMarker(final Marker marker) {
139             this.marker = marker;
140             return this;
141         }
142 
143         public Builder setMessage(final Message message) {
144             this.message = message;
145             return this;
146         }
147 
148         public Builder setThrown(final Throwable thrown) {
149             this.thrown = thrown;
150             return this;
151         }
152 
153         public Builder setTimeMillis(long timeMillis) {
154             this.timeMillis = timeMillis;
155             return this;
156         }
157 
158         public Builder setThrownProxy(ThrowableProxy thrownProxy) {
159             this.thrownProxy = thrownProxy;
160             return this;
161         }
162 
163         public Builder setContextMap(Map<String, String> contextMap) {
164             this.contextMap = contextMap;
165             return this;
166         }
167 
168         public Builder setContextStack(ThreadContext.ContextStack contextStack) {
169             this.contextStack = contextStack;
170             return this;
171         }
172 
173         public Builder setThreadName(String threadName) {
174             this.threadName = threadName;
175             return this;
176         }
177 
178         public Builder setSource(StackTraceElement source) {
179             this.source = source;
180             return this;
181         }
182 
183         public Builder setIncludeLocation(boolean includeLocation) {
184             this.includeLocation = includeLocation;
185             return this;
186         }
187 
188         public Builder setEndOfBatch(boolean endOfBatch) {
189             this.endOfBatch = endOfBatch;
190             return this;
191         }
192 
193         /**
194          * Sets the nano time for the event.
195          * @param nanoTime The value of the running Java Virtual Machine's high-resolution time source when the event
196          *          was created.
197          * @return this builder
198          */
199         public Builder setNanoTime(long nanoTime) {
200             this.nanoTime = nanoTime;
201             return this;
202         }
203 
204         @Override
205         public Log4jLogEvent build() {
206             final Log4jLogEvent result = new Log4jLogEvent(loggerName, marker, loggerFqcn, level, message, thrown,
207                     thrownProxy, contextMap, contextStack, threadName, source, timeMillis, nanoTime);
208             result.setIncludeLocation(includeLocation);
209             result.setEndOfBatch(endOfBatch);
210             return result;
211         }
212     }
213 
214     /**
215      * Returns a new empty {@code Log4jLogEvent.Builder} with all fields empty.
216      * @return a new empty builder.
217      */
218     public static Builder newBuilder() {
219         return new Builder();
220     }
221 
222     public Log4jLogEvent() {
223         this(Strings.EMPTY, null, Strings.EMPTY, null, null, (Throwable) null, null, null, null, null, null,
224                 CLOCK.currentTimeMillis(), nanoClock.nanoTime());
225     }
226 
227     /**
228     *
229     * @deprecated use {@link Log4jLogEvent.Builder} instead. This constructor will be removed in an upcoming release.
230     */
231    @Deprecated
232    public Log4jLogEvent(final long timestamp) {
233        this(Strings.EMPTY, null, Strings.EMPTY, null, null, (Throwable) null, null, null, null, null, null,
234                timestamp, nanoClock.nanoTime());
235    }
236 
237    /**
238     * Constructor.
239     * @param loggerName The name of the Logger.
240     * @param marker The Marker or null.
241     * @param loggerFQCN The fully qualified class name of the caller.
242     * @param level The logging Level.
243     * @param message The Message.
244     * @param t A Throwable or null.
245     * @deprecated use {@link Log4jLogEvent.Builder} instead. This constructor will be removed in an upcoming release.
246     */
247    @Deprecated
248    public Log4jLogEvent(final String loggerName, final Marker marker, final String loggerFQCN, final Level level,
249                         final Message message, final Throwable t) {
250        this(loggerName, marker, loggerFQCN, level, message, null, t);
251    }
252 
253    /**
254     * Constructor.
255     * @param loggerName The name of the Logger.
256     * @param marker The Marker or null.
257     * @param loggerFQCN The fully qualified class name of the caller.
258     * @param level The logging Level.
259     * @param message The Message.
260     * @param properties properties to add to the event.
261     * @param t A Throwable or null.
262     */
263    // This constructor is called from LogEventFactories.
264    public Log4jLogEvent(final String loggerName, final Marker marker, final String loggerFQCN, final Level level,
265                         final Message message, final List<Property> properties, final Throwable t) {
266        this(loggerName, marker, loggerFQCN, level, message, t, null,
267            createMap(properties),
268            ThreadContext.getDepth() == 0 ? null : ThreadContext.cloneStack(), // mutable copy
269            null, // thread name
270            null, // stack trace element
271            // LOG4J2-628 use log4j.Clock for timestamps
272            // LOG4J2-744 unless TimestampMessage already has one
273            message instanceof TimestampMessage ? ((TimestampMessage) message).getTimestamp() :
274                CLOCK.currentTimeMillis(),
275            nanoClock.nanoTime());
276    }
277 
278    /**
279     * Constructor.
280     * @param loggerName The name of the Logger.
281     * @param marker The Marker or null.
282     * @param loggerFQCN The fully qualified class name of the caller.
283     * @param level The logging Level.
284     * @param message The Message.
285     * @param t A Throwable or null.
286     * @param mdc The mapped diagnostic context.
287     * @param ndc the nested diagnostic context.
288     * @param threadName The name of the thread.
289     * @param location The locations of the caller.
290     * @param timestampMillis The timestamp of the event.
291     * @deprecated use {@link Log4jLogEvent.Builder} instead. This constructor will be removed in an upcoming release.
292     */
293    @Deprecated
294 public Log4jLogEvent(final String loggerName, final Marker marker, final String loggerFQCN, final Level level,
295                         final Message message, final Throwable t, final Map<String, String> mdc,
296                         final ThreadContext.ContextStack ndc, final String threadName,
297                         final StackTraceElement location, final long timestampMillis) {
298        this(loggerName, marker, loggerFQCN, level, message, t, null, mdc, ndc, threadName,
299                location, timestampMillis, nanoClock.nanoTime());
300    }
301 
302    /**
303     * Create a new LogEvent.
304     * @param loggerName The name of the Logger.
305     * @param marker The Marker or null.
306     * @param loggerFQCN The fully qualified class name of the caller.
307     * @param level The logging Level.
308     * @param message The Message.
309     * @param thrown A Throwable or null.
310     * @param thrownProxy A ThrowableProxy or null.
311     * @param mdc The mapped diagnostic context.
312     * @param ndc the nested diagnostic context.
313     * @param threadName The name of the thread.
314     * @param location The locations of the caller.
315     * @param timestamp The timestamp of the event.
316     * @return a new LogEvent
317     * @deprecated use {@link Log4jLogEvent.Builder} instead. This method will be removed in an upcoming release.
318     */
319     @Deprecated
320     public static Log4jLogEvent createEvent(final String loggerName, final Marker marker, final String loggerFQCN,
321                                             final Level level, final Message message, final Throwable thrown,
322                                             final ThrowableProxy thrownProxy,
323                                             final Map<String, String> mdc, final ThreadContext.ContextStack ndc,
324                                             final String threadName, final StackTraceElement location,
325                                             final long timestamp) {
326         final Log4jLogEvent result = new Log4jLogEvent(loggerName, marker, loggerFQCN, level, message, thrown, 
327                 thrownProxy, mdc, ndc, threadName, location, timestamp, nanoClock.nanoTime());
328         return result;
329     }
330 
331     /**
332      * Constructor.
333      * @param loggerName The name of the Logger.
334      * @param marker The Marker or null.
335      * @param loggerFQCN The fully qualified class name of the caller.
336      * @param level The logging Level.
337      * @param message The Message.
338      * @param thrown A Throwable or null.
339      * @param thrownProxy A ThrowableProxy or null.
340      * @param contextMap The mapped diagnostic context.
341      * @param contextStack the nested diagnostic context.
342      * @param threadName The name of the thread.
343      * @param source The locations of the caller.
344      * @param timestamp The timestamp of the event.
345      * @param nanoTime The value of the running Java Virtual Machine's high-resolution time source when the event was
346      *          created.
347      */
348     private Log4jLogEvent(final String loggerName, final Marker marker, final String loggerFQCN, final Level level,
349             final Message message, final Throwable thrown, final ThrowableProxy thrownProxy,
350             final Map<String, String> contextMap, final ThreadContext.ContextStack contextStack,
351             final String threadName, final StackTraceElement source, final long timestampMillis, final long nanoTime) {
352         this.loggerName = loggerName;
353         this.marker = marker;
354         this.loggerFqcn = loggerFQCN;
355         this.level = (level == null) ? Level.OFF : level; // LOG4J2-462, LOG4J2-465
356         this.message = message;
357         this.thrown = thrown;
358         this.thrownProxy = thrownProxy;
359         this.contextMap = contextMap == null ? ThreadContext.EMPTY_MAP : contextMap;
360         this.contextStack = contextStack == null ? ThreadContext.EMPTY_STACK : contextStack;
361         this.timeMillis = message instanceof TimestampMessage
362                 ? ((TimestampMessage) message).getTimestamp()
363                 : timestampMillis;
364         this.threadName = threadName;
365         this.source = source;
366         if (message != null && message instanceof LoggerNameAwareMessage) {
367             ((LoggerNameAwareMessage) message).setLoggerName(loggerName);
368         }
369         this.nanoTime = nanoTime;
370     }
371 
372     private static Map<String, String> createMap(final List<Property> properties) {
373         final Map<String, String> contextMap = ThreadContext.getImmutableContext();
374         if (properties == null || properties.isEmpty()) {
375             return contextMap; // may be ThreadContext.EMPTY_MAP but not null
376         }
377         final Map<String, String> map = new HashMap<>(contextMap);
378 
379         for (final Property prop : properties) {
380             if (!map.containsKey(prop.getName())) {
381                 map.put(prop.getName(), prop.getValue());
382             }
383         }
384         return Collections.unmodifiableMap(map);
385     }
386     
387     /**
388      * Returns the {@code NanoClock} to use for creating the nanoTime timestamp of log events.
389      * @return the {@code NanoClock} to use for creating the nanoTime timestamp of log events
390      */
391     public static NanoClock getNanoClock() {
392         return nanoClock;
393     }
394     
395     /**
396      * Sets the {@code NanoClock} to use for creating the nanoTime timestamp of log events.
397      * <p>
398      * FOR INTERNAL USE. This method may be called with a different {@code NanoClock} implementation when the
399      * configuration changes.
400      * 
401      * @param nanoClock the {@code NanoClock} to use for creating the nanoTime timestamp of log events
402      */
403     public static void setNanoClock(NanoClock nanoClock) {
404         Log4jLogEvent.nanoClock = Objects.requireNonNull(nanoClock, "NanoClock must be non-null");
405     }
406     
407     /**
408      * Returns a new fully initialized {@code Log4jLogEvent.Builder} containing a copy of all fields of this event.
409      * @return a new fully initialized builder.
410      */
411     public Builder asBuilder() {
412         return new Builder(this);
413     }
414 
415     /**
416      * Returns the logging Level.
417      * @return the Level associated with this event.
418      */
419     @Override
420     public Level getLevel() {
421         return level;
422     }
423 
424     /**
425      * Returns the name of the Logger used to generate the event.
426      * @return The Logger name.
427      */
428     @Override
429     public String getLoggerName() {
430         return loggerName;
431     }
432 
433     /**
434      * Returns the Message associated with the event.
435      * @return The Message.
436      */
437     @Override
438     public Message getMessage() {
439         return message;
440     }
441 
442     /**
443      * Returns the name of the Thread on which the event was generated.
444      * @return The name of the Thread.
445      */
446     @Override
447     public String getThreadName() {
448         if (threadName == null) {
449             threadName = Thread.currentThread().getName();
450         }
451         return threadName;
452     }
453 
454     /**
455      * Returns the time in milliseconds from the epoch when the event occurred.
456      * @return The time the event occurred.
457      */
458     @Override
459     public long getTimeMillis() {
460         return timeMillis;
461     }
462 
463     /**
464      * Returns the Throwable associated with the event, or null.
465      * @return The Throwable associated with the event.
466      */
467     @Override
468     public Throwable getThrown() {
469         return thrown;
470     }
471 
472     /**
473      * Returns the ThrowableProxy associated with the event, or null.
474      * @return The ThrowableProxy associated with the event.
475      */
476     @Override
477     public ThrowableProxy getThrownProxy() {
478         if (thrownProxy == null && thrown != null) {
479             thrownProxy = new ThrowableProxy(thrown);
480         }
481         return thrownProxy;
482     }
483 
484 
485     /**
486      * Returns the Marker associated with the event, or null.
487      * @return the Marker associated with the event.
488      */
489     @Override
490     public Marker getMarker() {
491         return marker;
492     }
493 
494     /**
495      * The fully qualified class name of the class that was called by the caller.
496      * @return the fully qualified class name of the class that is performing logging.
497      */
498     @Override
499     public String getLoggerFqcn() {
500         return loggerFqcn;
501     }
502 
503     /**
504      * Returns the immutable copy of the ThreadContext Map.
505      * @return The context Map.
506      */
507     @Override
508     public Map<String, String> getContextMap() {
509         return contextMap;
510     }
511 
512     /**
513      * Returns an immutable copy of the ThreadContext stack.
514      * @return The context Stack.
515      */
516     @Override
517     public ThreadContext.ContextStack getContextStack() {
518         return contextStack;
519     }
520 
521     /**
522      * Returns the StackTraceElement for the caller. This will be the entry that occurs right
523      * before the first occurrence of FQCN as a class name.
524      * @return the StackTraceElement for the caller.
525      */
526     @Override
527     public StackTraceElement getSource() {
528         if (source != null) {
529             return source;
530         }
531         if (loggerFqcn == null || !includeLocation) {
532             return null;
533         }
534         source = calcLocation(loggerFqcn);
535         return source;
536     }
537 
538     public static StackTraceElement calcLocation(final String fqcnOfLogger) {
539         if (fqcnOfLogger == null) {
540             return null;
541         }
542         final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
543         StackTraceElement last = null;
544         for (int i = stackTrace.length - 1; i > 0; i--) {
545             final String className = stackTrace[i].getClassName();
546             if (fqcnOfLogger.equals(className)) {
547                 return last;
548             }
549             last = stackTrace[i];
550         }
551         return null;
552     }
553 
554     @Override
555     public boolean isIncludeLocation() {
556         return includeLocation;
557     }
558 
559     @Override
560     public void setIncludeLocation(final boolean includeLocation) {
561         this.includeLocation = includeLocation;
562     }
563 
564     @Override
565     public boolean isEndOfBatch() {
566         return endOfBatch;
567     }
568 
569     @Override
570     public void setEndOfBatch(final boolean endOfBatch) {
571         this.endOfBatch = endOfBatch;
572     }
573 
574     @Override
575     public long getNanoTime() {
576         return nanoTime;
577     }
578 
579     /**
580      * Creates a LogEventProxy that can be serialized.
581      * @return a LogEventProxy.
582      */
583     protected Object writeReplace() {
584         getThrownProxy(); // ensure ThrowableProxy is initialized
585         return new LogEventProxy(this, this.includeLocation);
586     }
587 
588     public static Serializable serialize(final Log4jLogEvent event,
589             final boolean includeLocation) {
590         event.getThrownProxy(); // ensure ThrowableProxy is initialized
591         return new LogEventProxy(event, includeLocation);
592     }
593 
594     public static boolean canDeserialize(final Serializable event) {
595         return event instanceof LogEventProxy;
596     }
597 
598     public static Log4jLogEvent deserialize(final Serializable event) {
599         Objects.requireNonNull(event, "Event cannot be null");
600         if (event instanceof LogEventProxy) {
601             final LogEventProxy proxy = (LogEventProxy) event;
602             final Log4jLogEvent result = new Log4jLogEvent(proxy.loggerName, proxy.marker,
603                     proxy.loggerFQCN, proxy.level, proxy.message,
604                     proxy.thrown, proxy.thrownProxy, proxy.contextMap, proxy.contextStack, proxy.threadName,
605                     proxy.source, proxy.timeMillis, proxy.nanoTime);
606             result.setEndOfBatch(proxy.isEndOfBatch);
607             result.setIncludeLocation(proxy.isLocationRequired);
608             return result;
609         }
610         throw new IllegalArgumentException("Event is not a serialized LogEvent: " + event.toString());
611     }
612 
613     private void readObject(final ObjectInputStream stream) throws InvalidObjectException {
614         throw new InvalidObjectException("Proxy required");
615     }
616 
617     @Override
618     public String toString() {
619         final StringBuilder sb = new StringBuilder();
620         final String n = loggerName.isEmpty() ? "root" : loggerName;
621         sb.append("Logger=").append(n);
622         sb.append(" Level=").append(level.name());
623         sb.append(" Message=").append(message.getFormattedMessage());
624         return sb.toString();
625     }
626 
627     @Override
628     public boolean equals(final Object o) {
629         if (this == o) {
630             return true;
631         }
632         if (o == null || getClass() != o.getClass()) {
633             return false;
634         }
635 
636         final Log4jLogEvent that = (Log4jLogEvent) o;
637 
638         if (endOfBatch != that.endOfBatch) {
639             return false;
640         }
641         if (includeLocation != that.includeLocation) {
642             return false;
643         }
644         if (timeMillis != that.timeMillis) {
645             return false;
646         }
647         if (nanoTime != that.nanoTime) {
648             return false;
649         }
650         if (loggerFqcn != null ? !loggerFqcn.equals(that.loggerFqcn) : that.loggerFqcn != null) {
651             return false;
652         }
653         if (level != null ? !level.equals(that.level) : that.level != null) {
654             return false;
655         }
656         if (source != null ? !source.equals(that.source) : that.source != null) {
657             return false;
658         }
659         if (marker != null ? !marker.equals(that.marker) : that.marker != null) {
660             return false;
661         }
662         if (contextMap != null ? !contextMap.equals(that.contextMap) : that.contextMap != null) {
663             return false;
664         }
665         if (!message.equals(that.message)) {
666             return false;
667         }
668         if (!loggerName.equals(that.loggerName)) {
669             return false;
670         }
671         if (contextStack != null ? !contextStack.equals(that.contextStack) : that.contextStack != null) {
672             return false;
673         }
674         if (threadName != null ? !threadName.equals(that.threadName) : that.threadName != null) {
675             return false;
676         }
677         if (thrown != null ? !thrown.equals(that.thrown) : that.thrown != null) {
678             return false;
679         }
680         if (thrownProxy != null ? !thrownProxy.equals(that.thrownProxy) : that.thrownProxy != null) {
681             return false;
682         }
683 
684         return true;
685     }
686 
687     @Override
688     public int hashCode() {
689         // Check:OFF: MagicNumber
690         int result = loggerFqcn != null ? loggerFqcn.hashCode() : 0;
691         result = 31 * result + (marker != null ? marker.hashCode() : 0);
692         result = 31 * result + (level != null ? level.hashCode() : 0);
693         result = 31 * result + loggerName.hashCode();
694         result = 31 * result + message.hashCode();
695         result = 31 * result + (int) (timeMillis ^ (timeMillis >>> 32));
696         result = 31 * result + (int) (nanoTime ^ (nanoTime >>> 32));
697         result = 31 * result + (thrown != null ? thrown.hashCode() : 0);
698         result = 31 * result + (thrownProxy != null ? thrownProxy.hashCode() : 0);
699         result = 31 * result + (contextMap != null ? contextMap.hashCode() : 0);
700         result = 31 * result + (contextStack != null ? contextStack.hashCode() : 0);
701         result = 31 * result + (threadName != null ? threadName.hashCode() : 0);
702         result = 31 * result + (source != null ? source.hashCode() : 0);
703         result = 31 * result + (includeLocation ? 1 : 0);
704         result = 31 * result + (endOfBatch ? 1 : 0);
705         // Check:ON: MagicNumber
706         return result;
707     }
708 
709     /**
710      * Proxy pattern used to serialize the LogEvent.
711      */
712     private static class LogEventProxy implements Serializable {
713 
714         private static final long serialVersionUID = -7139032940312647146L;
715         private final String loggerFQCN;
716         private final Marker marker;
717         private final Level level;
718         private final String loggerName;
719         private final Message message;
720         private final long timeMillis;
721         private final transient Throwable thrown;
722         private final ThrowableProxy thrownProxy;
723         private final Map<String, String> contextMap;
724         private final ThreadContext.ContextStack contextStack;
725         private final String threadName;
726         private final StackTraceElement source;
727         private final boolean isLocationRequired;
728         private final boolean isEndOfBatch;
729         /** @since Log4J 2.4 */
730         private final transient long nanoTime;
731 
732         public LogEventProxy(final Log4jLogEvent event, final boolean includeLocation) {
733             this.loggerFQCN = event.loggerFqcn;
734             this.marker = event.marker;
735             this.level = event.level;
736             this.loggerName = event.loggerName;
737             this.message = event.message;
738             this.timeMillis = event.timeMillis;
739             this.thrown = event.thrown;
740             this.thrownProxy = event.thrownProxy;
741             this.contextMap = event.contextMap;
742             this.contextStack = event.contextStack;
743             this.source = includeLocation ? event.getSource() : null;
744             this.threadName = event.getThreadName();
745             this.isLocationRequired = includeLocation;
746             this.isEndOfBatch = event.endOfBatch;
747             this.nanoTime = event.nanoTime;
748         }
749 
750         /**
751          * Returns a Log4jLogEvent using the data in the proxy.
752          * @return Log4jLogEvent.
753          */
754         protected Object readResolve() {
755             final Log4jLogEvent result = new Log4jLogEvent(loggerName, marker, loggerFQCN, level, message, thrown,
756                     thrownProxy, contextMap, contextStack, threadName, source, timeMillis, nanoTime);
757             result.setEndOfBatch(isEndOfBatch);
758             result.setIncludeLocation(isLocationRequired);
759             return result;
760         }
761     }
762 }