View Javadoc
1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework. Copyright (C) 1999-2015, QOS.ch. All rights
3    * reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under either the terms of the Eclipse Public License
6    * v1.0 as published by the Eclipse Foundation
7    *
8    * or (per the licensee's choosing)
9    *
10   * under the terms of the GNU Lesser General Public License version 2.1 as published by the Free Software Foundation.
11   */
12  package ch.qos.logback.classic.spi;
13  
14  import java.io.IOException;
15  import java.io.ObjectOutputStream;
16  import java.lang.reflect.InvocationTargetException;
17  import java.lang.reflect.Method;
18  import java.time.Clock;
19  import java.time.Instant;
20  import java.util.ArrayList;
21  import java.util.Collections;
22  import java.util.List;
23  import java.util.Map;
24  
25  import ch.qos.logback.core.CoreConstants;
26  import ch.qos.logback.core.util.EnvUtil;
27  import ch.qos.logback.core.util.StringUtil;
28  import org.slf4j.Marker;
29  import org.slf4j.event.KeyValuePair;
30  import org.slf4j.helpers.MessageFormatter;
31  import org.slf4j.spi.MDCAdapter;
32  
33  import ch.qos.logback.classic.Level;
34  import ch.qos.logback.classic.Logger;
35  import ch.qos.logback.classic.LoggerContext;
36  import ch.qos.logback.classic.util.LogbackMDCAdapter;
37  import ch.qos.logback.core.spi.SequenceNumberGenerator;
38  
39  /**
40   * The internal representation of logging events. When an affirmative decision is made to log then a
41   * <code>LoggingEvent</code> instance is created. This instance is passed around to the different logback-classic
42   * components.
43   * <p/>
44   * <p>
45   * Writers of logback-classic components such as appenders should be aware of that some of the LoggingEvent fields are
46   * initialized lazily. Therefore, an appender wishing to output data to be later correctly read by a receiver, must
47   * initialize "lazy" fields prior to writing them out. See the {@link #prepareForDeferredProcessing()} method for the
48   * exact list.
49   * </p>
50   *
51   * @author Ceki G&uuml;lc&uuml;
52   * @author S&eacute;bastien Pennec
53   */
54  public class LoggingEvent implements ILoggingEvent {
55  
56      public static final String VIRTUAL_THREAD_NAME_PREFIX = "virtual-";
57      public static final String REGULAR_UNNAMED_THREAD_PREFIX = "unnamed-";
58  
59      /**
60       * Fully qualified name of the calling Logger class. This field does not survive serialization.
61       * <p/>
62       * <p/>
63       * Note that the getCallerInformation() method relies on this fact.
64       */
65      transient String fqnOfLoggerClass;
66  
67      /**
68       * The name of thread in which this logging event was generated.
69       */
70      private String threadName;
71  
72      private String loggerName;
73      private LoggerContext loggerContext;
74      private LoggerContextVO loggerContextVO;
75  
76      /**
77       * Level of logging event.
78       * <p/>
79       * <p>
80       * This field should not be accessed directly. You should use the {@link #getLevel} method instead.
81       * </p>
82       */
83      private transient Level level;
84  
85      private String message;
86  
87      // we gain significant space at serialization time by marking
88      // formattedMessage as transient and constructing it lazily in
89      // getFormattedMessage()
90      transient String formattedMessage;
91  
92      private transient Object[] argumentArray;
93  
94      private ThrowableProxy throwableProxy;
95  
96      private StackTraceElement[] callerDataArray;
97  
98      private List<Marker> markerList;
99  
100     private Map<String, String> mdcPropertyMap;
101 
102     /**
103      * @since 1.3.0
104      */
105     List<KeyValuePair> keyValuePairs;
106 
107     /**
108      * The number of milliseconds elapsed from 1/1/1970 until logging event was created.
109      */
110     private Instant instant;
111 
112     private long timeStamp;
113     private int nanoseconds;
114 
115     private long sequenceNumber;
116 
117     public LoggingEvent() {
118     }
119 
120     public LoggingEvent(String fqcn, Logger logger, Level level, String message, Throwable throwable,
121             Object[] argArray) {
122         this.fqnOfLoggerClass = fqcn;
123         this.loggerName = logger.getName();
124         this.loggerContext = logger.getLoggerContext();
125         this.loggerContextVO = loggerContext.getLoggerContextRemoteView();
126         this.level = level;
127 
128         this.message = message;
129         this.argumentArray = argArray;
130 
131         Instant instant = Clock.systemUTC().instant();
132         initTmestampFields(instant);
133 
134         if (loggerContext != null) {
135             SequenceNumberGenerator sequenceNumberGenerator = loggerContext.getSequenceNumberGenerator();
136             if (sequenceNumberGenerator != null)
137                 sequenceNumber = sequenceNumberGenerator.nextSequenceNumber();
138         }
139 
140         if (throwable == null) {
141             throwable = extractThrowableAnRearrangeArguments(argArray);
142         }
143 
144         if (throwable != null) {
145             this.throwableProxy = new ThrowableProxy(throwable);
146 
147             if (loggerContext != null && loggerContext.isPackagingDataEnabled()) {
148                 this.throwableProxy.calculatePackagingData();
149             }
150         }
151     }
152 
153     void initTmestampFields(Instant instant) {
154         this.instant = instant;
155         long epochSecond = instant.getEpochSecond();
156         this.nanoseconds = instant.getNano();
157         long milliseconds = nanoseconds / 1000_000;
158         this.timeStamp = (epochSecond * 1000) + (milliseconds);
159     }
160 
161     private Throwable extractThrowableAnRearrangeArguments(Object[] argArray) {
162         Throwable extractedThrowable = EventArgUtil.extractThrowable(argArray);
163         if (EventArgUtil.successfulExtraction(extractedThrowable)) {
164             this.argumentArray = EventArgUtil.trimmedCopy(argArray);
165         }
166         return extractedThrowable;
167     }
168 
169     public void setArgumentArray(Object[] argArray) {
170         if (this.argumentArray != null) {
171             throw new IllegalStateException("argArray has been already set");
172         }
173         this.argumentArray = argArray;
174     }
175 
176     public Object[] getArgumentArray() {
177         return this.argumentArray;
178     }
179 
180     public void addKeyValuePair(KeyValuePair kvp) {
181         if (keyValuePairs == null) {
182             keyValuePairs = new ArrayList<>(4);
183         }
184         keyValuePairs.add(kvp);
185     }
186 
187     public void setKeyValuePairs(List<KeyValuePair> kvpList) {
188         this.keyValuePairs = kvpList;
189     }
190 
191     @Override
192     public List<KeyValuePair> getKeyValuePairs() {
193         return this.keyValuePairs;
194     }
195 
196     public Level getLevel() {
197         return level;
198     }
199 
200     public String getLoggerName() {
201         return loggerName;
202     }
203 
204     public void setLoggerName(String loggerName) {
205         this.loggerName = loggerName;
206     }
207 
208     public String getThreadName() {
209         if (threadName == null) {
210             threadName = extractThreadName(Thread.currentThread());
211         }
212         return threadName;
213     }
214 
215     /**
216      * Extracts the name of aThread by calling {@link Thread#getName()}. If the value is null, then use the value
217      * returned by {@link Thread#getId()} prefixing with {@link #VIRTUAL_THREAD_NAME_PREFIX} if thread is virtual or
218      * with {@link #REGULAR_UNNAMED_THREAD_PREFIX} if regular.
219      *
220      * @param aThread
221      * @return
222      * @since 1.5.0
223      */
224     private String extractThreadName(Thread aThread) {
225         if (aThread == null) {
226             return CoreConstants.NA;
227         }
228         String threadName = aThread.getName();
229         if (StringUtil.notNullNorEmpty(threadName))
230             return threadName;
231         Long virtualThreadId = getVirtualThreadId(aThread);
232         if (virtualThreadId != null) {
233             return VIRTUAL_THREAD_NAME_PREFIX + virtualThreadId;
234         } else {
235             return REGULAR_UNNAMED_THREAD_PREFIX + aThread.getId();
236         }
237     }
238     // +
239 
240     /**
241      * Return the threadId if running under JDK 21+ and the thread is a virtual thread, return null otherwise.
242      *
243      * @param aThread
244      * @return Return the threadId if the thread is a virtual thread, return null otherwise.
245      */
246     Long getVirtualThreadId(Thread aThread) {
247         if (EnvUtil.isJDK21OrHigher()) {
248             try {
249                 Method isVirtualMethod = Thread.class.getMethod("isVirtual");
250                 boolean isVirtual = (boolean) isVirtualMethod.invoke(aThread);
251                 if (isVirtual)
252                     return aThread.getId();
253             } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
254                 return null;
255             }
256         }
257         return null;
258     }
259 
260     /**
261      * @param threadName The threadName to set.
262      * @throws IllegalStateException If threadName has been already set.
263      */
264     public void setThreadName(String threadName) throws IllegalStateException {
265         if (this.threadName != null) {
266             throw new IllegalStateException("threadName has been already set");
267         }
268         this.threadName = threadName;
269     }
270 
271     /**
272      * Returns the throwable information contained within this event. May be
273      * <code>null</code> if there is no such information.
274      */
275     public IThrowableProxy getThrowableProxy() {
276         return throwableProxy;
277     }
278 
279     /**
280      * Set this event's throwable information.
281      */
282     public void setThrowableProxy(ThrowableProxy tp) {
283         if (throwableProxy != null) {
284             throw new IllegalStateException("ThrowableProxy has been already set.");
285         } else {
286             throwableProxy = tp;
287         }
288     }
289 
290     /**
291      * This method should be called prior to serializing an event. It should also be called when using asynchronous or
292      * deferred logging.
293      * <p/>
294      * <p/>
295      * Note that due to performance concerns, this method does NOT extract caller data. It is the responsibility of the
296      * caller to extract caller information.
297      */
298     public void prepareForDeferredProcessing() {
299         this.getFormattedMessage();
300         this.getThreadName();
301         // fixes http://jira.qos.ch/browse/LBCLASSIC-104
302         this.getMDCPropertyMap();
303     }
304 
305     public void setLoggerContext(LoggerContext lc) {
306         this.loggerContext = lc;
307     }
308 
309     public LoggerContextVO getLoggerContextVO() {
310         return loggerContextVO;
311     }
312 
313     public void setLoggerContextRemoteView(LoggerContextVO loggerContextVO) {
314         this.loggerContextVO = loggerContextVO;
315     }
316 
317     public String getMessage() {
318         return message;
319     }
320 
321     public void setMessage(String message) {
322         if (this.message != null) {
323             throw new IllegalStateException("The message for this event has been set already.");
324         }
325         this.message = message;
326     }
327 
328     /**
329      * Return the {@link Instant} corresponding to the creation of this event.
330      *
331      * @see {@link #getTimeStamp()}
332      * @since 1.3
333      */
334     public Instant getInstant() {
335         return instant;
336     }
337 
338     /**
339      * Set {@link Instant} corresponding to the creation of this event.
340      *
341      * The value of {@link #getTimeStamp()} will be overridden as well.
342      */
343     public void setInstant(Instant instant) {
344         initTmestampFields(instant);
345     }
346 
347     /**
348      * Return the number of elapsed milliseconds since epoch in UTC.
349      */
350     public long getTimeStamp() {
351         return timeStamp;
352     }
353 
354     /**
355      * Return the number of nanoseconds past the {@link #getTimeStamp() timestamp in seconds}.
356      *
357      * @since 1.3.0
358      */
359     @Override
360     public int getNanoseconds() {
361         return nanoseconds;
362     }
363 
364     /**
365      * Set the number of elapsed milliseconds since epoch in UTC.
366      *
367      * Setting the timestamp will override the value contained in {@link #getInstant}. Nanoseconds value will be
368      * computed form the provided millisecond value.
369      */
370     public void setTimeStamp(long timeStamp) {
371         Instant instant = Instant.ofEpochMilli(timeStamp);
372         setInstant(instant);
373     }
374 
375     @Override
376     public long getSequenceNumber() {
377         return sequenceNumber;
378     }
379 
380     public void setSequenceNumber(long sn) {
381         sequenceNumber = sn;
382     }
383 
384     public void setLevel(Level level) {
385         if (this.level != null) {
386             throw new IllegalStateException("The level has been already set for this event.");
387         }
388         this.level = level;
389     }
390 
391     /**
392      * Get the caller information for this logging event. If caller information is null at the time of its invocation,
393      * this method extracts location information. The collected information is cached for future use.
394      * <p/>
395      * <p>
396      * Note that after serialization it is impossible to correctly extract caller information.
397      * </p>
398      */
399     public StackTraceElement[] getCallerData() {
400         if (callerDataArray == null) {
401             callerDataArray = CallerData.extract(new Throwable(), fqnOfLoggerClass,
402                     loggerContext.getMaxCallerDataDepth(), loggerContext.getFrameworkPackages());
403         }
404         return callerDataArray;
405     }
406 
407     public boolean hasCallerData() {
408         return (callerDataArray != null);
409     }
410 
411     public void setCallerData(StackTraceElement[] callerDataArray) {
412         this.callerDataArray = callerDataArray;
413     }
414 
415     public List<Marker> getMarkerList() {
416         return markerList;
417     }
418 
419     public void addMarker(Marker marker) {
420         if (marker == null) {
421             return;
422         }
423         if (markerList == null) {
424             markerList = new ArrayList<>(4);
425         }
426         markerList.add(marker);
427     }
428 
429     public long getContextBirthTime() {
430         return loggerContextVO.getBirthTime();
431     }
432 
433     // lazy computation as suggested in LOGBACK-495
434     public String getFormattedMessage() {
435         if (formattedMessage != null) {
436             return formattedMessage;
437         }
438         if (argumentArray != null) {
439             formattedMessage = MessageFormatter.arrayFormat(message, argumentArray).getMessage();
440         } else {
441             formattedMessage = message;
442         }
443 
444         return formattedMessage;
445     }
446 
447     public Map<String, String> getMDCPropertyMap() {
448         // populate mdcPropertyMap if null
449         if (mdcPropertyMap == null) {
450             MDCAdapter mdcAdapter = loggerContext.getMDCAdapter();
451             if (mdcAdapter instanceof LogbackMDCAdapter)
452                 mdcPropertyMap = ((LogbackMDCAdapter) mdcAdapter).getPropertyMap();
453             else
454                 mdcPropertyMap = mdcAdapter.getCopyOfContextMap();
455         }
456         // mdcPropertyMap still null, use emptyMap()
457         if (mdcPropertyMap == null)
458             mdcPropertyMap = Collections.emptyMap();
459 
460         return mdcPropertyMap;
461     }
462 
463     /**
464      * Set the MDC map for this event.
465      *
466      * @param map
467      * @since 1.0.8
468      */
469     public void setMDCPropertyMap(Map<String, String> map) {
470         if (mdcPropertyMap != null) {
471             throw new IllegalStateException("The MDCPropertyMap has been already set for this event.");
472         }
473         this.mdcPropertyMap = map;
474 
475     }
476 
477     /**
478      * Synonym for [@link #getMDCPropertyMap}.
479      *
480      * @deprecated Replaced by [@link #getMDCPropertyMap}
481      */
482     public Map<String, String> getMdc() {
483         return getMDCPropertyMap();
484     }
485 
486     @Override
487     public String toString() {
488         StringBuilder sb = new StringBuilder();
489         sb.append('[');
490         sb.append(level).append("] ");
491         sb.append(getFormattedMessage());
492         return sb.toString();
493     }
494 
495     /**
496      * LoggerEventVO instances should be used for serialization. Use {@link LoggingEventVO#build(ILoggingEvent) build}
497      * method to create the LoggerEventVO instance.
498      *
499      * @since 1.0.11
500      */
501     private void writeObject(ObjectOutputStream out) throws IOException {
502         throw new UnsupportedOperationException(this.getClass() + " does not support serialization. "
503                 + "Use LoggerEventVO instance instead. See also LoggerEventVO.build method.");
504     }
505 
506 }