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