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