blob: d09a462609395edb56aef512fe26e6739834bc69 [file] [log] [blame]
// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file *
// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance *
// * with the License. You may obtain a copy of the License at *
// * *
// * http://www.apache.org/licenses/LICENSE-2.0 *
// * *
// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an *
// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the *
// * specific language governing permissions and limitations under the License. *
// ***************************************************************************************************************************
package org.apache.juneau.rest;
import static org.apache.juneau.internal.StringUtils.*;
import java.util.*;
import java.util.logging.*;
import javax.servlet.http.*;
import org.apache.juneau.internal.*;
import org.apache.juneau.parser.*;
/**
* Represents a set of logging rules for how to handle logging of HTTP requests/responses.
*/
public class RestCallLoggerConfig {
private final RestCallLoggerRule[] rules;
private final boolean disabled, debugAlways, debugPerRequest, noTraceAlways, noTracePerRequest, useStackTraceHashing;
private final int stackTraceHashingTimeout;
private final Level level;
RestCallLoggerConfig(Builder b) {
RestCallLoggerConfig p = b.parent;
this.disabled = bool(b.disabled, p == null ? false : p.disabled);
this.debugAlways = always(b.debug, p == null ? false : p.debugAlways);
this.debugPerRequest = perRequest(b.debug, p == null ? false : p.debugPerRequest);
this.noTraceAlways = always(b.noTrace, p == null ? false : p.noTraceAlways);
this.noTracePerRequest = perRequest(b.noTrace, p == null ? false : p.noTracePerRequest);
this.useStackTraceHashing = boolOrNum(b.stackTraceHashing, p == null ? false : p.useStackTraceHashing);
this.stackTraceHashingTimeout = number(b.stackTraceHashing, p == null ? Integer.MAX_VALUE : p.stackTraceHashingTimeout);
this.level = level(b.level, p == null ? Level.INFO : p.level);
ArrayList<RestCallLoggerRule> rules = new ArrayList<>();
if (p != null)
rules.addAll(Arrays.asList(p.rules));
rules.addAll(b.rules);
this.rules = rules.toArray(new RestCallLoggerRule[rules.size()]);
}
private boolean always(String s, boolean def) {
if (s == null)
return def;
return "always".equalsIgnoreCase(s);
}
private boolean perRequest(String s, boolean def) {
if (s == null)
return def;
return "per-request".equalsIgnoreCase(s);
}
private boolean bool(String s, boolean def) {
if (s == null)
return def;
return "true".equalsIgnoreCase(s);
}
private boolean boolOrNum(String s, boolean def) {
if (s == null)
return def;
return "true".equalsIgnoreCase(s) || isNumeric(s);
}
private int number(String s, int def) {
if (StringUtils.isNumeric(s)) {
try {
return (Integer)parseNumber(s, Integer.class);
} catch (ParseException e) { /* Should never happen */ }
}
return def;
}
private Level level(String s, Level def) {
if (s == null)
return def;
return Level.parse(s);
}
/**
* Creates a builder for this class.
*
* @return A new builder for this class.
*/
public static Builder create() {
return new Builder();
}
/**
* Builder for {@link RestCallLoggerConfig} objects.
*/
public static class Builder {
List<RestCallLoggerRule> rules = new ArrayList<>();
RestCallLoggerConfig parent;
String stackTraceHashing, debug, noTrace, disabled, level;
/**
* Sets the parent logging config.
*
* @param parent The parent logging config.
* @return This object (for method chaining).
*/
public Builder parent(RestCallLoggerConfig parent) {
this.parent = parent;
return this;
}
/**
* Adds a new logging rule to this config.
*
* @param rule The logging rule to add to this config.
* @return This object (for method chaining).
*/
public Builder rule(RestCallLoggerRule rule) {
this.rules.add(rule);
return this;
}
/**
* Adds new logging rules to this config.
*
* @param rules The logging rules to add to this config.
* @return This object (for method chaining).
*/
public Builder rules(RestCallLoggerRule...rules) {
for (RestCallLoggerRule rule : rules)
this.rules.add(rule);
return this;
}
/**
* Enables debug mode on this config.
*
* <p>
* Debug mode causes the HTTP bodies to be cached in memory so that they can be logged.
*
* <p>
* Possible values (case-insensitive):
* <ul>
* <li><js>"always"</js> - Debug mode enabled for all requests.
* <li><js>"never"</js> - Debug mode disabled for all requests.
* <li><js>"per-request"</js> - Debug mode enabled for requests that have a <js>"X-Debug: true"</js> header.
* </ul>
*
* @param value The value for this property.
* @return This object (for method chaining).
*/
public Builder debug(String value) {
this.debug = value;
return this;
}
/**
* Shortcut for calling <c>debug(<js>"always"</js>);</c>.
*
* @return This object (for method chaining).
*/
public Builder debugAlways() {
this.debug = "always";
return this;
}
/**
* Shortcut for calling <c>debug(<js>"per-request"</js>);</c>.
*
* @return This object (for method chaining).
*/
public Builder debugPerRequest() {
this.debug = "per-request";
return this;
}
/**
* Enables no-trace mode on this config.
*
* <p>
* No-trace mode prevents logging of messages to the log file.
*
* <p>
* Possible values (case-insensitive):
* <ul>
* <li><js>"always"</js> - No-trace mode enabled for all requests.
* <li><js>"never"</js> - No-trace mode disabled for all requests.
* <li><js>"per-request"</js> - No-trace mode enabled for requests that have a <js>"X-NoTrace: true"</js> header.
* </ul>
*
* @param value The value for this property.
* @return This object (for method chaining).
*/
public Builder noTrace(String value) {
this.noTrace = value;
return this;
}
/**
* Shortcut for calling <c>noTrace(<js>"always"</js>);</c>.
*
* @return This object (for method chaining).
*/
public Builder noTraceAlways() {
this.noTrace = "always";
return this;
}
/**
* Shortcut for calling <c>noTrace(<js>"per-request"</js>);</c>.
*
* @return This object (for method chaining).
*/
public Builder noTracePerRequest() {
this.noTrace = "per-request";
return this;
}
/**
* Enables the use of stacktrace hashing.
*
* <p>
* When enabled, stacktraces will be replaced with hashes in the log file.
*
* <p>
* Possible values (case-insensitive):
* <ul>
* <li><js>"true"</js> - Stacktrace-hash mode enabled for all requests.
* <li><js>"false"</js> - Stacktrace-hash mode disabled for all requests.
* <li>Numeric value - Same as <js>"true"</js> but identifies a time in milliseconds during which stack traces
* should be hashed before starting over.
* <br>Useful if you cycle your log files and want to make sure stack traces are logged at least one per day
* (for example).
* </ul>
*
* @param value The value for this property.
* @return This object (for method chaining).
*/
public Builder stackTraceHashing(String value) {
this.stackTraceHashing = value;
return this;
}
/**
* Shortcut for calling <c>stackTraceHashing(<js>"true"</js>);</c>.
*
* @return This object (for method chaining).
*/
public Builder stackTraceHashing() {
this.stackTraceHashing = "true";
return this;
}
/**
* Shortcut for calling <c>stackTraceHashing(String.<jsm>valueOf</jsm>(timeout));</c>.
*
* @param timeout Time in milliseconds to hash stack traces for.
* @return This object (for method chaining).
*/
public Builder stackTraceHashingTimeout(int timeout) {
this.stackTraceHashing = String.valueOf(timeout);
return this;
}
/**
* Disable all logging.
*
* <p>
* This is equivalent to <c>noTrace(<js>"always"</js>)</c> and provided for convenience.
*
* <p>
* Possible values (case-insensitive):
* <ul>
* <li><js>"true"</js> - Stacktrace-hash mode enabled for all requests.
* <li><js>"false"</js> - Stacktrace-hash mode disabled for all requests.
* <li>Numeric value - Same as <js>"true"</js> but identifies a time in milliseconds during which stack traces
* should be hashed before starting over.
* <br>Useful if you cycle your log files and want to make sure stack traces are logged at least one per day
* (for example).
* </ul>
*
* @param value The value for this property.
* @return This object (for method chaining).
*/
public Builder disabled(String value) {
this.disabled = value;
return this;
}
/**
* Shortcut for calling <c>disabled(<js>"true"</js>);</c>.
*
* @return This object (for method chaining).
*/
public Builder disabled() {
this.disabled = "true";
return this;
}
/**
* The default logging level.
*
* <p>
* This defines the logging level for messages if they're not already defined on the matched rule.
*
* <p>
* If not specified, <js>"INFO"</js> is used.
*
* <p>
* See {@link Level} for possible values.
*
* @param value The value for this property.
* @return This object (for method chaining).
*/
public Builder level(String value) {
this.level = value;
return this;
}
/**
* The default logging level.
*
* <p>
* This defines the logging level for messages if they're not already defined on the matched rule.
*
* <p>
* If not specified, <js>"INFO"</js> is used.
*
* <p>
* See {@link Level} for possible values.
*
* @param value The value for this property.
* @return This object (for method chaining).
*/
public Builder level(Level value) {
this.level = value == null ? null : value.getName();
return this;
}
/**
* Creates the {@link RestCallLoggerConfig} object based on settings on this builder.
*
* @return A new {@link RestCallLoggerConfig} object.
*/
public RestCallLoggerConfig build() {
return new RestCallLoggerConfig(this);
}
}
/**
* Given the specified servlet request/response, find the rule that applies to it.
*
* @param req The servlet request.
* @param res The servlet response.
* @return The applicable logging rule, or <jk>null<jk> if a match could not be found.
*/
public RestCallLoggerRule getRule(HttpServletRequest req, HttpServletResponse res) {
if (isNoTrace(req))
return null;
int status = res.getStatus();
Throwable e = (Throwable)req.getAttribute("Exception");
boolean debug = isDebug(req);
for (RestCallLoggerRule r : rules)
if (r.matches(status, debug, e))
return r;
return null;
}
/**
* Returns <jk>true</jk> if the current request has debug enabled.
*
* @param req The request to check.
* @return <jk>true</jk> if the current request has debug enabled.
*/
public boolean isDebug(HttpServletRequest req) {
if (debugAlways)
return true;
if (debugPerRequest) {
if ("true".equalsIgnoreCase(req.getHeader("X-Debug")))
return true;
Boolean b = boolAttr(req, "Debug");
if (b != null && b == true)
return true;
}
return false;
}
private boolean isNoTrace(HttpServletRequest req) {
if (disabled || noTraceAlways)
return true;
if (noTracePerRequest) {
if ("true".equalsIgnoreCase(req.getHeader("X-NoTrace")))
return true;
Boolean b = boolAttr(req, "NoTrace");
if (b != null && b == true)
return true;
}
return false;
}
/**
* Returns the default logging level.
*
* @return The default logging level.
*/
public Level getLevel() {
return level;
}
/**
* Returns <jk>true</jk> if stack traces should be hashed.
*
* @return <jk>true</jk> if stack traces should be hashed.
*/
public boolean useStackTraceHashing() {
return useStackTraceHashing;
}
/**
* Returns the time in milliseconds that stacktrace hashes should be persisted.
*
* @return The time in milliseconds that stacktrace hashes should be persisted.
*/
public int getStackTraceHashingTimeout() {
return stackTraceHashingTimeout;
}
private Boolean boolAttr(HttpServletRequest req, String name) {
Object o = req.getAttribute(name);
if (o == null || ! (o instanceof Boolean))
return null;
return (Boolean)o;
}
}