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