Skip to content

Commit 56e6410

Browse files
author
Liudmila Molkova
authored
Structured logging with ClientLogger (Azure#25247)
* Structured logging implementation
1 parent 91990c9 commit 56e6410

File tree

8 files changed

+853
-25
lines changed

8 files changed

+853
-25
lines changed

eng/code-quality-reports/src/main/resources/checkstyle/checkstyle-suppressions.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,8 @@ the main ServiceBusClientBuilder. -->
242242
<!-- Logger class suppression -->
243243
<suppress checks="com.azure.tools.checkstyle.checks.GoodLoggingCheck" files="ClientLogger.java"/>
244244
<suppress checks="com.azure.tools.checkstyle.checks.GoodLoggingCheck" files="DefaultLogger.java"/>
245-
245+
<suppress checks="com.azure.tools.checkstyle.checks.GoodLoggingCheck" files="LoggingEventBuilder.java"/>
246+
246247
<!-- Requires static access to logger to report errors while loading i18n messages (from within a static initializer )-->
247248
<suppress checks="com.azure.tools.checkstyle.checks.GoodLoggingCheck" files="Messages.java"/>
248249

eng/code-quality-reports/src/main/resources/spotbugs/spotbugs-exclude.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2404,6 +2404,10 @@
24042404
<Class name="com.azure.core.util.logging.ClientLogger"/>
24052405
<Bug pattern="CRLF_INJECTION_LOGS"/>
24062406
</Match>
2407+
<Match>
2408+
<Class name="com.azure.core.util.logging.LoggingEventBuilder"/>
2409+
<Bug pattern="CRLF_INJECTION_LOGS"/>
2410+
</Match>
24072411

24082412
<!-- The predictable randomness doesn't expose any crucial detail in this case. -->
24092413
<Match>

sdk/core/azure-core/src/main/java/com/azure/core/implementation/logging/LoggingUtils.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@
55

66
import com.azure.core.util.CoreUtils;
77

8+
import java.util.Arrays;
9+
810
/**
911
* Contains utility methods for logging.
1012
*/
1113
public final class LoggingUtils {
1214
private static final char CR = '\r';
1315
private static final char LF = '\n';
14-
16+
1517
private LoggingUtils() {
1618
}
1719

@@ -48,4 +50,29 @@ public static String removeNewLinesFromLogMessage(String logMessage) {
4850
sb.append(logMessage, prevStart, logMessage.length());
4951
return sb.toString();
5052
}
53+
54+
/*
55+
* Determines if the arguments contains a throwable that would be logged, SLF4J logs a throwable if it is the last
56+
* element in the argument list.
57+
*
58+
* @param args The arguments passed to format the log message.
59+
* @return True if the last element is a throwable, false otherwise.
60+
*/
61+
public static boolean doesArgsHaveThrowable(Object... args) {
62+
if (args.length == 0) {
63+
return false;
64+
}
65+
66+
return args[args.length - 1] instanceof Throwable;
67+
}
68+
69+
/*
70+
* Removes the last element from the arguments as it is a throwable.
71+
*
72+
* @param args The arguments passed to format the log message.
73+
* @return The arguments with the last element removed.
74+
*/
75+
public static Object[] removeThrowable(Object... args) {
76+
return Arrays.copyOf(args, args.length - 1);
77+
}
5178
}

sdk/core/azure-core/src/main/java/com/azure/core/util/logging/ClientLogger.java

Lines changed: 84 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@
1010
import org.slf4j.LoggerFactory;
1111
import org.slf4j.helpers.NOPLogger;
1212

13-
import java.util.Arrays;
1413
import java.util.Objects;
1514
import java.util.function.Supplier;
1615

1716
import static com.azure.core.implementation.logging.LoggingUtils.removeNewLinesFromLogMessage;
1817

18+
import static com.azure.core.implementation.logging.LoggingUtils.doesArgsHaveThrowable;
19+
import static com.azure.core.implementation.logging.LoggingUtils.removeThrowable;
20+
1921
/**
2022
* This is a fluent logger helper class that wraps a pluggable {@link Logger}.
2123
*
@@ -345,11 +347,10 @@ public <T extends Throwable> T logThowableAsWarning(T throwable) {
345347
*/
346348
public <T extends Throwable> T logThrowableAsWarning(T throwable) {
347349
Objects.requireNonNull(throwable, "'throwable' cannot be null.");
348-
if (!logger.isWarnEnabled()) {
349-
return throwable;
350+
if (logger.isWarnEnabled()) {
351+
performLogging(LogLevel.WARNING, true, throwable.getMessage(), throwable);
350352
}
351353

352-
performLogging(LogLevel.WARNING, true, throwable.getMessage(), throwable);
353354
return throwable;
354355
}
355356

@@ -453,9 +454,9 @@ private void performLogging(LogLevel logLevel, boolean isExceptionLogging, Strin
453454
* @param args Arguments for the message, if an exception is being logged last argument is the throwable.
454455
*/
455456
private void performDeferredLogging(LogLevel logLevel, Supplier<String> messageSupplier, Throwable throwable) {
457+
String message = removeNewLinesFromLogMessage(messageSupplier.get());
456458
String throwableMessage = (throwable != null) ? throwable.getMessage() : "";
457-
String message = messageSupplier.get();
458-
message = removeNewLinesFromLogMessage(message);
459+
459460
switch (logLevel) {
460461
case VERBOSE:
461462
if (throwable != null) {
@@ -489,7 +490,6 @@ private void performDeferredLogging(LogLevel logLevel, Supplier<String> messageS
489490
* @param args The arguments passed to evaluate suppliers in args.
490491
* @return Return the argument with evaluated supplier
491492
*/
492-
493493
Object[] evaluateSupplierArgument(Object[] args) {
494494
if (isSupplierLogging(args)) {
495495
args[0] = ((Supplier<?>) args[0]).get();
@@ -530,28 +530,89 @@ public boolean canLogAtLevel(LogLevel logLevel) {
530530
}
531531
}
532532

533-
/*
534-
* Determines if the arguments contains a throwable that would be logged, SLF4J logs a throwable if it is the last
535-
* element in the argument list.
533+
/**
534+
* Creates {@link LoggingEventBuilder} for {@code error} log level that can be
535+
* used to enrich log with additional context.
536+
* <p><strong>Code samples</strong></p>
537+
*
538+
* <p>Logging with context at error level.</p>
536539
*
537-
* @param args The arguments passed to format the log message.
538-
* @return True if the last element is a throwable, false otherwise.
540+
* <!-- src_embed com.azure.core.util.logging.clientlogger.atverbose.addKeyValue#primitive -->
541+
* <pre>
542+
* logger.atVerbose&#40;&#41;
543+
* .addKeyValue&#40;&quot;key&quot;, 1L&#41;
544+
* .log&#40;&#40;&#41; -&gt; String.format&#40;&quot;Param 1: %s, Param 2: %s, Param 3: %s&quot;, &quot;param1&quot;, &quot;param2&quot;, &quot;param3&quot;&#41;&#41;;
545+
* </pre>
546+
* <!-- end com.azure.core.util.logging.clientlogger.atverbose.addKeyValue#primitive -->
547+
*
548+
* @return instance of {@link LoggingEventBuilder} or no-op if error logging is disabled.
539549
*/
540-
private boolean doesArgsHaveThrowable(Object... args) {
541-
if (args.length == 0) {
542-
return false;
543-
}
550+
public LoggingEventBuilder atError() {
551+
return LoggingEventBuilder.create(logger, LogLevel.ERROR, canLogAtLevel(LogLevel.ERROR));
552+
}
553+
554+
/**
555+
* Creates {@link LoggingEventBuilder} for {@code warning} log level that can be
556+
* used to enrich log with additional context.
557+
558+
* <p><strong>Code samples</strong></p>
559+
*
560+
* <p>Logging with context at warning level.</p>
561+
*
562+
* <!-- src_embed com.azure.core.util.logging.clientlogger.atWarning -->
563+
* <pre>
564+
* logger.atWarning&#40;&#41;
565+
* .addKeyValue&#40;&quot;key&quot;, &quot;value&quot;&#41;
566+
* .log&#40;&quot;A formattable message. Hello, &#123;&#125;&quot;, name, exception&#41;;
567+
* </pre>
568+
* <!-- end com.azure.core.util.logging.clientlogger.atWarning -->
569+
*
570+
* @return instance of {@link LoggingEventBuilder} or no-op if warn logging is disabled.
571+
*/
572+
public LoggingEventBuilder atWarning() {
573+
return LoggingEventBuilder.create(logger, LogLevel.WARNING, canLogAtLevel(LogLevel.WARNING));
574+
}
544575

545-
return args[args.length - 1] instanceof Throwable;
576+
/**
577+
* Creates {@link LoggingEventBuilder} for {@code info} log level that can be
578+
* used to enrich log with additional context.
579+
*
580+
* <p><strong>Code samples</strong></p>
581+
*
582+
* <p>Logging with context at info level.</p>
583+
*
584+
* <!-- src_embed com.azure.core.util.logging.clientlogger.atInfo -->
585+
* <pre>
586+
* logger.atInfo&#40;&#41;
587+
* .addKeyValue&#40;&quot;key&quot;, &quot;value&quot;&#41;
588+
* .log&#40;&quot;A formattable message. Hello, &#123;&#125;&quot;, name&#41;;
589+
* </pre>
590+
* <!-- end com.azure.core.util.logging.clientlogger.atInfo -->
591+
*
592+
* @return instance of {@link LoggingEventBuilder} or no-op if info logging is disabled.
593+
*/
594+
public LoggingEventBuilder atInfo() {
595+
return LoggingEventBuilder.create(logger, LogLevel.INFORMATIONAL, canLogAtLevel(LogLevel.INFORMATIONAL));
546596
}
547597

548-
/*
549-
* Removes the last element from the arguments as it is a throwable.
598+
/**
599+
* Creates {@link LoggingEventBuilder} for {@code verbose} log level that can be
600+
* used to enrich log with additional context.
601+
* <p><strong>Code samples</strong></p>
602+
*
603+
* <p>Logging with context at verbose level.</p>
604+
*
605+
* <!-- src_embed com.azure.core.util.logging.clientlogger.atverbose.addKeyValue#primitive -->
606+
* <pre>
607+
* logger.atVerbose&#40;&#41;
608+
* .addKeyValue&#40;&quot;key&quot;, 1L&#41;
609+
* .log&#40;&#40;&#41; -&gt; String.format&#40;&quot;Param 1: %s, Param 2: %s, Param 3: %s&quot;, &quot;param1&quot;, &quot;param2&quot;, &quot;param3&quot;&#41;&#41;;
610+
* </pre>
611+
* <!-- end com.azure.core.util.logging.clientlogger.atverbose.addKeyValue#primitive -->
550612
*
551-
* @param args The arguments passed to format the log message.
552-
* @return The arguments with the last element removed.
613+
* @return instance of {@link LoggingEventBuilder} or no-op if verbose logging is disabled.
553614
*/
554-
private Object[] removeThrowable(Object... args) {
555-
return Arrays.copyOf(args, args.length - 1);
615+
public LoggingEventBuilder atVerbose() {
616+
return LoggingEventBuilder.create(logger, LogLevel.VERBOSE, canLogAtLevel(LogLevel.VERBOSE));
556617
}
557618
}

0 commit comments

Comments
 (0)