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 org.apache.juneau.collections.JsonMap.*; 020 021import java.util.function.*; 022import java.util.logging.*; 023 024import org.apache.juneau.*; 025import org.apache.juneau.cp.*; 026import org.apache.juneau.internal.*; 027 028import jakarta.servlet.http.*; 029 030/** 031 * Represents a logging rule used by {@link CallLogger}. 032 * 033 * <h5 class='section'>See Also:</h5><ul> 034 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerLoggingAndDebugging">Logging / Debugging</a> 035 * </ul> 036 */ 037public class CallLoggerRule { 038 039 //----------------------------------------------------------------------------------------------------------------- 040 // Static 041 //----------------------------------------------------------------------------------------------------------------- 042 043 /** 044 * Static creator. 045 * 046 * @param beanStore The bean store to use for creating beans. 047 * @return A new builder for this object. 048 */ 049 public static Builder create(BeanStore beanStore) { 050 return new Builder(beanStore); 051 } 052 053 //----------------------------------------------------------------------------------------------------------------- 054 // Builder 055 //----------------------------------------------------------------------------------------------------------------- 056 057 /** 058 * Builder class. 059 */ 060 public static class Builder extends BeanBuilder<CallLoggerRule> { 061 062 Predicate<Integer> statusFilter; 063 Predicate<HttpServletRequest> requestFilter; 064 Predicate<HttpServletResponse> responseFilter; 065 Predicate<Throwable> exceptionFilter; 066 Enablement enabled; 067 Predicate<HttpServletRequest> enabledTest; 068 Level level; 069 CallLoggingDetail requestDetail, responseDetail; 070 boolean logStackTrace; 071 072 /** 073 * Constructor. 074 * 075 * @param beanStore The bean store to use for creating beans. 076 */ 077 protected Builder(BeanStore beanStore) { 078 super(CallLoggerRule.class, beanStore); 079 } 080 081 @Override /* BeanBuilder */ 082 protected CallLoggerRule buildDefault() { 083 return new CallLoggerRule(this); 084 } 085 086 //------------------------------------------------------------------------------------------------------------- 087 // Properties 088 //------------------------------------------------------------------------------------------------------------- 089 090 /** 091 * Apply a status-based predicate check for this rule to match against. 092 * 093 * <h5 class='section'>Example:</h5> 094 * <p class='bjava'> 095 * <jc>// Create a logger rule that only matches if the status code is greater than or equal to 500.</jc> 096 * RestLogger 097 * .<jsm>createRule</jsm>() 098 * .statusFilter(<jv>x</jv> -> <jv>x</jv> >= 500) 099 * .build(); 100 * </p> 101 * 102 * @param value 103 * The predicate check, or <jk>null</jk> to not use any filtering based on status code. 104 * @return This object. 105 */ 106 public Builder statusFilter(Predicate<Integer> value) { 107 this.statusFilter = value; 108 return this; 109 } 110 111 /** 112 * Apply a throwable-based predicate check for this rule to match against. 113 * 114 * <h5 class='section'>Example:</h5> 115 * <p class='bjava'> 116 * <jc>// Create a logger rule that only matches if a NotFound exception was thrown.</jc> 117 * RestLogger 118 * .<jsm>createRule</jsm>() 119 * .exceptionFilter(<jv>x</jv> -> <jv>x</jv> <jk>instanceof</jk> NotFound) 120 * .build(); 121 * </p> 122 * 123 * <p> 124 * This check is only performed if an actual throwable was thrown. Therefore it's not necessary to perform a 125 * null check in the predicate. 126 * 127 * @param value 128 * The predicate check, or <jk>null</jk> to not use any filtering based on exceptions. 129 * @return This object. 130 */ 131 public Builder exceptionFilter(Predicate<Throwable> value) { 132 this.exceptionFilter = value; 133 return this; 134 } 135 136 /** 137 * Apply a request-based predicate check for this rule to match against. 138 * 139 * <h5 class='section'>Example:</h5> 140 * <p class='bjava'> 141 * <jc>// Create a logger rule that only matches if the servlet path contains "foobar".</jc> 142 * RestLogger 143 * .<jsm>createRule</jsm>() 144 * .requestFilter(<jv>x</jv> -> <jv>x</jv>.getServletPath().contains(<js>"foobar"</js>)) 145 * .build(); 146 * </p> 147 * 148 * @param value 149 * The predicate check, or <jk>null</jk> to not use any filtering based on the request. 150 * @return This object. 151 */ 152 public Builder requestFilter(Predicate<HttpServletRequest> value) { 153 this.requestFilter = value; 154 return this; 155 } 156 157 /** 158 * Apply a response-based predicate check for this rule to match against. 159 * 160 * <h5 class='section'>Example:</h5> 161 * <p class='bjava'> 162 * <jc>// Create a logger rule that only matches if the servlet path contains "foobar".</jc> 163 * RestLogger 164 * .<jsm>createRule</jsm>() 165 * .responseFilter(<jv>x</jv> -> <jv>x</jv>.getStatus() >= 500) 166 * .build(); 167 * </p> 168 * 169 * <p> 170 * Note that the {@link #statusFilter(Predicate)} and {@link #exceptionFilter(Predicate)} methods are simply 171 * convenience response filters. 172 * 173 * @param value 174 * The predicate check, or <jk>null</jk> to not use any filtering based on the response. 175 * @return This object. 176 */ 177 public Builder responseFilter(Predicate<HttpServletResponse> value) { 178 this.responseFilter = value; 179 return this; 180 } 181 182 /** 183 * Specifies whether logging is enabled when using this rule. 184 * 185 * <h5 class='section'>Example:</h5> 186 * <p class='bjava'> 187 * <jc>// Create a logger rule where logging is only enabled if the query string contains "foobar".</jc> 188 * RestLogger 189 * .<jsm>createRule</jsm>() 190 * .enabled(<jsf>CONDITIONALLY</jsf>) 191 * .enabledPredicate(<jv>x</jv> -> <jv>x</jv>.getQueryString().contains(<js>"foobar"</js>)) 192 * .build(); 193 * </p> 194 * 195 * <ul class='values'> 196 * <li>{@link Enablement#ALWAYS ALWAYS} - Logging is enabled. 197 * <li>{@link Enablement#NEVER NEVER} - Logging is disabled. 198 * <li>{@link Enablement#CONDITIONAL CONDITIONALLY} - Logging is enabled if it passes the {@link #enabledPredicate(Predicate)} test. 199 * </ul> 200 * 201 * @param value 202 * The enablement flag value, or <jk>null</jk> to inherit from the call logger whose default value is {@link Enablement#ALWAYS ALWAYS} 203 * unless overridden via a <js>"juneau.restCallLogger.enabled"</js> system property or <js>"JUNEAU_RESTCALLLOGGER_ENABLED"</js> environment variable. 204 * @return This object. 205 */ 206 public Builder enabled(Enablement value) { 207 this.enabled = value; 208 return this; 209 } 210 211 /** 212 * Specifies the predicate test to use when the enabled setting is set to {@link Enablement#CONDITIONAL CONDITIONALLY}. 213 * 214 * <p> 215 * This setting has no effect if the enablement value is not {@link Enablement#CONDITIONAL CONDITIONALLY}. 216 * 217 * <h5 class='section'>Example:</h5> 218 * <p class='bjava'> 219 * <jc>// Create a logger rule where logging is only enabled if the query string contains "foobar".</jc> 220 * RestLogger 221 * .<jsm>createRule</jsm>() 222 * .enabled(<jsf>CONDITIONALLY</jsf>) 223 * .enabledPredicate(<jv>x</jv> -> <jv>x</jv>.getQueryString().contains(<js>"foobar"</js>)) 224 * .build(); 225 * </p> 226 * 227 * @param value 228 * The enablement predicate test, or <jk>null</jk> to inherit from the call logger whose default value is <c><jv>x</jv> -> <jk>false</jk></c>. 229 * @return This object. 230 */ 231 public Builder enabledPredicate(Predicate<HttpServletRequest> value) { 232 this.enabledTest = value; 233 return this; 234 } 235 236 /** 237 * Shortcut for calling <c>enabled(<jsf>NEVER</jsf>)</c>. 238 * 239 * @return This object. 240 */ 241 public Builder disabled() { 242 return this.enabled(Enablement.NEVER); 243 } 244 245 /** 246 * The level of detail to log on a request. 247 * 248 * <ul class='values'> 249 * <li>{@link CallLoggingDetail#STATUS_LINE STATUS_LINE} - Log only the status line. 250 * <li>{@link CallLoggingDetail#HEADER HEADER} - Log the status line and headers. 251 * <li>{@link CallLoggingDetail#ENTITY ENTITY} - Log the status line and headers and content if available. 252 * </ul> 253 * 254 * @param value 255 * The new value for this property, or <jk>null</jk> to inherit from the call logger. 256 * @return This object. 257 */ 258 public Builder requestDetail(CallLoggingDetail value) { 259 this.requestDetail = value; 260 return this; 261 } 262 263 /** 264 * The level of detail to log on a response. 265 * 266 * <ul class='values'> 267 * <li>{@link CallLoggingDetail#STATUS_LINE STATUS_LINE} - Log only the status line. 268 * <li>{@link CallLoggingDetail#HEADER HEADER} - Log the status line and headers. 269 * <li>{@link CallLoggingDetail#ENTITY ENTITY} - Log the status line and headers and content if available. 270 * </ul> 271 * 272 * @param value 273 * The new value for this property, or <jk>null</jk> to inherit from the call logger. 274 * @return This object. 275 */ 276 public Builder responseDetail(CallLoggingDetail value) { 277 this.responseDetail = value; 278 return this; 279 } 280 281 /** 282 * The logging level to use for logging the request/response. 283 * 284 * <p> 285 * The default value is {@link Level#INFO}. 286 * 287 * @param value 288 * The new value for this property, or <jk>null</jk> to inherit from the call logger. 289 * @return This object. 290 */ 291 public Builder level(Level value) { 292 this.level = value; 293 return this; 294 } 295 296 /** 297 * Log a stack trace as part of the log entry. 298 * 299 * <p> 300 * The default value is <jk>false</jk>. 301 * 302 * @return This object. 303 */ 304 public Builder logStackTrace() { 305 this.logStackTrace = true; 306 return this; 307 } 308 @Override /* Overridden from BeanBuilder */ 309 public Builder impl(Object value) { 310 super.impl(value); 311 return this; 312 } 313 314 @Override /* Overridden from BeanBuilder */ 315 public Builder type(Class<?> value) { 316 super.type(value); 317 return this; 318 } 319 } 320 321 //----------------------------------------------------------------------------------------------------------------- 322 // Instance 323 //----------------------------------------------------------------------------------------------------------------- 324 325 private final Predicate<Integer> statusFilter; 326 private final Predicate<HttpServletRequest> requestFilter; 327 private final Predicate<HttpServletResponse> responseFilter; 328 private final Predicate<Throwable> exceptionFilter; 329 private final Level level; 330 private final Enablement enabled; 331 private final Predicate<HttpServletRequest> enabledTest; 332 private final CallLoggingDetail requestDetail, responseDetail; 333 334 /** 335 * Constructor. 336 * 337 * @param b Builder 338 */ 339 CallLoggerRule(Builder b) { 340 this.statusFilter = b.statusFilter; 341 this.exceptionFilter = b.exceptionFilter; 342 this.requestFilter = b.requestFilter; 343 this.responseFilter = b.responseFilter; 344 this.level = b.level; 345 this.enabled = b.enabled; 346 this.enabledTest = b.enabledTest; 347 this.requestDetail = b.requestDetail; 348 this.responseDetail = b.responseDetail; 349 } 350 351 /** 352 * Returns <jk>true</jk> if this rule matches the specified parameters. 353 * 354 * @param req The HTTP request being logged. Never <jk>null</jk>. 355 * @param res The HTTP response being logged. Never <jk>null</jk>. 356 * @return <jk>true</jk> if this rule matches the specified parameters. 357 */ 358 public boolean matches(HttpServletRequest req, HttpServletResponse res) { 359 360 if ((requestFilter != null && ! requestFilter.test(req)) || (responseFilter != null && ! responseFilter.test(res))) 361 return false; 362 363 if (statusFilter != null && ! statusFilter.test(res.getStatus())) 364 return false; 365 366 Throwable e = (Throwable)req.getAttribute("Exception"); 367 if (e != null && exceptionFilter != null && ! exceptionFilter.test(e)) 368 return false; 369 370 return true; 371 } 372 373 /** 374 * Returns the detail level for HTTP requests. 375 * 376 * @return the detail level for HTTP requests, or <jk>null</jk> if it's not set. 377 */ 378 public CallLoggingDetail getRequestDetail() { 379 return requestDetail; 380 } 381 382 /** 383 * Returns the detail level for HTTP responses. 384 * 385 * @return the detail level for HTTP responses, or <jk>null</jk> if it's not set. 386 */ 387 public CallLoggingDetail getResponseDetail() { 388 return responseDetail; 389 } 390 391 /** 392 * Returns the log level on this rule. 393 * 394 * @return The log level on this rule, or <jk>null</jk> if it's not set. 395 */ 396 public Level getLevel() { 397 return level; 398 } 399 400 /** 401 * Returns the enablement flag value on this rule. 402 * 403 * @return The enablement flag value on this rule, or <jk>null</jk> if it's not set. 404 */ 405 public Enablement getEnabled() { 406 return enabled; 407 } 408 409 /** 410 * Returns the enablement predicate test on this rule. 411 * 412 * @return The enablement predicate test on this rule, or <jk>null</jk> if it's not set. 413 */ 414 public Predicate<HttpServletRequest> getEnabledTest() { 415 return enabledTest; 416 } 417 418 @Override /* Object */ 419 public String toString() { 420 return filteredMap() 421 .append("codeFilter", statusFilter) 422 .append("exceptionFilter", exceptionFilter) 423 .append("requestFilter", requestFilter) 424 .append("responseFilter", responseFilter) 425 .append("level", level) 426 .append("requestDetail", requestDetail) 427 .append("responseDetail", responseDetail) 428 .append("enabled", enabled) 429 .append("enabledTest", enabledTest) 430 .asReadableString(); 431 } 432}