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.microservice.jetty;
018
019import static java.util.logging.Level.*;
020import static org.apache.juneau.commons.utils.Shorts.*;
021import static org.apache.juneau.commons.utils.SystemUtils.*;
022
023import java.util.logging.*;
024import java.util.logging.Logger;
025
026import org.slf4j.*;
027import org.slf4j.helpers.*;
028import org.slf4j.spi.*;
029
030/**
031 * Implementation of Jetty {@link Logger} based on {@link Logger}.
032 *
033 * <p>
034 * Allows Jetty to log to the Java Util logging framework (and thus to the main log file defined in the
035 * <cc>[Logging]</cc> section).
036 *
037 * <p>
038 * Can be used by setting the following system property in the microservice config file.
039 *
040 * <p class='bini'>
041 *    <cs>[SystemProperties]</cs>
042 *
043 *    <cc># Configure Jetty to log using java-util logging</cc>
044 *    <ck>org.eclipse.jetty.util.log.class</ck> = org.apache.juneau.microservice.jetty.JettyLogger
045 * </p>
046 *
047 * <h5 class='section'>See Also:</h5><ul>
048 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauMicroserviceJetty">juneau-microservice-jetty</a>
049 * </ul>
050 */
051public class JettyLogger implements LocationAwareLogger {
052   private static final boolean SHOW_SOURCE = env("org.eclipse.jetty.util.log.SOURCE", env("org.eclipse.jetty.util.log.javautil.SOURCE", true));
053
054   /**
055    * Formats a log message by replacing "{}" placeholders with the provided arguments.
056    *
057    * <p>
058    * Uses SLF4J's {@link MessageFormatter} so the SLF4J-style <c>{}</c> anchors used by Jetty's logging calls are
059    * expanded (a printf-style formatter would leave the <c>{}</c> literal and silently drop the arguments).
060    *
061    * @param msg The message template with "{}" placeholders.
062    * @param args The arguments to substitute into the message.
063    * @return The formatted message string.
064    */
065   private static String format(String msg, Object...args) {
066      return MessageFormatter.arrayFormat(msg, args).getMessage();
067   }
068
069   /**
070    * Maps an SLF4J integer log level to the equivalent {@link Level}.
071    *
072    * @param level The SLF4J level constant (e.g. {@link LocationAwareLogger#DEBUG_INT}).
073    * @return The equivalent Java Util Logging level.
074    */
075   private static Level toLevel(int level) {
076      return switch (level) {
077         case LocationAwareLogger.TRACE_INT -> FINEST;
078         case LocationAwareLogger.DEBUG_INT -> FINE;
079         case LocationAwareLogger.INFO_INT -> INFO;
080         case LocationAwareLogger.WARN_INT -> WARNING;
081         default -> SEVERE;  // ERROR_INT and any out-of-range value map to the highest severity.
082      };
083   }
084
085   /**
086    * The originally configured log level, used to restore the level when debug mode is disabled.
087    */
088   private Level configuredLevel;
089
090   /**
091    * The underlying Java Util Logger instance.
092    */
093   private final Logger logger;
094
095   /**
096    * Default constructor.
097    *
098    * <p>
099    * Returns the logger with name <js>"org.eclipse.jetty.util.log.javautil"</js>.
100    */
101   public JettyLogger() {
102      this("org.eclipse.jetty.util.log.javautil");
103   }
104
105   /**
106    * Normal constructor.
107    *
108    * @param name The logger name.  Must not be <jk>null</jk>.
109    */
110   public JettyLogger(String name) {
111      logger = Logger.getLogger(name);
112      configuredLevel = logger.getLevel();
113   }
114
115   @Override
116   public void debug(Marker marker, String msg) {
117      debug(msg);  // Markers are not used by this JUL-backed implementation.
118   }
119
120   @Override
121   public void debug(Marker marker, String format, Object arg) {
122      debug(format, arg);  // Markers are not used by this JUL-backed implementation.
123   }
124
125   @Override
126   public void debug(Marker marker, String format, Object...arguments) {
127      debug(format, arguments);  // Markers are not used by this JUL-backed implementation.
128   }
129
130   @Override
131   public void debug(Marker marker, String format, Object arg1, Object arg2) {
132      debug(format, arg1, arg2);  // Markers are not used by this JUL-backed implementation.
133   }
134
135   @Override
136   public void debug(Marker marker, String msg, Throwable t) {
137      debug(msg, t);  // Markers are not used by this JUL-backed implementation.
138   }
139
140   @Override
141   public void debug(String msg) {
142      if (isLoggable(FINE))
143         log(FINE, msg, null);
144   }
145
146   /**
147    * Logs a debug message with a long argument.
148    *
149    * @param msg The message template with "{}" placeholder.
150    * @param arg The long argument to substitute into the message.
151    */
152   public void debug(String msg, long arg) {
153      if (isLoggable(FINE))
154         log(FINE, format(msg, arg), null);
155   }
156
157   @Override
158   public void debug(String msg, Object...args) {
159      if (isLoggable(FINE))
160         log(FINE, format(msg, args), null);
161   }
162
163   @Override
164   public void debug(String format, Object arg) {
165      if (isLoggable(FINE))
166         log(FINE, format(format, arg), null);
167   }
168
169   @Override
170   public void debug(String format, Object arg1, Object arg2) {
171      if (isLoggable(FINE))
172         log(FINE, format(format, arg1, arg2), null);
173   }
174
175   @Override
176   public void debug(String msg, Throwable thrown) {
177      if (isLoggable(FINE))
178         log(FINE, msg, thrown);
179   }
180
181   /**
182    * Logs a debug message with only a throwable.
183    *
184    * @param thrown The throwable to log.
185    */
186   public void debug(Throwable thrown) {
187      if (isLoggable(FINE))
188         log(FINE, "", thrown);
189   }
190
191   @Override
192   public void error(Marker marker, String msg) {
193      error(msg);  // Markers are not used by this JUL-backed implementation.
194   }
195
196   @Override
197   public void error(Marker marker, String format, Object arg) {
198      error(format, arg);  // Markers are not used by this JUL-backed implementation.
199   }
200
201   @Override
202   public void error(Marker marker, String format, Object...arguments) {
203      error(format, arguments);  // Markers are not used by this JUL-backed implementation.
204   }
205
206   @Override
207   public void error(Marker marker, String format, Object arg1, Object arg2) {
208      error(format, arg1, arg2);  // Markers are not used by this JUL-backed implementation.
209   }
210
211   @Override
212   public void error(Marker marker, String msg, Throwable t) {
213      error(msg, t);  // Markers are not used by this JUL-backed implementation.
214   }
215
216   @Override
217   public void error(String msg) {
218      if (isLoggable(SEVERE))
219         log(SEVERE, msg, null);
220   }
221
222   @Override
223   public void error(String format, Object arg) {
224      if (isLoggable(SEVERE))
225         log(SEVERE, format(format, arg), null);
226   }
227
228   @Override
229   public void error(String format, Object...arguments) {
230      if (isLoggable(SEVERE))
231         log(SEVERE, format(format, arguments), null);
232   }
233
234   @Override
235   public void error(String format, Object arg1, Object arg2) {
236      if (isLoggable(SEVERE))
237         log(SEVERE, format(format, arg1, arg2), null);
238   }
239
240   @Override
241   public void error(String msg, Throwable t) {
242      if (isLoggable(SEVERE))
243         log(SEVERE, msg, t);
244   }
245
246   /**
247    * Creates a child logger with the specified name.
248    *
249    * @param name The logger name.  Must not be <jk>null</jk>.
250    * @return A new JettyLogger instance for the specified name.
251    */
252   public JettyLogger getLogger(String name) {
253      return new JettyLogger(name);
254   }
255
256   @Override
257   public String getName() { return logger.getName(); }
258
259   /**
260    * Logs an ignored exception at the FINEST level.
261    *
262    * @param ignored The exception to log as ignored.
263    */
264   public void ignore(Throwable ignored) {
265      if (isLoggable(FINEST))
266         log(FINEST, "IGNORED EXCEPTION ", ignored);
267   }
268
269   @Override
270   public void info(Marker marker, String msg) {
271      info(msg);  // Markers are not used by this JUL-backed implementation.
272   }
273
274   @Override
275   public void info(Marker marker, String format, Object arg) {
276      info(format, arg);  // Markers are not used by this JUL-backed implementation.
277   }
278
279   @Override
280   public void info(Marker marker, String format, Object...arguments) {
281      info(format, arguments);  // Markers are not used by this JUL-backed implementation.
282   }
283
284   @Override
285   public void info(Marker marker, String format, Object arg1, Object arg2) {
286      info(format, arg1, arg2);  // Markers are not used by this JUL-backed implementation.
287   }
288
289   @Override
290   public void info(Marker marker, String msg, Throwable t) {
291      info(msg, t);  // Markers are not used by this JUL-backed implementation.
292   }
293
294   @Override
295   public void info(String msg) {
296      if (isLoggable(INFO))
297         log(INFO, msg, null);
298   }
299
300   @Override
301   public void info(String msg, Object...args) {
302      if (isLoggable(INFO))
303         log(INFO, format(msg, args), null);
304   }
305
306   @Override
307   public void info(String format, Object arg) {
308      if (isLoggable(INFO))
309         log(INFO, format(format, arg), null);
310   }
311
312   @Override
313   public void info(String format, Object arg1, Object arg2) {
314      if (isLoggable(INFO))
315         log(INFO, format(format, arg1, arg2), null);
316   }
317
318   @Override
319   public void info(String msg, Throwable thrown) {
320      if (isLoggable(INFO))
321         log(INFO, msg, thrown);
322   }
323
324   /**
325    * Logs an info message with only a throwable.
326    *
327    * @param thrown The throwable to log.
328    */
329   public void info(Throwable thrown) {
330      if (isLoggable(INFO))
331         log(INFO, "", thrown);
332   }
333
334   @Override
335   public boolean isDebugEnabled() { return isLoggable(FINE); }
336
337   @Override
338   public boolean isDebugEnabled(Marker marker) { return isLoggable(FINE); }
339
340   @Override
341   public boolean isErrorEnabled() { return isLoggable(SEVERE); }
342
343   @Override
344   public boolean isErrorEnabled(Marker marker) { return isLoggable(SEVERE); }
345
346   @Override
347   public boolean isInfoEnabled() { return isLoggable(INFO); }
348
349   @Override
350   public boolean isInfoEnabled(Marker marker) { return isLoggable(INFO); }
351
352   @Override
353   public boolean isTraceEnabled() { return isLoggable(FINEST); }
354
355   @Override
356   public boolean isTraceEnabled(Marker marker) { return isLoggable(FINEST); }
357
358   @Override
359   public boolean isWarnEnabled() { return isLoggable(WARNING); }
360
361   @Override
362   public boolean isWarnEnabled(Marker marker) { return isLoggable(WARNING); }
363
364   @Override
365   public void log(Marker marker, String fqcn, int level, String message, Object[] argArray, Throwable t) {
366      var l = toLevel(level);
367      if (isLoggable(l))
368         log(l, argArray == null ? message : format(message, argArray), t);
369   }
370
371   /**
372    * Enables or disables debug logging.
373    *
374    * <p>
375    * When enabled, sets the log level to FINE. When disabled, restores the previously configured log level.
376    *
377    * @param enabled <jk>true</jk> to enable debug logging, <jk>false</jk> to disable.
378    */
379   public void setDebugEnabled(boolean enabled) {
380      if (enabled) {
381         configuredLevel = logger.getLevel();
382         logger.setLevel(FINE);
383      } else {
384         logger.setLevel(configuredLevel);
385      }
386   }
387
388   @Override
389   public void trace(Marker marker, String msg) {
390      trace(msg);  // Markers are not used by this JUL-backed implementation.
391   }
392
393   @Override
394   public void trace(Marker marker, String format, Object arg) {
395      trace(format, arg);  // Markers are not used by this JUL-backed implementation.
396   }
397
398   @Override
399   public void trace(Marker marker, String format, Object...argArray) {
400      trace(format, argArray);  // Markers are not used by this JUL-backed implementation.
401   }
402
403   @Override
404   public void trace(Marker marker, String format, Object arg1, Object arg2) {
405      trace(format, arg1, arg2);  // Markers are not used by this JUL-backed implementation.
406   }
407
408   @Override
409   public void trace(Marker marker, String msg, Throwable t) {
410      trace(msg, t);  // Markers are not used by this JUL-backed implementation.
411   }
412
413   @Override
414   public void trace(String msg) {
415      if (isLoggable(FINEST))
416         log(FINEST, msg, null);
417   }
418
419   @Override
420   public void trace(String format, Object arg) {
421      if (isLoggable(FINEST))
422         log(FINEST, format(format, arg), null);
423   }
424
425   @Override
426   public void trace(String format, Object...arguments) {
427      if (isLoggable(FINEST))
428         log(FINEST, format(format, arguments), null);
429   }
430
431   @Override
432   public void trace(String format, Object arg1, Object arg2) {
433      if (isLoggable(FINEST))
434         log(FINEST, format(format, arg1, arg2), null);
435   }
436
437   @Override
438   public void trace(String msg, Throwable t) {
439      if (isLoggable(FINEST))
440         log(FINEST, msg, t);
441   }
442
443   @Override
444   public void warn(Marker marker, String msg) {
445      warn(msg);  // Markers are not used by this JUL-backed implementation.
446   }
447
448   @Override
449   public void warn(Marker marker, String format, Object arg) {
450      warn(format, arg);  // Markers are not used by this JUL-backed implementation.
451   }
452
453   @Override
454   public void warn(Marker marker, String format, Object...arguments) {
455      warn(format, arguments);  // Markers are not used by this JUL-backed implementation.
456   }
457
458   @Override
459   public void warn(Marker marker, String format, Object arg1, Object arg2) {
460      warn(format, arg1, arg2);  // Markers are not used by this JUL-backed implementation.
461   }
462
463   @Override
464   public void warn(Marker marker, String msg, Throwable t) {
465      warn(msg, t);  // Markers are not used by this JUL-backed implementation.
466   }
467
468   @Override
469   public void warn(String msg) {
470      if (isLoggable(WARNING))
471         log(WARNING, msg, null);
472   }
473
474   @Override
475   public void warn(String msg, Object...args) {
476      if (isLoggable(WARNING))
477         log(WARNING, format(msg, args), null);
478   }
479
480   @Override
481   public void warn(String format, Object arg) {
482      if (isLoggable(WARNING))
483         log(WARNING, format(format, arg), null);
484   }
485
486   @Override
487   public void warn(String format, Object arg1, Object arg2) {
488      if (isLoggable(WARNING))
489         log(WARNING, format(format, arg1, arg2), null);
490   }
491
492   @Override
493   public void warn(String msg, Throwable thrown) {
494      if (isLoggable(WARNING))
495         log(WARNING, msg, thrown);
496   }
497
498   /**
499    * Logs a warning message with only a throwable.
500    *
501    * @param thrown The throwable to log.
502    */
503   public void warn(Throwable thrown) {
504      if (isLoggable(WARNING))
505         log(WARNING, "", thrown);
506   }
507
508   /**
509    * Checks if a message at the specified level would be logged.
510    *
511    * @param level The log level to check.
512    * @return <jk>true</jk> if the level is loggable, <jk>false</jk> otherwise.
513    */
514   private boolean isLoggable(Level level) {
515      return logger.isLoggable(level);
516   }
517
518   /**
519    * Internal log method that creates a LogRecord and logs it.
520    *
521    * <p>
522    * Optionally includes source class and method name in the log record if SHOW_SOURCE is enabled.
523    *
524    * @param level The log level.
525    * @param msg The log message.
526    * @param thrown The throwable to log, or <jk>null</jk> if none.
527    */
528   private void log(Level level, String msg, Throwable thrown) {
529      var r = new LogRecord(level, msg);
530      if (nn(thrown))
531         r.setThrown(thrown);
532      r.setLoggerName(logger.getName());
533      if (SHOW_SOURCE) { // HTT - SHOW_SOURCE is a static final set at class load time from system property; false branch requires property set before JVM start
534         StackTraceElement[] stack = new Throwable().getStackTrace();
535         for (var e : stack) {
536            if (! e.getClassName().equals(cn(this))) {
537               r.setSourceClassName(e.getClassName());
538               r.setSourceMethodName(e.getMethodName());
539               break;
540            }
541         }
542      }
543      logger.log(r);
544   }
545}