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.rest.logger;
018
019import static java.util.logging.Level.*;
020import static org.apache.juneau.Enablement.*;
021import static org.apache.juneau.commons.utils.CollectionUtils.*;
022import static org.apache.juneau.commons.utils.IoUtils.*;
023import static org.apache.juneau.commons.utils.StringUtils.*;
024import static org.apache.juneau.commons.utils.ThrowableUtils.*;
025import static org.apache.juneau.commons.utils.Utils.*;
026import static org.apache.juneau.rest.logger.CallLoggingDetail.*;
027
028import java.util.*;
029import java.util.function.*;
030import java.util.logging.*;
031
032import org.apache.juneau.*;
033import org.apache.juneau.commons.collections.*;
034import org.apache.juneau.commons.utils.*;
035import org.apache.juneau.cp.*;
036import org.apache.juneau.rest.annotation.*;
037import org.apache.juneau.rest.stats.*;
038import org.apache.juneau.rest.util.*;
039
040import jakarta.servlet.http.*;
041
042/**
043 * Basic implementation of a {@link CallLogger} for logging HTTP requests.
044 *
045 * <p>
046 * Provides the following capabilities:
047 * <ul>
048 *    <li>Allows incoming HTTP requests to be logged at various {@link Enablement detail levels}.
049 *    <li>Allows rules to be defined to handle request logging differently depending on the resulting status code.
050 *    <li>Allows use of stack trace hashing to eliminate duplication of stack traces in log files.
051 *    <li>Allows customization of handling of where requests are logged to.
052 *    <li>Allows configuration via system properties or environment variables.
053 * </ul>
054 *
055 * <p>
056 * The following is an example of a logger that logs errors only when debugging is not enabled, and everything when
057 * logging is enabled.
058 *
059 * <h5 class='section'>Example:</h5>
060 * <p class='bjava'>
061 *       CallLogger <jv>logger</jv> = CallLogger
062 *          .<jsm>create</jsm>()
063 *          .logger(<js>"MyLogger"</js>)  <jc>// Use MyLogger Java logger.</jc>
064 *          .normalRules(  <jc>// Rules when debugging is not enabled.</jc>
065 *             <jsm>createRule</jsm>()  <jc>// Log 500+ errors with status-line and header information.</jc>
066 *                .statusFilter(x -&gt; x &gt;= 500)
067 *                .level(<jsf>SEVERE</jsf>)
068 *                .requestDetail(<jsf>HEADER</jsf>)
069 *                .responseDetail<jsf>(HEADER</jsf>)
070 *                .build(),
071 *             <jsm>createRule</jsm>()  <jc>// Log 400-500 errors with just status-line information.</jc>
072 *                .statusFilter(x -&gt; x &gt;= 400)
073 *                .level(<jsf>WARNING</jsf>)
074 *                .requestDetail(<jsf>STATUS_LINE</jsf>)
075 *                .responseDetail(<jsf>STATUS_LINE</jsf>)
076 *                .build()
077 *          )
078 *          .debugRules(  <jc>// Rules when debugging is enabled.</jc>
079 *             <jsm>createRule</jsm>()  <jc>// Log everything with full details.</jc>
080 *                .level(<jsf>SEVERE</jsf>)
081 *                .requestDetail(<jsf>ENTITY</jsf>)
082 *                .responseDetail(<jsf>ENTITY</jsf>)
083 *                .build()
084 *          )
085 *          .build()
086 *       ;
087 * </p>
088 *
089 * <h5 class='section'>See Also:</h5><ul>
090 *    <li class='jm'>{@link org.apache.juneau.rest.RestContext.Builder#callLogger()}
091 *    <li class='jm'>{@link org.apache.juneau.rest.RestContext.Builder#debugEnablement()}
092 *    <li class='ja'>{@link Rest#debug}
093 *    <li class='ja'>{@link RestOp#debug}
094 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerLoggingAndDebugging">Logging / Debugging</a>
095 * </ul>
096 */
097public class CallLogger {
098   /**
099    * Builder class.
100    */
101   public static class Builder {
102
103      Logger logger;
104      ThrownStore thrownStore;
105      List<CallLoggerRule> normalRules = list(), debugRules = list();
106      Enablement enabled;
107      Predicate<HttpServletRequest> enabledTest;
108      CallLoggingDetail requestDetail, responseDetail;
109      Level level;
110
111      /**
112       * Constructor.
113       *
114       * @param beanStore The bean store to use for creating beans.
115       */
116      protected Builder(BeanStore beanStore) {
117         logger = Logger.getLogger(env(SP_logger, "global"));
118         enabled = env(SP_enabled, ALWAYS);
119         enabledTest = x -> false;
120         requestDetail = env(SP_requestDetail, STATUS_LINE);
121         responseDetail = env(SP_responseDetail, STATUS_LINE);
122         level = env(SP_level).map(Level::parse).orElse(OFF);
123      }
124
125      /**
126       * Instantiates a new call logger based on the settings in this builder.
127       *
128       * @return A new call logger.
129       */
130      public CallLogger build() {
131         return new CallLogger(this);
132      }
133
134      /**
135       * Adds logging rules to use when debug mode is enabled.
136       *
137       * <p>
138       * Logging rules are matched in the order they are added.  The first to match wins.
139       *
140       * @param values The logging rules to add to the list of rules.
141       * @return This object.
142       */
143      public Builder debugRules(CallLoggerRule...values) {
144         for (var rule : values)
145            debugRules.add(rule);
146         return this;
147      }
148
149      /**
150       * Shortcut for calling <c>enabled(<jsf>NEVER</jsf>)</c>.
151       *
152       * @return This object.
153       */
154      public Builder disabled() {
155         return enabled(NEVER);
156      }
157
158      /**
159       * Specifies the default logging enablement setting.
160       *
161       * <p>
162       * This specifies the default logging enablement value if not set on the first matched rule or if no rules match.
163       *
164       * <p>
165       * If not specified, the setting is determined via the following:
166       * <ul>
167       *    <li><js>{@link CallLogger#SP_enabled "juneau.restLogger.enabled"} system property.
168       *    <li><js>{@link CallLogger#SP_enabled "JUNEAU_RESTLOGGER_ENABLED"} environment variable.
169       *    <li><js>"ALWAYS"</js>.
170       * </ul>
171       *
172       * <ul class='values'>
173       *    <li>{@link Enablement#ALWAYS ALWAYS} (default) - Logging is enabled.
174       *    <li>{@link Enablement#NEVER NEVER} - Logging is disabled.
175       *    <li>{@link Enablement#CONDITIONAL CONDITIONALLY} - Logging is enabled if it passes the {@link #enabledTest(Predicate)} test.
176       * </ul>
177       *
178       * <p>
179       * @param value
180       *    The default enablement flag value.  Can be <jk>null</jk> to use the default.
181       * @return This object.
182       */
183      public Builder enabled(Enablement value) {
184         enabled = value;
185         return this;
186      }
187
188      /**
189       * Specifies the default logging enablement test predicate.
190       *
191       * <p>
192       * This specifies the default logging enablement test if not set on the first matched rule or if no rules match.
193       *
194       * <p>
195       * This setting has no effect if the enablement setting is not {@link Enablement#CONDITIONAL CONDITIONALLY}.
196       *
197       * <p>
198       * The default if not specified is <c><jv>x</jv> -&gt; <jk>false</jk></c> (never log).
199       *
200       * @param value
201       *    The default enablement flag value.  Can be <jk>null</jk> to use the default.
202       * @return This object.
203       */
204      public Builder enabledTest(Predicate<HttpServletRequest> value) {
205         enabledTest = value;
206         return this;
207      }
208
209      /**
210       * The default logging level to use for logging the request/response.
211       *
212       * <p>
213       * This specifies the default logging level if not set on the first matched rule or if no rules match.
214       *
215       * <p>
216       * If not specified, the setting is determined via the following:
217       * <ul>
218       *    <li><js>{@link CallLogger#SP_level "juneau.restLogger.level"} system property.
219       *    <li><js>{@link CallLogger#SP_level "JUNEAU_RESTLOGGER_level"} environment variable.
220       *    <li><js>"OFF"</js>.
221       * </ul>
222       *
223       * @param value
224       *    The new value for this property, or <jk>null</jk> to use the default value.
225       * @return This object.
226       */
227      public Builder level(Level value) {
228         level = value;
229         return this;
230      }
231
232      /**
233       * Specifies the logger to use for logging the request.
234       *
235       * <p>
236       * If not specified, the logger name is determined in the following order:
237       * <ol>
238       *    <li><js>{@link CallLogger#SP_logger "juneau.restLogger.logger"} system property.
239       *    <li><js>{@link CallLogger#SP_logger "JUNEAU_RESTLOGGER_LOGGER"} environment variable.
240       *    <li><js>"global"</js>.
241       * </ol>
242       *
243       * <p>
244       * The {@link CallLogger#getLogger()} method can also be overridden to provide different logic.
245       *
246       * @param value
247       *    The logger to use for logging the request.
248       * @return This object.
249       */
250      public Builder logger(Logger value) {
251         logger = value;
252         return this;
253      }
254
255      /**
256       * Specifies the logger to use for logging the request.
257       *
258       * <p>
259       * Shortcut for calling <c>logger(Logger.<jsm>getLogger</jsm>(value))</c>.
260       *
261       * <p>
262       * If not specified, the logger name is determined in the following order:
263       * <ol>
264       *    <li><js>{@link CallLogger#SP_logger "juneau.restLogger.logger"} system property.
265       *    <li><js>{@link CallLogger#SP_logger "JUNEAU_RESTLOGGER_LOGGER"} environment variable.
266       *    <li><js>"global"</js>.
267       * </ol>
268       *
269       * <p>
270       * The {@link CallLogger#getLogger()} method can also be overridden to provide different logic.
271       *
272       * @param value
273       *    The logger to use for logging the request.
274       * @return This object.
275       */
276      public Builder logger(String value) {
277         logger = value == null ? null : Logger.getLogger(value);
278         return this;
279      }
280
281      /**
282       * Same as {@link #logger(Logger)} but only sets the value if it's currently <jk>null</jk>.
283       *
284       * @param value The logger to use for logging the request.
285       * @return This object.
286       */
287      public Builder loggerOnce(Logger value) {
288         if (logger == null)
289            logger = value;
290         return this;
291      }
292
293      /**
294       * Adds logging rules to use when debug mode is not enabled.
295       *
296       * <p>
297       * Logging rules are matched in the order they are added.  The first to match wins.
298       *
299       * @param values The logging rules to add to the list of rules.
300       * @return This object.
301       */
302      public Builder normalRules(CallLoggerRule...values) {
303         for (var rule : values)
304            normalRules.add(rule);
305         return this;
306      }
307
308      /**
309       * The default level of detail to log on a request.
310       *
311       * <p>
312       * This specifies the default level of request detail if not set on the first matched rule or if no rules match.
313       *
314       * <p>
315       * If not specified, the setting is determined via the following:
316       * <ul>
317       *    <li><js>{@link CallLogger#SP_requestDetail "juneau.restLogger.requestDetail"} system property.
318       *    <li><js>{@link CallLogger#SP_requestDetail "JUNEAU_RESTLOGGER_requestDetail"} environment variable.
319       *    <li><js>"STATUS_LINE"</js>.
320       * </ul>
321       *
322       * <ul class='values'>
323       *    <li>{@link CallLoggingDetail#STATUS_LINE STATUS_LINE} - Log only the status line.
324       *    <li>{@link CallLoggingDetail#HEADER HEADER} - Log the status line and headers.
325       *    <li>{@link CallLoggingDetail#ENTITY ENTITY} - Log the status line and headers and content if available.
326       * </ul>
327       *
328       * @param value
329       *    The new value for this property, or <jk>null</jk> to use the default.
330       * @return This object.
331       */
332      public Builder requestDetail(CallLoggingDetail value) {
333         requestDetail = value;
334         return this;
335      }
336
337      /**
338       * The default level of detail to log on a response.
339       *
340       * <p>
341       * This specifies the default level of response detail if not set on the first matched rule or if no rules match.
342       *
343       * <p>
344       * If not specified, the setting is determined via the following:
345       * <ul>
346       *    <li><js>{@link CallLogger#SP_responseDetail "juneau.restLogger.responseDetail"} system property.
347       *    <li><js>{@link CallLogger#SP_responseDetail "JUNEAU_RESTLOGGER_responseDetail"} environment variable.
348       *    <li><js>"STATUS_LINE"</js>.
349       * </ul>
350       *
351       * <ul class='values'>
352       *    <li>{@link CallLoggingDetail#STATUS_LINE STATUS_LINE} - Log only the status line.
353       *    <li>{@link CallLoggingDetail#HEADER HEADER} - Log the status line and headers.
354       *    <li>{@link CallLoggingDetail#ENTITY ENTITY} - Log the status line and headers and content if available.
355       * </ul>
356       *
357       * @param value
358       *    The new value for this property, or <jk>null</jk> to use the default.
359       * @return This object.
360       */
361      public Builder responseDetail(CallLoggingDetail value) {
362         responseDetail = value;
363         return this;
364      }
365
366      /**
367       * Shortcut for adding the same rules as normal and debug rules.
368       *
369       * <p>
370       * Logging rules are matched in the order they are added.  The first to match wins.
371       *
372       * @param values The logging rules to add to the list of rules.
373       * @return This object.
374       */
375      public Builder rules(CallLoggerRule...values) {
376         return normalRules(values).debugRules(values);
377      }
378
379      /**
380       * Specifies the thrown exception store to use for getting stack trace information (hash IDs and occurrence counts).
381       *
382       * @param value
383       *    The stack trace store.
384       *    <br>If <jk>null</jk>, stack trace information will not be logged.
385       * @return This object.
386       */
387      public Builder thrownStore(ThrownStore value) {
388         thrownStore = value;
389         return this;
390      }
391
392      /**
393       * Same as {@link #thrownStore(ThrownStore)} but only sets the value if it's currently <jk>null</jk>.
394       *
395       * @param value
396       *    The stack trace store.
397       *    <br>If <jk>null</jk>, stack trace information will not be logged.
398       * @return This object.
399       */
400      public Builder thrownStoreOnce(ThrownStore value) {
401         if (thrownStore == null)
402            thrownStore = value;
403         return this;
404      }
405   }
406
407   /** Represents no logger */
408   public abstract class Void extends CallLogger {
409      Void(BeanStore beanStore) {
410         super(beanStore);
411      }
412   }
413
414   private static final CallLoggerRule DEFAULT_RULE = CallLoggerRule.create(BeanStore.INSTANCE).build();
415
416   /**
417    * System property name for the default logger name to use for {@link CallLogger} objects.
418    * <p>
419    * Can also use a <c>JUNEAU_RESTLOGGER_LOGGER</c> environment variable.
420    * <p>
421    * If not specified, the default is <js>"global"</js>.
422    */
423   public static final String SP_logger = "juneau.restLogger.logger";
424
425   /**
426    * System property name for the default enablement setting for {@link CallLogger} objects.
427    * <p>
428    * Can also use a <c>JUNEAU_RESTLOGGER_ENABLED</c> environment variable.
429    * <p>
430    * The possible values are:
431    * <ul>
432    *    <li>{@link Enablement#ALWAYS "ALWAYS"} (default) - Logging is enabled.
433    *    <li>{@link Enablement#NEVER "NEVER"} - Logging is disabled.
434    *    <li>{@link Enablement#CONDITIONAL "CONDITIONALLY"} - Logging is enabled if it passes the {@link Builder#enabledTest(Predicate)} test.
435    * </ul>
436    */
437   public static final String SP_enabled = "juneau.restLogger.enabled";
438
439   /**
440    * System property name for the default request detail setting for {@link CallLogger} objects.
441    * <p>
442    * Can also use a <c>JUNEAU_RESTLOGGER_REQUESTDETAIL</c> environment variable.
443    *
444    * <ul class='values'>
445    *    <li>{@link CallLoggingDetail#STATUS_LINE "STATUS_LINE"} (default) - Log only the status line.
446    *    <li>{@link CallLoggingDetail#HEADER "HEADER"} - Log the status line and headers.
447    *    <li>{@link CallLoggingDetail#ENTITY "ENTITY"} - Log the status line and headers and content if available.
448    * </ul>
449    */
450   public static final String SP_requestDetail = "juneau.restLogger.requestDetail";
451
452   /**
453    * System property name for the default response detail setting for {@link CallLogger} objects.
454    * <p>
455    * Can also use a <c>JUNEAU_RESTLOGGER_RESPONSEDETAIL</c> environment variable.
456    *
457    * <ul class='values'>
458    *    <li>{@link CallLoggingDetail#STATUS_LINE "STATUS_LINE"} (default) - Log only the status line.
459    *    <li>{@link CallLoggingDetail#HEADER "HEADER"} - Log the status line and headers.
460    *    <li>{@link CallLoggingDetail#ENTITY "ENTITY"} - Log the status line and headers and content if available.
461    * </ul>
462    */
463   public static final String SP_responseDetail = "juneau.restLogger.responseDetail";
464
465   /**
466    * System property name for the logging level setting for {@link CallLogger} objects.
467    * <p>
468    * Can also use a <c>JUNEAU_RESTLOGGER_LEVEL</c> environment variable.
469    *
470    * <ul class='values'>
471    *    <li>{@link Level#OFF "OFF"} (default)
472    *    <li>{@link Level#SEVERE "SEVERE"}
473    *    <li>{@link Level#WARNING "WARNING"}
474    *    <li>{@link Level#INFO "INFO"}
475    *    <li>{@link Level#CONFIG "CONFIG"}
476    *    <li>{@link Level#FINE "FINE"}
477    *    <li>{@link Level#FINER "FINER"}
478    *    <li>{@link Level#FINEST "FINEST"}
479    * </ul>
480    */
481   public static final String SP_level = "juneau.restLogger.level";
482
483   /**
484    * Static creator.
485    *
486    * @param beanStore The bean store to use for creating beans.
487    * @return A new builder for this object.
488    */
489   public static Builder create(BeanStore beanStore) {
490      return new Builder(beanStore);
491   }
492
493   private final Logger logger;
494   private final ThrownStore thrownStore;
495   private final CallLoggerRule[] normalRules, debugRules;
496   private final Enablement enabled;
497   private final Predicate<HttpServletRequest> enabledTest;
498   private final Level level;
499   private final CallLoggingDetail requestDetail, responseDetail;
500
501   /**
502    * Constructor.
503    * <p>
504    * Subclasses typically override the {@link #init(BeanStore)} method when using this constructor.
505    *
506    * @param beanStore The bean store containing injectable beans for this logger.
507    */
508   public CallLogger(BeanStore beanStore) {
509      var builder = init(beanStore);
510      this.logger = builder.logger;
511      this.thrownStore = builder.thrownStore;
512      this.normalRules = builder.normalRules.toArray(new CallLoggerRule[builder.normalRules.size()]);
513      this.debugRules = builder.debugRules.toArray(new CallLoggerRule[builder.debugRules.size()]);
514      this.enabled = builder.enabled;
515      this.enabledTest = builder.enabledTest;
516      this.requestDetail = builder.requestDetail;
517      this.responseDetail = builder.responseDetail;
518      this.level = builder.level;
519   }
520
521   /**
522    * Constructor.
523    *
524    * @param builder The builder for this logger.
525    */
526   public CallLogger(Builder builder) {
527      this.logger = builder.logger;
528      this.thrownStore = builder.thrownStore;
529      this.normalRules = builder.normalRules.toArray(new CallLoggerRule[builder.normalRules.size()]);
530      this.debugRules = builder.debugRules.toArray(new CallLoggerRule[builder.debugRules.size()]);
531      this.enabled = builder.enabled;
532      this.enabledTest = builder.enabledTest;
533      this.requestDetail = builder.requestDetail;
534      this.responseDetail = builder.responseDetail;
535      this.level = builder.level;
536   }
537
538   /**
539    * Called at the end of a servlet request to log the request.
540    *
541    * @param req The servlet request.
542    * @param res The servlet response.
543    */
544   public void log(HttpServletRequest req, HttpServletResponse res) {
545
546      var rule = getRule(req, res);
547
548      if (! isEnabled(rule, req))
549         return;
550
551      var level = firstNonNull(rule.getLevel(), this.level);
552
553      if (level == Level.OFF)
554         return;
555
556      var e = cast(Throwable.class, req.getAttribute("Exception"));
557      var execTime = cast(Long.class, req.getAttribute("ExecTime"));
558
559      var reqd = firstNonNull(rule.getRequestDetail(), requestDetail);
560      var resd = firstNonNull(rule.getResponseDetail(), responseDetail);
561
562      var method = req.getMethod();
563      int status = res.getStatus();
564      var uri = req.getRequestURI();
565      var reqContent = getRequestContent(req);
566      var resContent = getResponseContent(req, res);
567
568      var sb = new StringBuilder();
569
570      if (reqd != STATUS_LINE || resd != STATUS_LINE)
571         sb.append("\n=== HTTP Call (incoming) ======================================================\n");
572
573      var sti = getThrownStats(e);
574
575      sb.append('[').append(status);
576
577      if (nn(sti)) {
578         var count = sti.getCount();
579         sb.append(',').append(StringUtils.toHex8(Math.abs(sti.getHash()))).append('.').append(count);
580         if (count > 1)
581            e = null;
582      }
583
584      sb.append("] ");
585
586      sb.append("HTTP ").append(method).append(' ').append(uri);
587
588      if (reqd != STATUS_LINE || resd != STATUS_LINE) {
589
590         if (reqd.isOneOf(HEADER, ENTITY)) {
591            var qs = req.getQueryString();
592            if (nn(qs))
593               sb.append('?').append(qs);
594         }
595
596         if (nn(reqContent) && reqd.isOneOf(HEADER, ENTITY))
597            sb.append("\n\tRequest length: ").append(reqContent.length).append(" bytes");
598
599         if (resd.isOneOf(HEADER, ENTITY))
600            sb.append("\n\tResponse code: ").append(status);
601
602         if (nn(resContent) && resd.isOneOf(HEADER, ENTITY))
603            sb.append("\n\tResponse length: ").append(resContent.length).append(" bytes");
604
605         if (nn(execTime) && resd.isOneOf(HEADER, ENTITY))
606            sb.append("\n\tExec time: ").append(execTime).append("ms");
607
608         if (reqd.isOneOf(HEADER, ENTITY)) {
609            var hh = req.getHeaderNames();
610            if (hh.hasMoreElements()) {
611               sb.append("\n---Request Headers---");
612               while (hh.hasMoreElements()) {
613                  var h = hh.nextElement();
614                  sb.append("\n\t").append(h).append(": ").append(req.getHeader(h));
615               }
616            }
617         }
618
619         if (resd.isOneOf(HEADER, ENTITY)) {
620            var hh = res.getHeaderNames();
621            if (hh.size() > 0) {
622               sb.append("\n---Response Headers---");
623               for (var h : hh) {
624                  sb.append("\n\t").append(h).append(": ").append(res.getHeader(h));
625               }
626            }
627         }
628
629         if (nn(reqContent) && reqContent.length > 0 && reqd == ENTITY) {
630            try {
631               sb.append("\n---Request Content UTF-8---");
632               sb.append("\n").append(new String(reqContent, UTF8));
633               sb.append("\n---Request Content Hex---");
634               sb.append("\n").append(toSpacedHex(reqContent));
635            } catch (Exception e1) {
636               sb.append("\n").append(lm(e1));
637            }
638         }
639
640         if (nn(resContent) && resContent.length > 0 && resd == ENTITY) {
641            try {
642               sb.append("\n---Response Content UTF-8---");
643               sb.append("\n").append(new String(resContent, UTF8));
644               sb.append("\n---Response Content Hex---");
645               sb.append("\n").append(toSpacedHex(resContent));
646            } catch (Exception e1) {
647               sb.append(lm(e1));
648            }
649         }
650         sb.append("\n=== END ======================================================================");
651      }
652
653      log(level, sb.toString(), e);
654
655   }
656
657   protected FluentMap<String,Object> properties() {
658      // @formatter:off
659      return filteredBeanPropertyMap()
660         .a("debugRules", debugRules)
661         .a("enabled", enabled)
662         .a("level", level)
663         .a("logger", logger)
664         .a("normalRules", normalRules)
665         .a("requestDetail", requestDetail)
666         .a("responseDetail", responseDetail)
667         .a("thrownStore", thrownStore);
668      // @formatter:on
669   }
670
671   @Override /* Overridden from Object */
672   public String toString() {
673      return r(properties());
674   }
675
676   private static byte[] getRequestContent(HttpServletRequest req) {
677      if (req instanceof CachingHttpServletRequest req2)
678         return req2.getContent();
679      return cast(byte[].class, req.getAttribute("RequestContent"));
680   }
681
682   private static byte[] getResponseContent(HttpServletRequest req, HttpServletResponse res) {
683      if (res instanceof CachingHttpServletResponse res2)
684         return res2.getContent();
685      return cast(byte[].class, req.getAttribute("ResponseContent"));
686   }
687
688   private ThrownStats getThrownStats(Throwable e) {
689      if (e == null || thrownStore == null)
690         return null;
691      return thrownStore.add(e);
692   }
693
694   /**
695    * Returns the logger to use for logging REST calls.
696    *
697    * <p>
698    * Returns the logger specified in the builder, or {@link Logger#getGlobal()} if it wasn't specified.
699    *
700    * <p>
701    * This method can be overridden in subclasses to provide a different logger.
702    *
703    * @return The logger to use for logging REST calls.
704    */
705   protected Logger getLogger() { return logger; }
706
707   /**
708    * Given the specified servlet request/response, find the rule that applies to it.
709    *
710    * <p>
711    * This method can be overridden to provide specialized logic for finding rules.
712    *
713    * @param req The servlet request.
714    * @param res The servlet response.
715    * @return The applicable logging rule, or the default rule if not found.  Never <jk>null</jk>.
716    */
717   protected CallLoggerRule getRule(HttpServletRequest req, HttpServletResponse res) {
718      for (var r : isDebug(req) ? debugRules : normalRules)
719         if (r.matches(req, res))
720            return r;
721      return DEFAULT_RULE;
722   }
723
724   /**
725    * Initializer.
726    * <p>
727    * Subclasses should override this method to make modifications to the builder used to create this logger.
728    *
729    * @param beanStore The bean store containing injectable beans for this logger.
730    * @return A new builder object.
731    */
732   protected Builder init(BeanStore beanStore) {
733      return new Builder(beanStore).logger(beanStore.getBean(Logger.class).orElse(null)).thrownStore(beanStore.getBean(ThrownStore.class).orElse(null));
734   }
735
736   /**
737    * Returns <jk>true</jk> if debug is enabled on this request.
738    *
739    * <p>
740    * Looks for the request attribute <js>"Debug"</js> to determine whether debug is enabled.
741    *
742    * <p>
743    * This method can be overridden to provide specialized logic for determining whether debug mode is enabled on a request.
744    *
745    * @param req The HTTP request being logged.
746    * @return <jk>true</jk> if debug is enabled on this request.
747    * @see org.apache.juneau.rest.RestContext.Builder#debugEnablement()
748    * @see Rest#debug()
749    * @see RestOp#debug()
750    */
751   protected boolean isDebug(HttpServletRequest req) {
752      return firstNonNull(cast(Boolean.class, req.getAttribute("Debug")), false);
753   }
754
755   /**
756    * Returns <jk>true</jk> if logging is enabled for this request.
757    *
758    * <p>
759    * Uses the enabled and enabled-test settings on the matched rule and this logger to determine whether a REST
760    * call should be logged.
761    *
762    * <p>
763    * This method can be overridden to provide specialized logic for determining whether a REST call should be logged.
764    *
765    * @param rule The first matching rule.  Never <jk>null</jk>.
766    * @param req The HTTP request.
767    * @return <jk>true</jk> if logging is enabled for this request.
768    */
769   protected boolean isEnabled(CallLoggerRule rule, HttpServletRequest req) {
770      var enabled = firstNonNull(rule.getEnabled(), this.enabled);
771      var enabledTest = firstNonNull(rule.getEnabledTest(), this.enabledTest);
772      return enabled.isEnabled(enabledTest.test(req));
773   }
774
775   /**
776    * Logs the specified message to the logger.
777    *
778    * <p>
779    * Subclasses can override this method to capture messages being sent to the logger and handle it differently.
780    *
781    * @param level The log level.
782    * @param msg The log message.
783    * @param e The exception.
784    */
785   protected void log(Level level, String msg, Throwable e) {
786      getLogger().log(level, msg, e);
787   }
788}