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> -&gt; <jv>x</jv> &gt;= 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> -&gt; <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> -&gt; <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> -&gt; <jv>x</jv>.getStatus() &gt;= 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> -&gt; <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> -&gt; <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> -&gt; <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}