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