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 -> x >= 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 -> x >= 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> -> <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}