001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.commons.logging;
018
019import static java.util.logging.Level.*;
020import static org.apache.juneau.commons.utils.Shorts.*;
021
022import java.lang.ref.*;
023import java.util.*;
024import java.util.concurrent.*;
025import java.util.function.*;
026import java.util.logging.*;
027
028/**
029 * Extended logger that provides convenience methods for logging formatted messages.
030 *
031 * <p>
032 * This class extends {@link java.util.logging.Logger} and uses delegation internally to wrap
033 * an underlying {@link java.util.logging.Logger} instance. This design provides:
034 * </p>
035 * <ul>
036 *    <li>Type compatibility - extends RichLogger for use anywhere a RichLogger is expected
037 *    <li>Flexibility - can wrap existing logger instances
038 *    <li>Convenience methods for formatted logging using {@link org.apache.juneau.commons.utils.Shorts#fs(String, Object...)}
039 * </ul>
040 *
041 * <h5 class='section'>Features:</h5>
042 * <ul>
043 *    <li>All standard {@link java.util.logging.Logger} functionality via delegation
044 *    <li>Convenience methods for formatted logging at all log levels
045 *    <li>Lazy evaluation of formatted messages using {@link org.apache.juneau.commons.utils.Shorts#fs(String, Object...)}
046 *    <li>Printf-style formatting (rendered via {@link org.apache.juneau.commons.utils.StringUtils#format(String, Object...)})
047 * </ul>
048 *
049 * <h5 class='section'>Format Support:</h5>
050 * <p>
051 * The formatting methods use printf-style ({@link java.util.Formatter}) placeholders. MessageFormat-style
052 * (<js>"{0}"</js>) patterns are <b>not</b> expanded here — use {@link org.apache.juneau.commons.utils.Shorts#mf(String, Object...)} /
053 * {@link org.apache.juneau.commons.utils.StringUtils#mformat(String, Object...)} for those:
054 * </p>
055 * <ul>
056 *    <li><b>Printf-style:</b> <js>"%s"</js>, <js>"%d"</js>, <js>"%1$s"</js>, etc.
057 * </ul>
058 *
059 * <p>
060 * Juneau {@link RichLogger} patterns are printf-style. Because {@link LogRecord#getMessage()} returns the
061 * already-substituted message, the {@link java.text.MessageFormat} parameter contract of the underlying
062 * {@link java.util.logging.LogRecord} only surfaces if a record is serialized and consumed as a plain
063 * {@link java.util.logging.LogRecord} by a non-Juneau handler.
064 * </p>
065 *
066 * <h5 class='section'>Example:</h5>
067 * <p class='bjava'>
068 *    RichLogger <jv>logger</jv> = RichLogger.getLogger(MyClass.<jk>class</jk>.getName());
069 *
070 *    <jc>// Formatted logging at different levels (printf-style placeholders)</jc>
071 *    <jv>logger</jv>.severe(<js>"Error processing user %s: %s"</js>, userId, error);
072 *    <jv>logger</jv>.warning(<js>"Failed to connect to %s after %d attempts"</js>, hostname, attempts);
073 *    <jv>logger</jv>.info(<js>"Processing %s items"</js>, count);
074 *    <jv>logger</jv>.fine(<js>"Debug value: %s"</js>, debugValue);
075 * </p>
076 *
077 * <h5 class='section'>See Also:</h5><ul>
078 *    <li class='jc'>{@link org.apache.juneau.commons.utils.Shorts#fs(String, Object...)}
079 *    <li class='jc'>{@link org.apache.juneau.commons.utils.StringUtils#format(String, Object...)}
080 *    <li class='jc'>{@link java.util.logging.Logger}
081 * </ul>
082 */
083@SuppressWarnings({
084   "java:S100", // Method names match java.util.logging.Logger for API compatibility
085   "java:S1192", // String literals intentionally duplicated for clarity
086   "java:S2176", // Class name intentionally matches java.util.logging.Logger; extends it to provide Juneau-specific logging enhancements
087   "resource" // Caller takes ownership of the returned LogRecordCapture
088})
089public class RichLogger extends java.util.logging.Logger {
090
091   /**
092    * Permanent registry of logger instances by name.
093    *
094    * <p>
095    * Uses an unbounded {@link ConcurrentHashMap} so every name maps to exactly one
096    * {@link RichLogger} instance for the JVM lifetime.  An evictable cache (e.g. one with a
097    * {@code maxSize} that clears the whole map) would cause a class-loaded
098    * {@code static final RichLogger} field to refer to an instance that is no longer in the
099    * registry; a subsequent call to {@link #getLogger(String)} would then return a new,
100    * different instance, breaking {@link LogRecordCapture} and any other code that
101    * assumes one name == one identity.
102    */
103   private static final ConcurrentHashMap<String,LoggerRef> loggers = new ConcurrentHashMap<>();
104   private static final ReferenceQueue<RichLogger> loggerQueue = new ReferenceQueue<>();
105
106   private static final class LoggerRef extends WeakReference<RichLogger> {
107      final String name;
108
109      LoggerRef(String name, RichLogger referent, ReferenceQueue<RichLogger> q) {
110         super(referent, q);
111         this.name = name;
112      }
113   }
114
115   /**
116    * The underlying logger instance that we delegate to.
117    */
118   private final java.util.logging.Logger delegate;
119
120   private final MessageGenerator generator;
121   private final RichLogger canonical;
122
123   /**
124    * List of log record listeners.
125    */
126   private final List<LogRecordListener> listeners;
127   private volatile boolean useParentListeners = true;
128
129   /**
130    * Protected constructor - wraps an existing logger instance.
131    *
132    * @param delegate The underlying logger to delegate to.  Must not be <jk>null</jk>.
133    */
134   protected RichLogger(java.util.logging.Logger delegate) {
135      this(delegate, MessageGenerator.PRINTF, null);
136   }
137
138   private RichLogger(java.util.logging.Logger delegate, MessageGenerator generator, RichLogger canonical) {
139      super(delegate.getName(), null);
140      this.delegate = delegate;
141      this.generator = generator == null ? MessageGenerator.PRINTF : generator;
142      this.canonical = canonical == null ? this : canonical;
143      this.listeners = canonical == null ? Collections.synchronizedList(new ArrayList<>()) : null;
144   }
145
146   /**
147    * Creates a logger for the specified name.
148    *
149    * <p>
150    * This method returns the same RichLogger instance for a given name, ensuring that
151    * listeners attached to a logger persist across multiple calls to {@link #getLogger(String)}.
152    * RichLogger instances are cached and automatically created using the underlying
153    * {@link java.util.logging.Logger#getLogger(String)}.
154    *
155    * @param name The logger name.  Must not be <jk>null</jk>.
156    * @return A logger instance (cached and reused for the same name).
157    */
158   @SuppressWarnings({
159      "java:S9149" // Intentional factory override of the hidden static Logger.getLogger(String); public API callers depend on this name/signature.
160   })
161   public static RichLogger getLogger(String name) {
162      drainCollectedLoggers();
163      while (true) {
164         var currentRef = loggers.get(name);
165         var current = currentRef == null ? null : currentRef.get();
166         if (current != null)
167            return current;
168         var created = new RichLogger(java.util.logging.Logger.getLogger(name));
169         var createdRef = new LoggerRef(name, created, loggerQueue);
170         if (currentRef == null) {
171            if (loggers.putIfAbsent(name, createdRef) == null)
172               return created;
173         } else if (loggers.replace(name, currentRef, createdRef))
174            return created;
175         drainCollectedLoggers();
176      }
177   }
178
179   /**
180    * Creates a logger for the specified class.
181    *
182    * @param clazz The class.  Must not be <jk>null</jk>.
183    * @return A logger instance.
184    */
185   public static RichLogger getLogger(Class<?> clazz) {
186      return getLogger(cn(clazz));
187   }
188
189   /**
190    * Returns the thread-confined {@link LogContext} whose entries are auto-attached to every {@link LogRecord} emitted
191    * on the current thread while a scope is open.
192    *
193    * <p>
194    * Repeated calls return the same singleton (there is exactly one thread-local context map; sharing is harmless).
195    *
196    * @return The shared {@link LogContext} singleton.  Never <jk>null</jk>.
197    */
198   public static LogContext context() {
199      return LogContext.INSTANCE;
200   }
201
202   static RichLogger findLogger(String name) {
203      drainCollectedLoggers();
204      var ref = loggers.get(name);
205      if (ref == null)
206         return null;
207      var logger = ref.get();
208      if (logger == null)
209         loggers.remove(name, ref);
210      return logger;
211   }
212
213   static void forEachLiveAncestor(String name, Consumer<RichLogger> action) {
214      var ancestorName = parentName(name);
215      while (ancestorName != null) {
216         var ancestor = findLogger(ancestorName);
217         if (ancestor != null)
218            action.accept(ancestor);
219         ancestorName = parentName(ancestorName);
220      }
221   }
222
223   private static String parentName(String name) {
224      if (ie(name))
225         return null;
226      var i = name.lastIndexOf('.');
227      return i == -1 ? "" : name.substring(0, i);
228   }
229
230   private static void drainCollectedLoggers() {
231      LoggerRef ref;
232      while ((ref = (LoggerRef)loggerQueue.poll()) != null)
233         loggers.remove(ref.name, ref);
234   }
235
236   /**
237    * Creates a builder for the specified logger name.
238    *
239    * @param name The logger name.
240    * @return A new builder.
241    */
242   public static Builder builder(String name) {
243      return new Builder(name);
244   }
245
246   /**
247    * Creates a builder for the specified class logger name.
248    *
249    * @param clazz The class.
250    * @return A new builder.
251    */
252   public static Builder builder(Class<?> clazz) {
253      return builder(cn(clazz));
254   }
255
256   /**
257    * Builder for creating configured {@link RichLogger} views.
258    */
259   public static class Builder {
260      private final String name;
261      private MessageGenerator generator = MessageGenerator.PRINTF;
262
263      Builder(String name) {
264         this.name = name;
265      }
266
267      public Builder printf() {
268         generator = MessageGenerator.PRINTF;
269         return this;
270      }
271
272      public Builder messageFormat() {
273         generator = MessageGenerator.MESSAGE_FORMAT;
274         return this;
275      }
276
277      public Builder generator(MessageGenerator value) {
278         generator = value == null ? MessageGenerator.PRINTF : value;
279         return this;
280      }
281
282      public RichLogger build() {
283         var canonical = getLogger(name);
284         return new RichLogger(canonical.delegate, generator, canonical);
285      }
286   }
287
288   /**
289    * Creates a log record capture for testing.
290    *
291    * <p>
292    * This method creates a {@link LogRecordCapture} that will capture all log records
293    * from this logger. The capture should be closed when done (typically using try-with-resources).
294    *
295    * @return A LogRecordCapture instance.
296    */
297   public LogRecordCapture captureEvents() {
298      return new LogRecordCapture(canonical);
299   }
300
301   public LogRecordCapture captureEvents(Level min) {
302      return new LogRecordCapture(canonical, x -> x.getLevel().intValue() >= min.intValue());
303   }
304
305   public LogRecordCapture captureEvents(Predicate<java.util.logging.LogRecord> filter) {
306      return new LogRecordCapture(canonical, filter);
307   }
308
309   /**
310    * Adds a log record listener.
311    *
312    * @param listener The listener to add.
313    */
314   void addLogRecordListener(LogRecordListener listener) {
315      canonical.listeners.add(listener);
316   }
317
318   /**
319    * Removes a log record listener.
320    *
321    * @param listener The listener to remove.
322    */
323   void removeLogRecordListener(LogRecordListener listener) {
324      canonical.listeners.remove(listener);
325   }
326
327   public void setUseParentListeners(boolean useParentListeners) {
328      canonical.useParentListeners = useParentListeners;
329   }
330
331   public boolean isUseParentListeners() {
332      return canonical.useParentListeners;
333   }
334
335   @Override
336   public void log(java.util.logging.LogRecord rec) {
337      LogRecordContext.attachIfAbsent(rec);
338      canonical.listeners.forEach(x -> x.onLogRecord(rec));
339      if (canonical.useParentListeners) {
340         var ancestors = new ArrayList<RichLogger>();
341         forEachLiveAncestor(canonical.getName(), ancestors::add);
342         for (var ancestor : ancestors) {
343            ancestor.listeners.forEach(x -> x.onLogRecord(rec));
344            if (!ancestor.useParentListeners)
345               break;
346         }
347      }
348      delegate.log(rec);
349   }
350
351   private static boolean hasAnyListenerInChain(RichLogger logger) {
352      if (!logger.listeners.isEmpty())
353         return true;
354      if (!logger.useParentListeners)
355         return false;
356      var ancestors = new ArrayList<RichLogger>();
357      forEachLiveAncestor(logger.getName(), ancestors::add);
358      for (var ancestor : ancestors) {
359         if (!ancestor.listeners.isEmpty())
360            return true;
361         if (!ancestor.useParentListeners)
362            break;
363      }
364      return false;
365   }
366
367   /**
368    * Central logging method that all logging calls feed through.
369    *
370    * <p>
371    * This method creates a {@link LogRecord}, notifies all listeners, and then delegates
372    * to the underlying logger.
373    *
374    * <p>
375    * The method will proceed even if the log level is not loggable if there are listeners
376    * attached, allowing listeners to capture all log records regardless of level.
377    *
378    * @param level The log level.
379    * @param msg The log message or format pattern.
380    * @param args The format arguments, or <jk>null</jk> if none.
381    * @param thrown The throwable, or <jk>null</jk> if none.
382    */
383   private void doLog(Level level, String msg, Object[] args, Throwable thrown) {
384      if (!isLoggable(level) && !hasAnyListenerInChain(canonical))
385         return;
386
387      // Create LogRecord with lazy formatting support
388      var rec = new LogRecord(getName(), level, msg, args, thrown, generator);
389      this.log(rec);
390   }
391
392   private void logSupplier(Level level, Supplier<String> msgSupplier, Throwable thrown) {
393      if (!isLoggable(level) && !hasAnyListenerInChain(canonical))
394         return;
395      var msg = msgSupplier == null ? null : msgSupplier.get();
396      var rec = new LogRecord(getName(), level, msg, null, thrown, generator);
397      this.log(rec);
398   }
399
400   // Convenience methods with formatted strings
401
402   // Standard RichLogger methods - feed through central doLog method
403   @Override
404   public void severe(String msg) {
405      doLog(SEVERE, msg, null, null);
406   }
407
408   @Override
409   public void severe(Supplier<String> msgSupplier) {
410      logSupplier(SEVERE, msgSupplier, null);
411   }
412
413   @Override
414   public void warning(String msg) {
415      doLog(WARNING, msg, null, null);
416   }
417
418   @Override
419   public void warning(Supplier<String> msgSupplier) {
420      logSupplier(WARNING, msgSupplier, null);
421   }
422
423   @Override
424   public void info(String msg) {
425      doLog(INFO, msg, null, null);
426   }
427
428   @Override
429   public void info(Supplier<String> msgSupplier) {
430      logSupplier(INFO, msgSupplier, null);
431   }
432
433   @Override
434   public void config(String msg) {
435      doLog(CONFIG, msg, null, null);
436   }
437
438   @Override
439   public void config(Supplier<String> msgSupplier) {
440      logSupplier(CONFIG, msgSupplier, null);
441   }
442
443   @Override
444   public void fine(String msg) {
445      doLog(FINE, msg, null, null);
446   }
447
448   @Override
449   public void fine(Supplier<String> msgSupplier) {
450      logSupplier(FINE, msgSupplier, null);
451   }
452
453   @Override
454   public void finer(String msg) {
455      doLog(FINER, msg, null, null);
456   }
457
458   @Override
459   public void finer(Supplier<String> msgSupplier) {
460      logSupplier(FINER, msgSupplier, null);
461   }
462
463   @Override
464   public void finest(String msg) {
465      doLog(FINEST, msg, null, null);
466   }
467
468   @Override
469   public void finest(Supplier<String> msgSupplier) {
470      logSupplier(FINEST, msgSupplier, null);
471   }
472
473   @Override
474   public void log(Level level, String msg) {
475      doLog(level, msg, null, null);
476   }
477
478   @Override
479   public void log(Level level, Supplier<String> msgSupplier) {
480      logSupplier(level, msgSupplier, null);
481   }
482
483   @Override
484   public void log(Level level, String msg, Object param1) {
485      doLog(level, msg, new Object[]{param1}, null);
486   }
487
488   @Override
489   public void log(Level level, String msg, Object[] params) {
490      doLog(level, msg, params, null);
491   }
492
493   @Override
494   public void log(Level level, String msg, Throwable thrown) {
495      doLog(level, msg, null, thrown);
496   }
497
498   @Override
499   public boolean isLoggable(Level level) {
500      return delegate.isLoggable(level);
501   }
502
503   @Override
504   public Level getLevel() {
505      return delegate.getLevel();
506   }
507
508   @Override
509   public void setLevel(Level newLevel) {
510      delegate.setLevel(newLevel);
511   }
512
513   /**
514    * Returns the wrapped JUL logger, which (unlike this wrapper) is linked into the JUL logger hierarchy.
515    *
516    * <p>
517    * Use it to walk {@link java.util.logging.Logger#getParent() parent} loggers, e.g. to find where a level was
518    * configured.
519    *
520    * @return The wrapped logger.  Never <jk>null</jk>.
521    */
522   public java.util.logging.Logger getDelegate() {
523      return delegate;
524   }
525
526   @Override
527   public String getName() {
528      return delegate.getName();
529   }
530
531   @Override
532   public void addHandler(Handler handler) {
533      delegate.addHandler(handler);
534   }
535
536   @Override
537   public void removeHandler(Handler handler) {
538      delegate.removeHandler(handler);
539   }
540
541   @Override
542   public Handler[] getHandlers() {
543      return delegate.getHandlers();
544   }
545
546   // Convenience methods with formatted strings
547
548   /**
549    * Logs a SEVERE level message with formatted string.
550    *
551    * @param pattern The format pattern.
552    * @param args The format arguments.
553    */
554   public void severe(String pattern, Object...args) {
555      doLog(SEVERE, pattern, args, null);
556   }
557
558   /**
559    * Logs a SEVERE level message with formatted string and throwable.
560    *
561    * @param thrown The throwable.
562    * @param pattern The format pattern.
563    * @param args The format arguments.
564    */
565   public void severe(Throwable thrown, String pattern, Object...args) {
566      doLog(SEVERE, pattern, args, thrown);
567   }
568
569   /**
570    * Logs a WARNING level message with formatted string.
571    *
572    * @param pattern The format pattern.
573    * @param args The format arguments.
574    */
575   public void warning(String pattern, Object...args) {
576      doLog(WARNING, pattern, args, null);
577   }
578
579   /**
580    * Logs a WARNING level message with formatted string and throwable.
581    *
582    * @param thrown The throwable.
583    * @param pattern The format pattern.
584    * @param args The format arguments.
585    */
586   public void warning(Throwable thrown, String pattern, Object...args) {
587      doLog(WARNING, pattern, args, thrown);
588   }
589
590   /**
591    * Logs an INFO level message with formatted string.
592    *
593    * @param pattern The format pattern.
594    * @param args The format arguments.
595    */
596   public void info(String pattern, Object...args) {
597      doLog(INFO, pattern, args, null);
598   }
599
600   /**
601    * Logs an INFO level message with formatted string and throwable.
602    *
603    * @param thrown The throwable.
604    * @param pattern The format pattern.
605    * @param args The format arguments.
606    */
607   public void info(Throwable thrown, String pattern, Object...args) {
608      doLog(INFO, pattern, args, thrown);
609   }
610
611   /**
612    * Logs a CONFIG level message with formatted string.
613    *
614    * @param pattern The format pattern.
615    * @param args The format arguments.
616    */
617   public void config(String pattern, Object...args) {
618      doLog(CONFIG, pattern, args, null);
619   }
620
621   /**
622    * Logs a CONFIG level message with formatted string and throwable.
623    *
624    * @param thrown The throwable.
625    * @param pattern The format pattern.
626    * @param args The format arguments.
627    */
628   public void config(Throwable thrown, String pattern, Object...args) {
629      doLog(CONFIG, pattern, args, thrown);
630   }
631
632   /**
633    * Logs a FINE level message with formatted string.
634    *
635    * @param pattern The format pattern.
636    * @param args The format arguments.
637    */
638   public void fine(String pattern, Object...args) {
639      doLog(FINE, pattern, args, null);
640   }
641
642   /**
643    * Logs a FINE level message with formatted string and throwable.
644    *
645    * @param thrown The throwable.
646    * @param pattern The format pattern.
647    * @param args The format arguments.
648    */
649   public void fine(Throwable thrown, String pattern, Object...args) {
650      doLog(FINE, pattern, args, thrown);
651   }
652
653   /**
654    * Logs a FINER level message with formatted string.
655    *
656    * @param pattern The format pattern.
657    * @param args The format arguments.
658    */
659   public void finer(String pattern, Object...args) {
660      doLog(FINER, pattern, args, null);
661   }
662
663   /**
664    * Logs a FINER level message with formatted string and throwable.
665    *
666    * @param thrown The throwable.
667    * @param pattern The format pattern.
668    * @param args The format arguments.
669    */
670   public void finer(Throwable thrown, String pattern, Object...args) {
671      doLog(FINER, pattern, args, thrown);
672   }
673
674   /**
675    * Logs a FINEST level message with formatted string.
676    *
677    * @param pattern The format pattern.
678    * @param args The format arguments.
679    */
680   public void finest(String pattern, Object...args) {
681      doLog(FINEST, pattern, args, null);
682   }
683
684   /**
685    * Logs a FINEST level message with formatted string and throwable.
686    *
687    * @param thrown The throwable.
688    * @param pattern The format pattern.
689    * @param args The format arguments.
690    */
691   public void finest(Throwable thrown, String pattern, Object...args) {
692      doLog(FINEST, pattern, args, thrown);
693   }
694
695   /**
696    * Logs a message at the specified level with formatted string.
697    *
698    * @param level The log level.  Must not be <jk>null</jk>.
699    * @param pattern The format pattern.
700    * @param args The format arguments.
701    */
702   public void logf(Level level, String pattern, Object...args) {
703      doLog(level, pattern, args, null);
704   }
705
706   /**
707    * Logs a message at the specified level with formatted string and throwable.
708    *
709    * @param level The log level.  Must not be <jk>null</jk>.
710    * @param pattern The format pattern.
711    * @param thrown The throwable.
712    * @param args The format arguments.
713    */
714   public void logf(Level level, String pattern, Throwable thrown, Object...args) {
715      doLog(level, pattern, args, thrown);
716   }
717}