001/**
002 * Logback: the reliable, generic, fast and flexible logging framework. Copyright (C) 1999-2015, QOS.ch. All rights
003 * reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under either the terms of the Eclipse Public License
006 * v1.0 as published by the Eclipse Foundation
007 *
008 * or (per the licensee's choosing)
009 *
010 * under the terms of the GNU Lesser General Public License version 2.1 as published by the Free Software Foundation.
011 */
012package ch.qos.logback.classic.spi;
013
014import java.io.IOException;
015import java.io.ObjectOutputStream;
016import java.lang.reflect.InvocationTargetException;
017import java.lang.reflect.Method;
018import java.time.Clock;
019import java.time.Instant;
020import java.util.ArrayList;
021import java.util.Collections;
022import java.util.List;
023import java.util.Map;
024
025import ch.qos.logback.core.CoreConstants;
026import ch.qos.logback.core.util.EnvUtil;
027import ch.qos.logback.core.util.StringUtil;
028import org.slf4j.Marker;
029import org.slf4j.event.KeyValuePair;
030import org.slf4j.helpers.MessageFormatter;
031import org.slf4j.spi.MDCAdapter;
032
033import ch.qos.logback.classic.Level;
034import ch.qos.logback.classic.Logger;
035import ch.qos.logback.classic.LoggerContext;
036import ch.qos.logback.classic.util.LogbackMDCAdapter;
037import ch.qos.logback.core.spi.SequenceNumberGenerator;
038
039/**
040 * The internal representation of logging events. When an affirmative decision is made to log then a
041 * <code>LoggingEvent</code> instance is created. This instance is passed around to the different logback-classic
042 * components.
043 * <p/>
044 * <p>
045 * Writers of logback-classic components such as appenders should be aware of that some of the LoggingEvent fields are
046 * initialized lazily. Therefore, an appender wishing to output data to be later correctly read by a receiver, must
047 * initialize "lazy" fields prior to writing them out. See the {@link #prepareForDeferredProcessing()} method for the
048 * exact list.
049 * </p>
050 *
051 * @author Ceki G&uuml;lc&uuml;
052 * @author S&eacute;bastien Pennec
053 */
054public class LoggingEvent implements ILoggingEvent {
055
056    public static final String VIRTUAL_THREAD_NAME_PREFIX = "virtual-";
057    public static final String REGULAR_UNNAMED_THREAD_PREFIX = "unnamed-";
058
059    /**
060     * Fully qualified name of the calling Logger class. This field does not survive serialization.
061     * <p/>
062     * <p/>
063     * Note that the getCallerInformation() method relies on this fact.
064     */
065    transient String fqnOfLoggerClass;
066
067    /**
068     * The name of thread in which this logging event was generated.
069     */
070    private String threadName;
071
072    private String loggerName;
073    private LoggerContext loggerContext;
074    private LoggerContextVO loggerContextVO;
075
076    /**
077     * Level of logging event.
078     * <p/>
079     * <p>
080     * This field should not be accessed directly. You should use the {@link #getLevel} method instead.
081     * </p>
082     */
083    private transient Level level;
084
085    private String message;
086
087    // we gain significant space at serialization time by marking
088    // formattedMessage as transient and constructing it lazily in
089    // getFormattedMessage()
090    transient String formattedMessage;
091
092    private transient Object[] argumentArray;
093
094    private ThrowableProxy throwableProxy;
095
096    private StackTraceElement[] callerDataArray;
097
098    private List<Marker> markerList;
099
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            if(throwableProxy == null) {
440                formattedMessage = MessageFormatter.arrayFormat(message, argumentArray).getMessage();
441            } else {
442                // very rare case where the argument array ends with two exceptions
443                // See https://github.com/qos-ch/logback/issues/876
444                formattedMessage = MessageFormatter.arrayFormat(message, argumentArray, null).getMessage();
445            }
446        } else {
447            formattedMessage = message;
448        }
449
450        return formattedMessage;
451    }
452
453    public Map<String, String> getMDCPropertyMap() {
454        // populate mdcPropertyMap if null
455        if (mdcPropertyMap == null) {
456            MDCAdapter mdcAdapter = loggerContext.getMDCAdapter();
457            if (mdcAdapter instanceof LogbackMDCAdapter)
458                mdcPropertyMap = ((LogbackMDCAdapter) mdcAdapter).getPropertyMap();
459            else
460                mdcPropertyMap = mdcAdapter.getCopyOfContextMap();
461        }
462        // mdcPropertyMap still null, use emptyMap()
463        if (mdcPropertyMap == null)
464            mdcPropertyMap = Collections.emptyMap();
465
466        return mdcPropertyMap;
467    }
468
469    /**
470     * Set the MDC map for this event.
471     *
472     * @param map
473     * @since 1.0.8
474     */
475    public void setMDCPropertyMap(Map<String, String> map) {
476        if (mdcPropertyMap != null) {
477            throw new IllegalStateException("The MDCPropertyMap has been already set for this event.");
478        }
479        this.mdcPropertyMap = map;
480
481    }
482
483    /**
484     * Synonym for [@link #getMDCPropertyMap}.
485     *
486     * @deprecated Replaced by [@link #getMDCPropertyMap}
487     */
488    public Map<String, String> getMdc() {
489        return getMDCPropertyMap();
490    }
491
492    @Override
493    public String toString() {
494        StringBuilder sb = new StringBuilder();
495        sb.append('[');
496        sb.append(level).append("] ");
497        sb.append(getFormattedMessage());
498        return sb.toString();
499    }
500
501    /**
502     * LoggerEventVO instances should be used for serialization. Use {@link LoggingEventVO#build(ILoggingEvent) build}
503     * method to create the LoggerEventVO instance.
504     *
505     * @since 1.0.11
506     */
507    private void writeObject(ObjectOutputStream out) throws IOException {
508        throw new UnsupportedOperationException(this.getClass() + " does not support serialization. "
509                + "Use LoggerEventVO instance instead. See also LoggerEventVO.build method.");
510    }
511
512}