001package io.jstach.rainbowgum; 002 003import java.lang.StackWalker.Option; 004import java.lang.StackWalker.StackFrame; 005import java.lang.System.Logger.Level; 006import java.time.Instant; 007import java.util.ArrayList; 008import java.util.List; 009import java.util.function.Supplier; 010 011import org.eclipse.jdt.annotation.Nullable; 012 013import io.jstach.rainbowgum.KeyValues.MutableKeyValues; 014import io.jstach.rainbowgum.LogEvent.Builder; 015import io.jstach.rainbowgum.LogEvent.Caller; 016import io.jstach.rainbowgum.LogRouter.Router; 017 018/** 019 * A LogEvent is a container for a single call to a logger. An event should not be created 020 * unless a route or logger is actually enabled. 021 * 022 * @author agentgt 023 * @apiNote LogEvent is currently sealed. The reason there are so many static creation 024 * methods is for optimization purposes because other than actual outputting creating 025 * events is generally the most expensive operation (mostly in terms of memory) a logging 026 * system does. 027 * @see LogMessageFormatter 028 */ 029public sealed interface LogEvent { 030 031 /** 032 * Creates a log event. 033 * @param level the logging level. 034 * @param loggerName the name of the logger which is usually a class name. 035 * @param formattedMessage the unformatted message. 036 * @param keyValues key values that come from MDC or an SLF4J Event Builder. 037 * @param throwable an exception if passed maybe <code>null</code>. 038 * @return event 039 * @see LevelResolver 040 * @apiNote the message is already assumed to be formatted as no arguments are passed. 041 */ 042 public static LogEvent of(System.Logger.Level level, String loggerName, String formattedMessage, 043 KeyValues keyValues, @Nullable Throwable throwable) { 044 Instant timeStamp = Instant.now(); 045 Thread currentThread = Thread.currentThread(); 046 String threadName = currentThread.getName(); 047 long threadId = currentThread.threadId(); 048 049 return new DefaultLogEvent(timeStamp, threadName, threadId, level, loggerName, formattedMessage, keyValues, 050 throwable); 051 052 } 053 054 /** 055 * Creates a log event. 056 * @param level the logging level. 057 * @param loggerName the name of the logger which is usually a class name. 058 * @param formattedMessage the unformatted message. 059 * @param throwable an exception if passed maybe <code>null</code>. 060 * @return event 061 * @see LevelResolver 062 * @apiNote the message is already assumed to be formatted as no arguments are passed. 063 */ 064 static LogEvent of(System.Logger.Level level, String loggerName, String formattedMessage, 065 @Nullable Throwable throwable) { 066 return of(level, loggerName, formattedMessage, KeyValues.of(), throwable); 067 } 068 069 /** 070 * Creates a log event. 071 * @param level the logging level. 072 * @param loggerName the name of the logger which is usually a class name. 073 * @param message the unformatted message. 074 * @param keyValues key values that come from MDC or an SLF4J Event Builder. 075 * @param messageFormatter formatter to use for rendering a message when 076 * #{@link LogEvent#formattedMessage(StringBuilder)} is called. 077 * @param arg1 argument that will be passed to messageFormatter. 078 * @return event 079 * @see LevelResolver 080 * @see LogMessageFormatter 081 */ 082 public static LogEvent of(System.Logger.Level level, String loggerName, String message, KeyValues keyValues, 083 LogMessageFormatter messageFormatter, @Nullable Object arg1) { 084 Instant timeStamp = Instant.now(); 085 Thread currentThread = Thread.currentThread(); 086 String threadName = currentThread.getName(); 087 long threadId = currentThread.threadId(); 088 if (arg1 instanceof Throwable t) { 089 return new DefaultLogEvent(timeStamp, threadName, threadId, level, loggerName, message, keyValues, t); 090 } 091 return new OneArgLogEvent(timeStamp, threadName, threadId, level, loggerName, message, keyValues, 092 messageFormatter, null, arg1); 093 } 094 095 /** 096 * Creates a log event. 097 * @param level the logging level. 098 * @param loggerName the name of the logger which is usually a class name. 099 * @param message the unformatted message. 100 * @param keyValues key values that come from MDC or an SLF4J Event Builder. 101 * @param messageFormatter formatter to use for rendering a message when 102 * #{@link LogEvent#formattedMessage(StringBuilder)} is called. 103 * @param arg1 argument that will be passed to messageFormatter. 104 * @param arg2 argument that will be passed to messageFormatter. 105 * @return event 106 * @see LevelResolver 107 * @see LogMessageFormatter 108 */ 109 public static LogEvent of(System.Logger.Level level, String loggerName, String message, KeyValues keyValues, 110 LogMessageFormatter messageFormatter, @Nullable Object arg1, @Nullable Object arg2) { 111 Instant timeStamp = Instant.now(); 112 Thread currentThread = Thread.currentThread(); 113 String threadName = currentThread.getName(); 114 long threadId = currentThread.threadId(); 115 if (arg2 instanceof Throwable t) { 116 return new OneArgLogEvent(timeStamp, threadName, threadId, level, loggerName, message, keyValues, 117 messageFormatter, t, arg1); 118 } 119 return new TwoArgLogEvent(timeStamp, threadName, threadId, level, loggerName, message, keyValues, 120 messageFormatter, null, arg1, arg2); 121 } 122 123 /** 124 * Creates a log event. 125 * @param level the logging level. 126 * @param loggerName the name of the logger which is usually a class name. 127 * @param message the unformatted message. 128 * @param keyValues key values that come from MDC or an SLF4J Event Builder. 129 * @param messageFormatter formatter to use for rendering a message when 130 * #{@link LogEvent#formattedMessage(StringBuilder)} is called. 131 * @param args an array of arguments that will be passed to messageFormatter. The 132 * contents maybe null elements but the array itself should not be null. 133 * @return event 134 * @see LevelResolver 135 * @see LogMessageFormatter 136 */ 137 public static LogEvent ofArgs(System.Logger.Level level, String loggerName, String message, KeyValues keyValues, 138 LogMessageFormatter messageFormatter, @Nullable Object[] args) { 139 Instant timeStamp = Instant.now(); 140 Thread currentThread = Thread.currentThread(); 141 String threadName = currentThread.getName(); 142 long threadId = currentThread.threadId(); 143 return ofAll(timeStamp, threadName, threadId, level, loggerName, message, keyValues, null, messageFormatter, 144 args); 145 } 146 147 /** 148 * Creates a log event with everything specified. 149 * @param timestamp time of event 150 * @param threadName or empty string 151 * @param threadId thread id or 0 if that cannot be resolved. 152 * @param level the logging level. 153 * @param loggerName the name of the logger which is usually a class name. 154 * @param message the unformatted message. 155 * @param keyValues key values that come from MDC or an SLF4J Event Builder. 156 * @param throwable or <code>null</code>. 157 * @param messageFormatter formatter to use for rendering a message when 158 * #{@link LogEvent#formattedMessage(StringBuilder)} is called. 159 * @param args an array of arguments that will be passed to messageFormatter. The 160 * contents maybe null elements but the array itself should not be null. 161 * @return event 162 * @see LevelResolver 163 * @see LogMessageFormatter 164 */ 165 public static LogEvent ofAll(Instant timestamp, String threadName, long threadId, System.Logger.Level level, 166 String loggerName, String message, KeyValues keyValues, @Nullable Throwable throwable, 167 LogMessageFormatter messageFormatter, @SuppressWarnings("exports") @Nullable List<@Nullable Object> args) { 168 169 if (args == null) { 170 return new DefaultLogEvent(timestamp, threadName, threadId, level, loggerName, message, keyValues, 171 throwable); 172 } 173 int length = args.size(); 174 if (throwable == null && length > 0 && args.get(length - 1) instanceof Throwable t) { 175 throwable = t; 176 length = length - 1; 177 } 178 return switch (length) { 179 case 0 -> 180 new DefaultLogEvent(timestamp, threadName, threadId, level, loggerName, message, keyValues, throwable); 181 case 1 -> new OneArgLogEvent(timestamp, threadName, threadId, level, loggerName, message, keyValues, 182 messageFormatter, throwable, args.get(0)); 183 case 2 -> new TwoArgLogEvent(timestamp, threadName, threadId, level, loggerName, message, keyValues, 184 messageFormatter, throwable, args.get(0), args.get(1)); 185 default -> new ArrayArgLogEvent(timestamp, threadName, threadId, level, loggerName, message, keyValues, 186 messageFormatter, throwable, args.toArray(), length); 187 }; 188 } 189 190 /** 191 * Creates a log event with everything specified. 192 * @param timestamp time of event 193 * @param threadName or empty string 194 * @param threadId thread id or 0 if that cannot be resolved. 195 * @param level the logging level. 196 * @param loggerName the name of the logger which is usually a class name. 197 * @param message the unformatted message. 198 * @param keyValues key values that come from MDC or an SLF4J Event Builder. 199 * @param throwable or <code>null</code>. 200 * @param messageFormatter formatter to use for rendering a message when 201 * #{@link LogEvent#formattedMessage(StringBuilder)} is called. 202 * @param args an array of arguments that will be passed to messageFormatter. The 203 * contents maybe null elements but the array itself should not be null. 204 * @return event 205 * @see LevelResolver 206 * @see LogMessageFormatter 207 */ 208 public static LogEvent ofAll(Instant timestamp, String threadName, long threadId, System.Logger.Level level, 209 String loggerName, String message, KeyValues keyValues, @Nullable Throwable throwable, 210 LogMessageFormatter messageFormatter, @SuppressWarnings("exports") @Nullable Object @Nullable [] args) { 211 212 if (args == null) { 213 return new DefaultLogEvent(timestamp, threadName, threadId, level, loggerName, message, keyValues, 214 throwable); 215 } 216 int length = args.length; 217 if (throwable == null && length > 0 && args[length - 1] instanceof Throwable t) { 218 throwable = t; 219 length = length - 1; 220 } 221 return switch (length) { 222 case 0 -> 223 new DefaultLogEvent(timestamp, threadName, threadId, level, loggerName, message, keyValues, throwable); 224 case 1 -> new OneArgLogEvent(timestamp, threadName, threadId, level, loggerName, message, keyValues, 225 messageFormatter, throwable, args[0]); 226 case 2 -> new TwoArgLogEvent(timestamp, threadName, threadId, level, loggerName, message, keyValues, 227 messageFormatter, throwable, args[0], args[1]); 228 default -> new ArrayArgLogEvent(timestamp, threadName, threadId, level, loggerName, message, keyValues, 229 messageFormatter, throwable, args, length); 230 }; 231 } 232 233 /** 234 * Creates a new event with the caller info attached. <em> This method always returns 235 * a new event does not check if the original has caller info. </em> 236 * @param event original event. 237 * @param caller caller info. 238 * @return new log event. 239 */ 240 public static LogEvent withCaller(LogEvent event, Caller caller) { 241 return new StackFrameLogEvent(event, caller); 242 } 243 244 /** 245 * Timestamp when the event was created. 246 * @return instant when the event was created. 247 */ 248 public Instant timestamp(); 249 250 /** 251 * Name of the thread. 252 * @return thread name. 253 * @apiNote this maybe empty and often is if virtual threads are used. 254 */ 255 public String threadName(); 256 257 /** 258 * Thread id. 259 * @return thread id. 260 */ 261 public long threadId(); 262 263 /** 264 * The logging level. {@linkplain java.lang.System.Logger.Level#ALL} and 265 * {@linkplain java.lang.System.Logger.Level#OFF} should not be returned as they have 266 * special meaning. 267 * @return level. 268 */ 269 public System.Logger.Level level(); 270 271 /** 272 * Name of logger. 273 * @return name of logger. 274 */ 275 public String loggerName(); 276 277 /** 278 * Unformatted message. 279 * @return unformatted message 280 * @see #formattedMessage(StringBuilder) 281 */ 282 public String message(); 283 284 /** 285 * Appends the formatted message. 286 * @param sb string builder to use. 287 * @see LogMessageFormatter 288 */ 289 public void formattedMessage(StringBuilder sb); 290 291 /** 292 * Throwable at the time of the event passed from the logger. 293 * @return if the event does not have a throwable <code>null</code> will be returned. 294 */ 295 public @Nullable Throwable throwableOrNull(); 296 297 /** 298 * Key values that usually come from MDC or an SLF4J Event Builder. 299 * @return key values. 300 */ 301 public KeyValues keyValues(); 302 303 /** 304 * Returns info about caller or <code>null</code> if not supported. 305 * @return caller info 306 */ 307 default @Nullable Caller callerOrNull() { 308 return null; 309 } 310 311 /** 312 * Freeze will return a LogEvent that is safe to use in a different thread. Usually 313 * this entails copying the data or checking if it is already immutable. Freeze should 314 * be called before passing an event to an {@link LogPublisher.AsyncLogPublisher}. 315 * @return thread safe log event. 316 */ 317 public LogEvent freeze(); 318 319 /** 320 * Freeze and replace with the given timestamp. 321 * @param timestamp instant to replace timestamp in this. 322 * @return a copy of this with the given timestamp. 323 */ 324 public LogEvent freeze(Instant timestamp); 325 326 /** 327 * A builder to create events by calling {@link Router#eventBuilder(String, Level)}. 328 * <strong>{@link #log()}</strong> should be called at the very end otherwise the 329 * event will not be logged. <strong> Because of potential future optimizations (see 330 * API note) It is best to assume this builder returns a different builder on every 331 * setter call even if it may not currently! </strong> 332 * 333 * @apiNote unless the builder gets marked for escape analysis the static 334 * {@link LogEvent} factory methods are more likely to be superior in performance. EA 335 * is tricky business and often requires the object be small enough and should not 336 * leave the method it is constructed in but the JDK is continuously getting better 337 * optimizing immutable objects. Thus it is best to assume this builder will return a 338 * different builder on every call. 339 */ 340 public sealed interface Builder { 341 342 /** 343 * Timestamp when the event was created. 344 * @param timestamp time the event happened. 345 * @return this. 346 */ 347 public Builder timestamp(Instant timestamp); 348 349 /** 350 * Add an argument to the event being built. 351 * @param argSupplier supplier will be called immediately if this event is to be 352 * logged. 353 * @return this. 354 */ 355 Builder arg(Supplier<?> argSupplier); 356 357 /** 358 * Add an argument to the event being built. 359 * @param arg maybe null. 360 * @return this. 361 */ 362 Builder arg(@Nullable Object arg); 363 364 /** 365 * Sets the message formatter which interpolates argument place holders. 366 * @param messageFormatter formatter if not set 367 * {@link LogMessageFormatter.StandardMessageFormatter#SLF4J} will be used. 368 * @return this. 369 */ 370 public Builder messageFormatter(LogMessageFormatter messageFormatter); 371 372 /** 373 * Name of the thread. 374 * @param threadName name of thread. 375 * @return this. 376 * @apiNote this maybe empty and often is if virtual threads are used. 377 */ 378 public Builder threadName(String threadName); 379 380 /** 381 * Thread id. 382 * @param threadId {@link Thread#getId()}. 383 * @return this. 384 */ 385 public Builder threadId(long threadId); 386 387 /** 388 * Unformatted message. 389 * @param message unformatted message. 390 * @return unformatted message 391 */ 392 public Builder message(String message); 393 394 /** 395 * Throwable at the time of the event passed from the logger. 396 * @param throwable exception at the time of the event. 397 * @return if the event does not have a throwable <code>null</code> will be 398 * returned. 399 */ 400 public Builder throwable(@Nullable Throwable throwable); 401 402 /** 403 * Key values that usually come from MDC or an SLF4J Event Builder. 404 * @param keyValues will use the passed in key values for the event. 405 * @return key values. 406 */ 407 public Builder keyValues(KeyValues keyValues); 408 409 /** 410 * Adds caller info. 411 * @param caller caller maybe <code>null</code>. 412 * @return this. 413 */ 414 public Builder caller(@Nullable Caller caller); 415 416 /** 417 * Will log the event with the current values. 418 * @return true if accepted. 419 */ 420 public boolean log(); 421 422 /** 423 * Will generate the event if the router can accept otherwise <code>null</code>. 424 * @return event or <code>null</code>. 425 * @apiNote The event is not logged but just created if possible if logging is 426 * desired use {@link #log()} or check if the return is not <code>null</code> and 427 * then log. 428 */ 429 public @Nullable LogEvent eventOrNull(); 430 431 } 432 433 /** 434 * Caller info usually derived from Stack walking. 435 */ 436 public sealed interface Caller { 437 438 /** 439 * Creates caller info from a stack frame. 440 * @param stackFrame stack frame must have 441 * {@link java.lang.StackWalker.Option#RETAIN_CLASS_REFERENCE}. 442 * @return caller info. 443 */ 444 public static Caller of(StackFrame stackFrame) { 445 return new StackFrameCallerInfo(stackFrame); 446 } 447 448 /** 449 * Returns caller from a certain depth or <code>null</code> 450 * @param depth how deep in the stack to pull stack frame. 451 * @return caller or <code>null</code>. 452 */ 453 public static @Nullable Caller ofDepthOrNull(int depth) { 454 return StackFrameCallerInfo.stackWalker.<@Nullable Caller>walk( 455 s -> s.skip(depth + 1).limit(1).map(f -> Caller.of(f)).findFirst().orElse(null)); 456 } 457 458 /** 459 * See {@link StackFrame#getClassName()}. 460 * @return class name. 461 */ 462 public String className(); 463 464 /** 465 * See {@link StackFrame#getFileName()}. 466 * @return file name. 467 */ 468 public @Nullable String fileNameOrNull(); 469 470 /** 471 * See {@link StackFrame#getLineNumber()}. 472 * @return line number. 473 */ 474 public int lineNumber(); 475 476 /** 477 * See {@link StackFrame#getMethodName()}. 478 * @return method name. 479 */ 480 public String methodName(); 481 482 /** 483 * Make the caller info immutable. 484 * @return immutable caller info and if this is already immutable return this. 485 */ 486 public Caller freeze(); 487 488 /** 489 * Convenience toString for caller. 490 * @param caller if caller is <code>null</code> the string "null" will be 491 * returned. 492 * @return string representation. 493 */ 494 public static String toString(@Nullable Caller caller) { 495 /* 496 * TODO maybe move this to formatters? 497 */ 498 if (caller == null) 499 return "null"; 500 return caller.fileNameOrNull() + ":" + caller.lineNumber() + "/" + caller.className() + "." 501 + caller.methodName(); 502 } 503 504 } 505 506} 507 508record FrozenCallerInfo(String className, @Nullable String fileNameOrNull, int lineNumber, 509 String methodName) implements LogEvent.Caller { 510 @Override 511 public Caller freeze() { 512 return this; 513 } 514} 515 516record StackFrameCallerInfo(StackFrame stackFrame) implements LogEvent.Caller { 517 518 static final StackWalker stackWalker = StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE); 519 520 @Override 521 public String className() { 522 return stackFrame.getClassName(); 523 } 524 525 @Override 526 public @Nullable String fileNameOrNull() { 527 /* 528 * TODO checker did not know file name is null. 529 */ 530 return stackFrame.getFileName(); 531 } 532 533 @Override 534 public int lineNumber() { 535 return stackFrame.getLineNumber(); 536 } 537 538 @Override 539 public String methodName() { 540 return stackFrame.getMethodName(); 541 } 542 543 @Override 544 public Caller freeze() { 545 return new FrozenCallerInfo(className(), fileNameOrNull(), lineNumber(), methodName()); 546 } 547 548} 549 550final class LogEventBuilder implements LogEvent.Builder { 551 552 private final LogEventLogger logger; 553 554 private final Level level; 555 556 private final String loggerName; 557 558 private Instant timestamp = Instant.now(); 559 560 private String threadName = Thread.currentThread().getName(); 561 562 private long threadId = Thread.currentThread().threadId(); 563 564 private String message = ""; 565 566 private @Nullable Throwable throwable; 567 568 private KeyValues keyValues = KeyValues.of(); 569 570 private @Nullable List<@Nullable Object> args = null; 571 572 private LogMessageFormatter messageFormatter = LogMessageFormatter.StandardMessageFormatter.SLF4J; 573 574 private @Nullable Caller caller = null; 575 576 LogEventBuilder(LogEventLogger logger, Level level, String loggerName) { 577 super(); 578 this.logger = logger; 579 this.level = level; 580 this.loggerName = loggerName; 581 } 582 583 @Override 584 public Builder timestamp(Instant timestamp) { 585 this.timestamp = timestamp; 586 return this; 587 } 588 589 @Override 590 public Builder threadName(String threadName) { 591 this.threadName = threadName; 592 return this; 593 } 594 595 @Override 596 public Builder threadId(long threadId) { 597 this.threadId = threadId; 598 return this; 599 } 600 601 @Override 602 public Builder message(String message) { 603 this.message = message; 604 return this; 605 } 606 607 @Override 608 public Builder messageFormatter(LogMessageFormatter messageFormatter) { 609 this.messageFormatter = messageFormatter; 610 return this; 611 } 612 613 @Override 614 public Builder throwable(@Nullable Throwable throwable) { 615 this.throwable = throwable; 616 return this; 617 } 618 619 @Override 620 public Builder keyValues(KeyValues keyValues) { 621 this.keyValues = keyValues; 622 return this; 623 } 624 625 @Override 626 public Builder arg(@Nullable Object arg) { 627 var list = this.args; 628 if (list == null) { 629 list = this.args = new ArrayList<>(); 630 } 631 list.add(arg); 632 return this; 633 } 634 635 @Override 636 public Builder arg(Supplier<?> argSupplier) { 637 return arg(argSupplier.get()); 638 } 639 640 @Override 641 public Builder caller(@Nullable Caller caller) { 642 this.caller = caller; 643 return this; 644 } 645 646 @Override 647 public boolean log() { 648 var event = eventOrNull(); 649 logger.log(event); 650 return true; 651 } 652 653 @Override 654 public LogEvent eventOrNull() { 655 var event = LogEvent.ofAll(timestamp, threadName, threadId, level, loggerName, message, keyValues, throwable, 656 messageFormatter, this.args); 657 var c = caller; 658 if (c != null) { 659 return LogEvent.withCaller(event, c); 660 } 661 return event; 662 } 663 664} 665 666enum NoOpLogEventBuilder implements LogEvent.Builder { 667 668 NOOP; 669 670 @Override 671 public Builder timestamp(Instant timestamp) { 672 return this; 673 } 674 675 @Override 676 public Builder threadName(String threadName) { 677 return this; 678 } 679 680 @Override 681 public Builder threadId(long threadId) { 682 return this; 683 } 684 685 @Override 686 public Builder message(String message) { 687 return this; 688 } 689 690 @Override 691 public Builder throwable(@Nullable Throwable throwable) { 692 return this; 693 } 694 695 @Override 696 public Builder keyValues(KeyValues keyValues) { 697 return this; 698 } 699 700 @Override 701 public Builder messageFormatter(LogMessageFormatter messageFormatter) { 702 return this; 703 } 704 705 @Override 706 public boolean log() { 707 return false; 708 } 709 710 @Override 711 public @Nullable LogEvent eventOrNull() { 712 return null; 713 } 714 715 @Override 716 public Builder arg(Supplier<?> argSupplier) { 717 return this; 718 } 719 720 @Override 721 public Builder arg(@Nullable Object arg) { 722 return this; 723 } 724 725 @Override 726 public Builder caller(@Nullable Caller caller) { 727 return this; 728 } 729 730} 731 732record OneArgLogEvent(Instant timestamp, String threadName, long threadId, System.Logger.Level level, String loggerName, 733 String message, KeyValues keyValues, LogMessageFormatter messageFormatter, @Nullable Throwable throwableOrNull, 734 @Nullable Object arg1) implements LogEvent { 735 736 @Override 737 public void formattedMessage(StringBuilder sb) { 738 messageFormatter.format(sb, message, arg1); 739 } 740 741 @Override 742 public LogEvent freeze() { 743 return freeze(timestamp); 744 } 745 746 @Override 747 public LogEvent freeze(Instant timestamp) { 748 StringBuilder sb = new StringBuilder(message.length()); 749 formattedMessage(sb); 750 return new DefaultLogEvent(timestamp, threadName, threadId, level, loggerName, sb.toString(), 751 keyValues.freeze(), throwableOrNull); 752 } 753 754} 755 756record TwoArgLogEvent(Instant timestamp, String threadName, long threadId, System.Logger.Level level, String loggerName, 757 String message, KeyValues keyValues, LogMessageFormatter messageFormatter, @Nullable Throwable throwableOrNull, 758 @Nullable Object arg1, @Nullable Object arg2) implements LogEvent { 759 760 @Override 761 public void formattedMessage(StringBuilder sb) { 762 messageFormatter.format(sb, message, arg1, arg2); 763 } 764 765 @Override 766 public LogEvent freeze() { 767 return freeze(timestamp); 768 } 769 770 @Override 771 public LogEvent freeze(Instant timestamp) { 772 StringBuilder sb = new StringBuilder(message.length()); 773 formattedMessage(sb); 774 return new DefaultLogEvent(timestamp, threadName, threadId, level, loggerName, sb.toString(), 775 keyValues.freeze(), throwableOrNull); 776 } 777} 778 779@SuppressWarnings("ArrayRecordComponent") 780record ArrayArgLogEvent(Instant timestamp, String threadName, long threadId, System.Logger.Level level, 781 String loggerName, String message, KeyValues keyValues, LogMessageFormatter messageFormatter, 782 @Nullable Throwable throwableOrNull, @Nullable Object[] args, int length) implements LogEvent { 783 784 @Override 785 public void formattedMessage(StringBuilder sb) { 786 messageFormatter.formatArray(sb, message, args, length); 787 } 788 789 public int argCount() { 790 return args.length; 791 } 792 793 @Override 794 public LogEvent freeze() { 795 return freeze(timestamp); 796 } 797 798 @Override 799 public LogEvent freeze(Instant timestamp) { 800 StringBuilder sb = new StringBuilder(message.length()); 801 formattedMessage(sb); 802 return new DefaultLogEvent(timestamp, threadName, threadId, level, loggerName, sb.toString(), 803 keyValues.freeze(), throwableOrNull); 804 } 805 806} 807 808record DefaultLogEvent(Instant timestamp, String threadName, long threadId, System.Logger.Level level, 809 String loggerName, String formattedMessage, KeyValues keyValues, 810 @Nullable Throwable throwableOrNull) implements LogEvent { 811 812 @Override 813 public void formattedMessage(StringBuilder sb) { 814 sb.append(this.formattedMessage); 815 } 816 817 @Override 818 public String message() { 819 return this.formattedMessage; 820 } 821 822 @Override 823 public LogEvent freeze() { 824 if (keyValues instanceof MutableKeyValues mkvs) { 825 return new DefaultLogEvent(timestamp, threadName, threadId, level, loggerName, formattedMessage, 826 mkvs.freeze(), throwableOrNull); 827 } 828 return this; 829 } 830 831 @Override 832 public LogEvent freeze(Instant timestamp) { 833 if (keyValues instanceof MutableKeyValues mkvs) { 834 return new DefaultLogEvent(timestamp, threadName, threadId, level, loggerName, formattedMessage, 835 mkvs.freeze(), throwableOrNull); 836 } 837 return new DefaultLogEvent(timestamp, threadName, threadId, level, loggerName, formattedMessage, keyValues, 838 throwableOrNull); 839 } 840 841} 842 843record StackFrameLogEvent(LogEvent event, Caller callerOrNull) implements LogEvent { 844 845 @Override 846 public Instant timestamp() { 847 return event.timestamp(); 848 } 849 850 @Override 851 public String threadName() { 852 return event.threadName(); 853 } 854 855 @Override 856 public long threadId() { 857 return event.threadId(); 858 } 859 860 @Override 861 public Level level() { 862 return event.level(); 863 } 864 865 @Override 866 public String loggerName() { 867 return event.loggerName(); 868 } 869 870 @Override 871 public String message() { 872 return event.message(); 873 } 874 875 @Override 876 public void formattedMessage(StringBuilder sb) { 877 event.formattedMessage(sb); 878 879 } 880 881 @Override 882 public @Nullable Throwable throwableOrNull() { 883 return event.throwableOrNull(); 884 } 885 886 @Override 887 public KeyValues keyValues() { 888 return event.keyValues(); 889 } 890 891 @Override 892 public LogEvent freeze() { 893 var e = event.freeze(); 894 var info = callerOrNull.freeze(); 895 if (e == event && info == callerOrNull) { 896 return this; 897 } 898 899 return new StackFrameLogEvent(e, info); 900 } 901 902 @Override 903 public LogEvent freeze(Instant timestamp) { 904 var e = event.freeze(timestamp); 905 var info = callerOrNull.freeze(); 906 if (e == event && info == callerOrNull) { 907 return this; 908 } 909 return new StackFrameLogEvent(e, info); 910 } 911 912}