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ülcü 052 * @author Sé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}