1212
1313import java .util .Arrays ;
1414import java .util .Objects ;
15+ import java .util .function .Function ;
16+ import java .util .function .Supplier ;
1517import java .util .regex .Pattern ;
1618
1719/**
3739 */
3840public class ClientLogger {
3941 private static final Pattern CRLF_PATTERN = Pattern .compile ("[\r \n ]" );
42+ private static final String DEFAULT_MESSAGE_FORMAT = "{}" ;
4043 private final Logger logger ;
4144
4245 /**
@@ -59,13 +62,50 @@ public ClientLogger(String className) {
5962 logger = initLogger instanceof NOPLogger ? new DefaultLogger (className ) : initLogger ;
6063 }
6164
65+ /**
66+ * Logs a formattable message that uses {@code {}} as the placeholder at the given {@code logLevel}.
67+ *
68+ * <p><strong>Code samples</strong></p>
69+ *
70+ * <p>Logging with a specific log level</p>
71+ * {@codesnippet com.azure.core.util.logging.clientlogger.log}
72+ *
73+ * @param logLevel Logging level for the log message.
74+ * @param message The formattable message to log.
75+ */
76+ public void log (LogLevel logLevel , Supplier <String > message ) {
77+ if (message != null ) {
78+ performDeferredLogging (logLevel , false , DEFAULT_MESSAGE_FORMAT , message );
79+ }
80+ }
81+
82+ /**
83+ * Logs a formattable message that uses {@code {}} as the placeholder at {@code verbose} log level.
84+ *
85+ * <p><strong>Code samples</strong></p>
86+ *
87+ * <p>Logging with a specific log level and exception</p>
88+ * <p>
89+ * {@codesnippet com.azure.core.util.logging.clientlogger.log#throwable}
90+ *
91+ * @param logLevel Logging level for the log message.
92+ * @param message The formattable message to log.
93+ * @param throwable Throwable for the message.
94+ * {@link Throwable}.
95+ */
96+ public void log (LogLevel logLevel , Supplier <String > message , Throwable throwable ) {
97+ if (message != null ) {
98+ performDeferredLogging (logLevel , true , DEFAULT_MESSAGE_FORMAT , message , throwable );
99+ }
100+ }
101+
62102 /**
63103 * Logs a message at {@code verbose} log level.
64104 *
65105 * <p><strong>Code samples</strong></p>
66106 *
67107 * <p>Logging a message at verbose log level.</p>
68- *
108+ * <p>
69109 * {@codesnippet com.azure.core.util.logging.clientlogger.verbose}
70110 *
71111 * @param message The message to log.
@@ -82,12 +122,12 @@ public void verbose(String message) {
82122 * <p><strong>Code samples</strong></p>
83123 *
84124 * <p>Logging a message at verbose log level.</p>
85- *
125+ * <p>
86126 * {@codesnippet com.azure.core.util.logging.clientlogger.verbose#string-object}
87127 *
88128 * @param format The formattable message to log.
89129 * @param args Arguments for the message. If an exception is being logged, the last argument should be the
90- * {@link Throwable}.
130+ * {@link Throwable}.
91131 */
92132 public void verbose (String format , Object ... args ) {
93133 if (logger .isDebugEnabled ()) {
@@ -101,7 +141,7 @@ public void verbose(String format, Object... args) {
101141 * <p><strong>Code samples</strong></p>
102142 *
103143 * <p>Logging a message at verbose log level.</p>
104- *
144+ * <p>
105145 * {@codesnippet com.azure.core.util.logging.clientlogger.info}
106146 *
107147 * @param message The message to log.
@@ -118,12 +158,12 @@ public void info(String message) {
118158 * <p><strong>Code samples</strong></p>
119159 *
120160 * <p>Logging a message at informational log level.</p>
121- *
161+ * <p>
122162 * {@codesnippet com.azure.core.util.logging.clientlogger.info#string-object}
123163 *
124164 * @param format The formattable message to log
125165 * @param args Arguments for the message. If an exception is being logged, the last argument should be the
126- * {@link Throwable}.
166+ * {@link Throwable}.
127167 */
128168 public void info (String format , Object ... args ) {
129169 if (logger .isInfoEnabled ()) {
@@ -136,8 +176,8 @@ public void info(String format, Object... args) {
136176 *
137177 * <p><strong>Code samples</strong></p>
138178 *
139- * <p>Logging a message at verbose log level.</p>
140- *
179+ * <p>Logging a message at warning log level.</p>
180+ * <p>
141181 * {@codesnippet com.azure.core.util.logging.clientlogger.warning}
142182 *
143183 * @param message The message to log.
@@ -154,12 +194,12 @@ public void warning(String message) {
154194 * <p><strong>Code samples</strong></p>
155195 *
156196 * <p>Logging a message at warning log level.</p>
157- *
197+ * <p>
158198 * {@codesnippet com.azure.core.util.logging.clientlogger.warning#string-object}
159199 *
160200 * @param format The formattable message to log.
161201 * @param args Arguments for the message. If an exception is being logged, the last argument should be the
162- * {@link Throwable}.
202+ * {@link Throwable}.
163203 */
164204 public void warning (String format , Object ... args ) {
165205 if (logger .isWarnEnabled ()) {
@@ -172,8 +212,8 @@ public void warning(String format, Object... args) {
172212 *
173213 * <p><strong>Code samples</strong></p>
174214 *
175- * <p>Logging a message at verbose log level.</p>
176- *
215+ * <p>Logging a message at error log level.</p>
216+ * <p>
177217 * {@codesnippet com.azure.core.util.logging.clientlogger.error}
178218 *
179219 * @param message The message to log.
@@ -190,12 +230,12 @@ public void error(String message) {
190230 * <p><strong>Code samples</strong></p>
191231 *
192232 * <p>Logging an error with stack trace.</p>
193- *
233+ * <p>
194234 * {@codesnippet com.azure.core.util.logging.clientlogger.error#string-object}
195235 *
196236 * @param format The formattable message to log.
197237 * @param args Arguments for the message. If an exception is being logged, the last argument should be the
198- * {@link Throwable}.
238+ * {@link Throwable}.
199239 */
200240 public void error (String format , Object ... args ) {
201241 if (logger .isErrorEnabled ()) {
@@ -330,30 +370,98 @@ private void performLogging(LogLevel logLevel, boolean isExceptionLogging, Strin
330370 }
331371
332372 sanitizeLogMessageInput (format );
373+ executeLogging (logLevel , format , throwableMessage , s -> s , args );
374+ }
375+
376+ /*
377+ * Performs the logging.
378+ *
379+ * @param logLevel sets the logging level
380+ * @isExceptionLogging sets exception logging
381+ * @param args Arguments for the message, if an exception is being logged last argument is the throwable.
382+ */
383+ private void performDeferredLogging (LogLevel logLevel , boolean isExceptionLogging , String format , Object ... args ) {
384+ // If the logging level is less granular than verbose remove the potential throwable from the args.
385+ String throwableMessage = "" ;
386+ if (doesArgsHaveThrowable (args )) {
387+ // If we are logging an exception the format string is already the exception message, don't append it.
388+ if (!isExceptionLogging ) {
389+ Object throwable = args [args .length - 1 ];
390+
391+ // This is true from before but is needed to appease SpotBugs.
392+ if (throwable instanceof Throwable ) {
393+ throwableMessage = ((Throwable ) throwable ).getMessage ();
394+ }
395+ }
396+
397+ /*
398+ * Environment is logging at a level higher than verbose, strip out the throwable as it would log its
399+ * stack trace which is only expected when logging at a verbose level.
400+ */
401+ if (!logger .isDebugEnabled ()) {
402+ args = removeThrowable (args );
403+ }
404+ }
405+
406+ sanitizeLogMessageInput (format );
407+ executeLogging (logLevel , format , throwableMessage , this ::evaluateSupplierArgument , args );
408+ }
409+
410+ /*
411+ * Performs the logging.
412+ *
413+ * @param logLevel sets the logging level
414+ * @format sets exception logging
415+ * @throwableMessage the evaluated exception message which get value based on log level.
416+ * @loggingFunction sets how the logging message should be evaluated
417+ * @args Arguments for the message, if an exception is being logged last argument is the throwable.
418+ */
419+ private void executeLogging (LogLevel logLevel , String format , String throwableMessage ,
420+ Function <Object [], Object []> loggingEvaluation , Object [] args ) {
333421 switch (logLevel ) {
334422 case VERBOSE :
335- logger .debug (format , args );
423+ logger .debug (format , loggingEvaluation . apply ( args ) );
336424 break ;
337425 case INFORMATIONAL :
338- logger .info (format , args );
426+ logger .info (format , loggingEvaluation . apply ( args ) );
339427 break ;
340428 case WARNING :
341429 if (!CoreUtils .isNullOrEmpty (throwableMessage )) {
342430 format += System .lineSeparator () + throwableMessage ;
343431 }
344- logger .warn (format , args );
432+ logger .warn (format , loggingEvaluation . apply ( args ) );
345433 break ;
346434 case ERROR :
347435 if (!CoreUtils .isNullOrEmpty (throwableMessage )) {
348436 format += System .lineSeparator () + throwableMessage ;
349437 }
350- logger .error (format , args );
438+ logger .error (format , loggingEvaluation . apply ( args ) );
351439 break ;
352440 default :
353441 // Don't do anything, this state shouldn't be possible.
354442 break ;
355443 }
444+ }
445+
446+ /**
447+ * @param args The arguments passed to evaluate suppliers in args.
448+ * @return Return the argument with evaluated supplier
449+ */
450+
451+ Object [] evaluateSupplierArgument (Object [] args ) {
452+ if (isSupplierLogging (args )) {
453+ args [0 ] = ((Supplier <?>) args [0 ]).get ();
454+ }
455+ return args ;
456+ }
356457
458+ /**
459+ * @param args The arguments passed to determine supplier evaluation
460+ * @return Determines if it is supplier logging
461+ */
462+ boolean isSupplierLogging (Object [] args ) {
463+ return (args .length == 1 && args [0 ] instanceof Supplier )
464+ || (args .length == 2 && args [0 ] instanceof Supplier && (args [1 ] instanceof Throwable || args [1 ] == null ));
357465 }
358466
359467 /**
0 commit comments