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