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