This is an automated email from the ASF dual-hosted git repository.
jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/master by this push:
new 210725e REST refactoring.
210725e is described below
commit 210725ebc14a727351673a5656bc8e41bf4870f1
Author: JamesBognar <[email protected]>
AuthorDate: Mon Feb 1 15:50:21 2021 -0500
REST refactoring.
---
.../java/org/apache/juneau/rest/RestContext.java | 36 ++-
.../apache/juneau/rest/SwaggerProviderBuilder.java | 22 +-
.../rest/logging/BasicDisabledRestLogger.java | 11 +-
.../juneau/rest/logging/BasicRestLogger.java | 352 +++++++++++++++++---
.../rest/logging/BasicTestCaptureRestLogger.java | 33 +-
.../juneau/rest/logging/BasicTestRestLogger.java | 10 +-
.../org/apache/juneau/rest/logging/RestLogger.java | 357 +--------------------
.../juneau/rest/logging/RestLoggerBuilder.java | 66 ++--
.../juneau/rest/logging/RestLoggerRuleBuilder.java | 2 +-
9 files changed, 446 insertions(+), 443 deletions(-)
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
index 699b333..49e9b9a 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
@@ -3925,14 +3925,18 @@ public class RestContext extends BeanContext {
if (resource instanceof RestLogger)
x = (RestLogger)resource;
- if (x == null)
- x = getInstanceProperty(REST_callLogger,
RestLogger.class, null, beanFactory);
+ Object o = getProperty(REST_callLogger);
+ if (o instanceof RestLogger)
+ x = (RestLogger)o;
if (x == null)
x = beanFactory.getBean(RestLogger.class).orElse(null);
- if (x == null)
- x = getInstanceProperty(REST_callLoggerDefault,
RestLogger.class, null, beanFactory);
+ if (x == null) {
+ o = getProperty(REST_callLoggerDefault);
+ if (o instanceof RestLogger)
+ x = (RestLogger)o;
+ }
if (x == null)
x = createCallLoggerBuilder(resource,
beanFactory).build();
@@ -3959,18 +3963,36 @@ public class RestContext extends BeanContext {
* @return The call logger builder for this REST resource.
* @throws Exception If call logger builder could not be instantiated.
*/
+ @SuppressWarnings("unchecked")
protected RestLoggerBuilder createCallLoggerBuilder(Object resource,
BeanFactory beanFactory) throws Exception {
+ Class<? extends RestLogger> c = null;
+
+ Object o = getProperty(REST_callLogger);
+ if (o instanceof Class)
+ c = (Class<? extends RestLogger>)o;
+
+ if (c == null) {
+ o = getProperty(REST_callLoggerDefault);
+ if (o instanceof Class)
+ c = (Class<? extends RestLogger>)o;
+ }
+
+ if (c == null)
+ c = BasicRestLogger.class;
+
RestLoggerBuilder x = RestLogger
.create()
+ .beanFactory(beanFactory)
+ .implClass(c)
.normalRules( // Rules when debugging is not enabled.
- RestLogger.createRule() // Log 500+ errors
with status-line and header information.
+ RestLoggerRule.create() // Log 500+ errors
with status-line and header information.
.statusFilter(a -> a >= 500)
.level(SEVERE)
.requestDetail(HEADER)
.responseDetail(HEADER)
.build(),
- RestLogger.createRule() // Log 400-500 errors
with just status-line information.
+ RestLoggerRule.create() // Log 400-500 errors
with just status-line information.
.statusFilter(a -> a >= 400)
.level(WARNING)
.requestDetail(STATUS_LINE)
@@ -3978,7 +4000,7 @@ public class RestContext extends BeanContext {
.build()
)
.debugRules( // Rules when debugging is enabled.
- RestLogger.createRule() // Log everything with
full details.
+ RestLoggerRule.create() // Log everything with
full details.
.level(SEVERE)
.requestDetail(ENTITY)
.responseDetail(ENTITY)
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/SwaggerProviderBuilder.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/SwaggerProviderBuilder.java
index d99693b..76c801c 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/SwaggerProviderBuilder.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/SwaggerProviderBuilder.java
@@ -73,6 +73,17 @@ public class SwaggerProviderBuilder {
}
/**
+ * Specifies a subclass of {@link SwaggerProvider} to create when the
{@link #build()} method is called.
+ *
+ * @param value The new value for this setting.
+ * @return This object (for method chaining).
+ */
+ public SwaggerProviderBuilder implClass(Class<? extends
SwaggerProvider> value) {
+ this.implClass = value;
+ return this;
+ }
+
+ /**
* Specifies the variable resolver to use for the {@link
SwaggerProvider} object.
*
* @param value The new value for this setting.
@@ -115,15 +126,4 @@ public class SwaggerProviderBuilder {
this.fileFinder = value;
return this;
}
-
- /**
- * Specifies a subclass of {@link SwaggerProvider} to create when the
{@link #build()} method is called.
- *
- * @param value The new value for this setting.
- * @return This object (for method chaining).
- */
- public SwaggerProviderBuilder implClass(Class<? extends
SwaggerProvider> value) {
- this.implClass = value;
- return this;
- }
}
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logging/BasicDisabledRestLogger.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logging/BasicDisabledRestLogger.java
index 4a83481..612c4e1 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logging/BasicDisabledRestLogger.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logging/BasicDisabledRestLogger.java
@@ -12,8 +12,6 @@
//
***************************************************************************************************************************
package org.apache.juneau.rest.logging;
-import java.util.function.*;
-
import org.apache.juneau.rest.*;
/**
@@ -23,12 +21,7 @@ import org.apache.juneau.rest.*;
* <li class='link'>{@doc RestLoggingAndDebugging}
* </ul>
*/
-public class BasicDisabledRestLogger extends RestLogger {
-
- /**
- * Returns a builder with the settings used by this logger.
- */
- public static final Supplier<RestLoggerBuilder> SETTINGS =
()->create().disabled();
+public class BasicDisabledRestLogger extends BasicRestLogger {
/**
* Constructor.
@@ -36,6 +29,6 @@ public class BasicDisabledRestLogger extends RestLogger {
* @param context The context of the resource object.
*/
public BasicDisabledRestLogger(RestContext context) {
- super(create().disabled());
+ super(RestLogger.create().disabled());
}
}
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logging/BasicRestLogger.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logging/BasicRestLogger.java
index fb88866..9290efc 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logging/BasicRestLogger.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logging/BasicRestLogger.java
@@ -12,24 +12,50 @@
//
***************************************************************************************************************************
package org.apache.juneau.rest.logging;
+import static org.apache.juneau.internal.ObjectUtils.*;
+import static org.apache.juneau.internal.StringUtils.*;
import static org.apache.juneau.rest.logging.RestLoggingDetail.*;
+import static org.apache.juneau.Enablement.*;
+import static org.apache.juneau.SystemProperties.*;
+import static java.util.logging.Level.*;
+import java.util.*;
import java.util.function.*;
+import java.util.logging.*;
-import static java.util.logging.Level.*;
+import javax.servlet.http.*;
+import org.apache.juneau.*;
+import org.apache.juneau.collections.*;
+import org.apache.juneau.internal.*;
+import org.apache.juneau.json.*;
import org.apache.juneau.rest.*;
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.util.*;
+import org.apache.juneau.utils.*;
/**
- * Default implementation of a {@link RestLogger} with typically handling.
+ * Basic implementation of a {@link RestLogger} for logging HTTP requests.
+ *
+ * <p>
+ * Provides the following capabilities:
+ * <ul>
+ * <li>Allows incoming HTTP requests to be logged at various {@link
Enablement detail levels}.
+ * <li>Allows rules to be defined to handle request logging differently
depending on the resulting status code.
+ * <li>Allows use of stack trace hashing to eliminate duplication of stack
traces in log files.
+ * <li>Allows customization of handling of where requests are logged to.
+ * <li>Allows configuration via system properties or environment variables.
+ * </ul>
*
* <p>
- * Uses the following builder settings:
+ * The following is an example of a logger that logs errors only when
debugging is not enabled, and everything when
+ * logging is enabled.
+ *
+ * <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
- * RestLogger
+ * RestLogger <jv>logger</jv> = RestLogger
* .<jsm>create</jsm>()
- * .logger(<jv>context</jv>.getLogger()) <jc>// Use
logger registered on REST context.</jc>
- * .stackTraceStore(<jv>context</jv>.getStackTraceStore())
<jc>// Use stack trace store registered on REST context.</jc>
+ * .logger(<js>"MyLogger"</js>) <jc>// Use MyLogger Java
logger.</jc>
* .normalRules( <jc>// Rules when debugging is not
enabled.</jc>
* <jsm>createRule</jsm>() <jc>// Log 500+ errors
with status-line and header information.</jc>
* .statusFilter(x -> x >= 500)
@@ -51,52 +77,306 @@ import org.apache.juneau.rest.*;
* .responseDetail(<jsf>ENTITY</jsf>)
* .build()
* )
+ * .build()
* ;
* </p>
*
* <ul class='seealso'>
+ * <li class='jf'>{@link RestContext#REST_callLogger}
+ * <li class='jf'>{@link RestContext#REST_callLoggerDefault}
+ * <li class='jf'>{@link RestContext#REST_debug}
+ * <li class='jf'>{@link RestContext#REST_debugOn}
+ * <li class='ja'>{@link Rest#debug}
+ * <li class='ja'>{@link RestOp#debug}
* <li class='link'>{@doc RestLoggingAndDebugging}
* </ul>
*/
-public class BasicRestLogger extends RestLogger {
+public class BasicRestLogger implements RestLogger {
+
+ private static final RestLoggerRule DEFAULT_RULE =
RestLoggerRule.create().build();
+
+ private final Logger logger;
+ private final StackTraceStore stackTraceStore;
+ private final RestLoggerRule[] normalRules, debugRules;
+ private final Enablement enabled;
+ private final Predicate<HttpServletRequest> enabledTest;
+ private final Level level;
+ private final RestLoggingDetail requestDetail, responseDetail;
/**
- * Returns a builder with the settings used by this logger.
+ * Constructor.
+ *
+ * @param builder The builder object.
*/
- public static final Supplier<RestLoggerBuilder> SETTINGS =
()->builder();
+ public BasicRestLogger(RestLoggerBuilder builder) {
+ this.logger = firstNonNull(builder.logger,
Logger.getLogger(getProperty(String.class, SP_logger, "global")));
+ this.stackTraceStore = builder.stackTraceDatabase;
+ this.normalRules = builder.normalRules.toArray(new
RestLoggerRule[builder.normalRules.size()]);
+ this.debugRules = builder.debugRules.toArray(new
RestLoggerRule[builder.debugRules.size()]);
+ this.enabled = firstNonNull(builder.enabled,
getProperty(Enablement.class, SP_enabled, ALWAYS));
+ this.enabledTest = firstNonNull(builder.enabledTest, x ->
false);
+ this.requestDetail = firstNonNull(builder.requestDetail,
getProperty(RestLoggingDetail.class, SP_requestDetail, STATUS_LINE));
+ this.responseDetail = firstNonNull(builder.responseDetail,
getProperty(RestLoggingDetail.class, SP_responseDetail, STATUS_LINE));
+ this.level = firstNonNull(builder.level,
getProperty(Level.class, SP_level, OFF));
+ }
/**
- * Constructor.
+ * Called at the end of a servlet request to log the request.
*
- * @param context The context of the resource object.
+ * @param req The servlet request.
+ * @param res The servlet response.
*/
- public BasicRestLogger(RestContext context) {
-
super(builder().logger(context.getLogger()).stackTraceStore(context.getStackTraceStore()));
+ @Override /* RestLogger */
+ public void log(HttpServletRequest req, HttpServletResponse res) {
+
+ RestLoggerRule rule = getRule(req, res);
+
+ if (! isEnabled(rule, req))
+ return;
+
+ Level level = firstNonNull(rule.getLevel(), this.level);
+
+ if (level == Level.OFF)
+ return;
+
+ Throwable e = castOrNull(req.getAttribute("Exception"),
Throwable.class);
+ Long execTime = castOrNull(req.getAttribute("ExecTime"),
Long.class);
+
+ RestLoggingDetail reqd = firstNonNull(rule.getRequestDetail(),
requestDetail);
+ RestLoggingDetail resd = firstNonNull(rule.getResponseDetail(),
responseDetail);
+
+ String method = req.getMethod();
+ int status = res.getStatus();
+ String uri = req.getRequestURI();
+ byte[] reqBody = getRequestBody(req);
+ byte[] resBody = getResponseBody(req, res);
+
+ StringBuilder sb = new StringBuilder();
+
+ if (reqd != STATUS_LINE || resd != STATUS_LINE)
+ sb.append("\n=== HTTP Call (incoming)
======================================================\n");
+
+ StackTraceInfo sti = getStackTraceInfo(e);
+
+ sb.append('[').append(status);
+
+ if (sti != null) {
+ int count = sti.getCount();
+
sb.append(',').append(sti.getHash()).append('.').append(count);
+ if (count > 1)
+ e = null;
+ }
+
+ sb.append("] ");
+
+ sb.append("HTTP ").append(method).append(' ').append(uri);
+
+ if (reqd != STATUS_LINE || resd != STATUS_LINE) {
+
+ if (reqd.isOneOf(HEADER, ENTITY)) {
+ String qs = req.getQueryString();
+ if (qs != null)
+ sb.append('?').append(qs);
+ }
+
+ if (reqBody != null && reqd.isOneOf(HEADER ,ENTITY))
+ sb.append("\n\tRequest length:
").append(reqBody.length).append(" bytes");
+
+ if (resd.isOneOf(HEADER, ENTITY))
+ sb.append("\n\tResponse code: ").append(status);
+
+ if (resBody != null && resd.isOneOf(HEADER, ENTITY))
+ sb.append("\n\tResponse length:
").append(resBody.length).append(" bytes");
+
+ if (execTime != null && resd.isOneOf(HEADER, ENTITY))
+ sb.append("\n\tExec time:
").append(execTime).append("ms");
+
+ if (reqd.isOneOf(HEADER, ENTITY)) {
+ Enumeration<String> hh = req.getHeaderNames();
+ if (hh.hasMoreElements()) {
+ sb.append("\n---Request Headers---");
+ while (hh.hasMoreElements()) {
+ String h = hh.nextElement();
+
sb.append("\n\t").append(h).append(": ").append(req.getHeader(h));
+ }
+ }
+ }
+
+ if (resd.isOneOf(HEADER, ENTITY)) {
+ Collection<String> hh = res.getHeaderNames();
+ if (hh.size() > 0) {
+ sb.append("\n---Response Headers---");
+ for (String h : hh) {
+
sb.append("\n\t").append(h).append(": ").append(res.getHeader(h));
+ }
+ }
+ }
+
+ if (reqBody != null && reqBody.length > 0 && reqd ==
ENTITY) {
+ try {
+ sb.append("\n---Request Body UTF-8---");
+ sb.append("\n").append(new
String(reqBody, IOUtils.UTF8));
+ sb.append("\n---Request Body Hex---");
+
sb.append("\n").append(toSpacedHex(reqBody));
+ } catch (Exception e1) {
+
sb.append("\n").append(e1.getLocalizedMessage());
+ }
+ }
+
+ if (resBody != null && resBody.length > 0 && resd ==
ENTITY) {
+ try {
+ sb.append("\n---Response Body
UTF-8---");
+ sb.append("\n").append(new
String(resBody, IOUtils.UTF8));
+ sb.append("\n---Response Body Hex---");
+
sb.append("\n").append(toSpacedHex(resBody));
+ } catch (Exception e1) {
+ sb.append(e1.getLocalizedMessage());
+ }
+ }
+ sb.append("\n=== END
======================================================================");
+ }
+
+ if (rule.isLogStackTrace() && e == null)
+ e = new Throwable("Stacktrace");
+
+ log(level, sb.toString(), e);
+
}
- private static RestLoggerBuilder builder() {
- return create()
- .normalRules( // Rules when debugging is not enabled.
- createRule() // Log 500+ errors with
status-line and header information.
- .statusFilter(x -> x >= 500)
- .level(SEVERE)
- .requestDetail(HEADER)
- .responseDetail(HEADER)
- .build(),
- createRule() // Log 400-500 errors with just
status-line information.
- .statusFilter(x -> x >= 400)
- .level(WARNING)
- .requestDetail(STATUS_LINE)
- .responseDetail(STATUS_LINE)
- .build()
- )
- .debugRules( // Rules when debugging is enabled.
- createRule() // Log everything with full
details.
- .level(SEVERE)
- .requestDetail(ENTITY)
- .responseDetail(ENTITY)
- .build()
- )
+ /**
+ * Given the specified servlet request/response, find the rule that
applies to it.
+ *
+ * <p>
+ * This method can be overridden to provide specialized logic for
finding rules.
+ *
+ * @param req The servlet request.
+ * @param res The servlet response.
+ * @return The applicable logging rule, or the default rule if not
found. Never <jk>null</jk>.
+ */
+ protected RestLoggerRule getRule(HttpServletRequest req,
HttpServletResponse res) {
+ for (RestLoggerRule r : isDebug(req) ? debugRules : normalRules)
+ if (r.matches(req, res))
+ return r;
+ return DEFAULT_RULE;
+ }
+
+ /**
+ * Returns <jk>true</jk> if debug is enabled on this request.
+ *
+ * <p>
+ * Looks for the request attribute <js>"Debug"</js> to determine
whether debug is enabled.
+ *
+ * <p>
+ * This method can be overridden to provide specialized logic for
determining whether debug mode is enabled on a request.
+ *
+ * @param req The HTTP request being logged.
+ * @return <jk>true</jk> if debug is enabled on this request.
+ * @see RestContext#REST_debug
+ * @see RestContext#REST_debugOn
+ * @see Rest#debug()
+ * @see RestOp#debug()
+ */
+ protected boolean isDebug(HttpServletRequest req) {
+ return firstNonNull(castOrNull(req.getAttribute("Debug"),
Boolean.class), false);
+ }
+
+ /**
+ * Returns <jk>true</jk> if logging is enabled for this request.
+ *
+ * <p>
+ * Uses the enabled and enabled-test settings on the matched rule and
this logger to determine whether a REST
+ * call should be logged.
+ *
+ * <p>
+ * This method can be overridden to provide specialized logic for
determining whether a REST call should be logged.
+ *
+ * @param rule The first matching rule. Never <jk>null</jk>.
+ * @param req The HTTP request.
+ * @return <jk>true</jk> if logging is enabled for this request.
+ */
+ protected boolean isEnabled(RestLoggerRule rule, HttpServletRequest
req) {
+ Enablement enabled = firstNonNull(rule.getEnabled(),
this.enabled);
+ Predicate<HttpServletRequest> enabledTest =
firstNonNull(rule.getEnabledTest(), this.enabledTest);
+ return enabled.isEnabled(enabledTest.test(req));
+ }
+
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // Other methods
+
//-----------------------------------------------------------------------------------------------------------------
+
+ /**
+ * Returns the logger to use for logging REST calls.
+ *
+ * <p>
+ * Returns the logger specified in the builder, or {@link
Logger#getGlobal()} if it wasn't specified.
+ *
+ * <p>
+ * This method can be overridden in subclasses to provide a different
logger.
+ *
+ * @return The logger to use for logging REST calls.
+ */
+ protected Logger getLogger() {
+ return logger;
+ }
+
+ /**
+ * Logs the specified message to the logger.
+ *
+ * <p>
+ * Subclasses can override this method to capture messages being sent
to the logger and handle it differently.
+ *
+ * @param level The log level.
+ * @param msg The log message.
+ * @param e The exception.
+ */
+ protected void log(Level level, String msg, Throwable e) {
+ getLogger().log(level, msg, e);
+ }
+
+ private byte[] getRequestBody(HttpServletRequest req) {
+ if (req instanceof RestRequest)
+ req = ((RestRequest)req).getInner();
+ if (req instanceof CachingHttpServletRequest)
+ return ((CachingHttpServletRequest)req).getBody();
+ return castOrNull(req.getAttribute("RequestBody"),
byte[].class);
+ }
+
+ private byte[] getResponseBody(HttpServletRequest req,
HttpServletResponse res) {
+ if (res instanceof RestResponse)
+ res = ((RestResponse)res).getInner();
+ if (res instanceof CachingHttpServletResponse)
+ return ((CachingHttpServletResponse)res).getBody();
+ return castOrNull(req.getAttribute("ResponseBody"),
byte[].class);
+ }
+
+ private StackTraceInfo getStackTraceInfo(Throwable e) {
+ if (e == null || stackTraceStore == null)
+ return null;
+ stackTraceStore.add(e);
+ return stackTraceStore.getStackTraceInfo(e);
+ }
+
+ /**
+ * Returns the properties defined on this bean context as a simple map
for debugging purposes.
+ *
+ * @return A new map containing the properties defined on this context.
+ */
+ public OMap toMap() {
+ return OMap.create()
+ .a("logger", logger)
+ .a("stackTraceStore", stackTraceStore)
+ .a("enabled", enabled)
+ .a("level", level)
+ .a("requestDetail", requestDetail)
+ .a("responseDetail", responseDetail)
+ .a("normalRules", normalRules.length == 0 ? null :
normalRules)
+ .a("debugRules", debugRules.length == 0 ? null :
debugRules)
;
}
+
+ @Override /* Object */
+ public String toString() {
+ return SimpleJsonSerializer.DEFAULT_READABLE.toString(toMap());
+ }
}
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logging/BasicTestCaptureRestLogger.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logging/BasicTestCaptureRestLogger.java
index 13c5a89..ff395ab 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logging/BasicTestCaptureRestLogger.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logging/BasicTestCaptureRestLogger.java
@@ -12,6 +12,9 @@
//
***************************************************************************************************************************
package org.apache.juneau.rest.logging;
+import static java.util.logging.Level.*;
+import static org.apache.juneau.rest.logging.RestLoggingDetail.*;
+
import java.util.concurrent.atomic.*;
import java.util.logging.*;
@@ -62,7 +65,7 @@ import org.apache.juneau.assertions.*;
* }
* </p>
*/
-public class BasicTestCaptureRestLogger extends RestLogger {
+public class BasicTestCaptureRestLogger extends BasicRestLogger {
private AtomicReference<LogRecord> lastRecord = new AtomicReference<>();
@@ -81,7 +84,33 @@ public class BasicTestCaptureRestLogger extends RestLogger {
* Uses the same settings as {@link BasicRestLogger}.
*/
public BasicTestCaptureRestLogger() {
- super(BasicRestLogger.SETTINGS.get());
+ super(builder());
+ }
+
+ private static RestLoggerBuilder builder() {
+ return RestLogger.create()
+ .normalRules( // Rules when debugging is not enabled.
+ RestLoggerRule.create() // Log 500+ errors
with status-line and header information.
+ .statusFilter(x -> x >= 500)
+ .level(SEVERE)
+ .requestDetail(HEADER)
+ .responseDetail(HEADER)
+ .build(),
+ RestLoggerRule.create() // Log 400-500 errors
with just status-line information.
+ .statusFilter(x -> x >= 400)
+ .level(WARNING)
+ .requestDetail(STATUS_LINE)
+ .responseDetail(STATUS_LINE)
+ .build()
+ )
+ .debugRules( // Rules when debugging is enabled.
+ RestLoggerRule.create() // Log everything with
full details.
+ .level(SEVERE)
+ .requestDetail(ENTITY)
+ .responseDetail(ENTITY)
+ .build()
+ )
+ ;
}
@Override
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logging/BasicTestRestLogger.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logging/BasicTestRestLogger.java
index 97dc96d..ab9260f 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logging/BasicTestRestLogger.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logging/BasicTestRestLogger.java
@@ -47,7 +47,7 @@ import org.apache.juneau.rest.*;
* <li class='link'>{@doc RestLoggingAndDebugging}
* </ul>
*/
-public class BasicTestRestLogger extends RestLogger {
+public class BasicTestRestLogger extends BasicRestLogger {
/**
* Constructor.
@@ -59,9 +59,9 @@ public class BasicTestRestLogger extends RestLogger {
}
private static RestLoggerBuilder builder() {
- return create()
+ return RestLogger.create()
.normalRules( // Rules when debugging is not enabled.
- createRule() // Log 500+ errors with
status-line and header information.
+ RestLoggerRule.create() // Log 500+ errors
with status-line and header information.
.statusFilter(x -> x >= 400)
.level(SEVERE)
.requestDetail(HEADER)
@@ -70,7 +70,7 @@ public class BasicTestRestLogger extends RestLogger {
.enabledTest(x -> ! isNoLog(x)) //
Only log if it's not a no-trace request.
.logStackTrace()
.build(),
- createRule() // Log 400-500 errors with just
status-line information.
+ RestLoggerRule.create() // Log 400-500 errors
with just status-line information.
.statusFilter(x -> x >= 400)
.level(WARNING)
.requestDetail(STATUS_LINE)
@@ -81,7 +81,7 @@ public class BasicTestRestLogger extends RestLogger {
.build()
)
.debugRules( // Rules when debugging is enabled.
- createRule() // Log everything with full
details.
+ RestLoggerRule.create() // Log everything with
full details.
.level(SEVERE)
.requestDetail(ENTITY)
.responseDetail(ENTITY)
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logging/RestLogger.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logging/RestLogger.java
index 520637a..ecf2eb7 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logging/RestLogger.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logging/RestLogger.java
@@ -12,76 +12,20 @@
//
***************************************************************************************************************************
package org.apache.juneau.rest.logging;
-import static org.apache.juneau.internal.ObjectUtils.*;
-import static org.apache.juneau.internal.StringUtils.*;
-import static org.apache.juneau.rest.logging.RestLoggingDetail.*;
-import static org.apache.juneau.Enablement.*;
-import static org.apache.juneau.SystemProperties.*;
-import static java.util.logging.Level.*;
-
-import java.util.*;
import java.util.function.*;
import java.util.logging.*;
import javax.servlet.http.*;
import org.apache.juneau.*;
-import org.apache.juneau.collections.*;
-import org.apache.juneau.internal.*;
-import org.apache.juneau.json.*;
import org.apache.juneau.rest.*;
import org.apache.juneau.rest.annotation.*;
-import org.apache.juneau.rest.util.*;
-import org.apache.juneau.utils.*;
/**
- * Interface class used for logging HTTP requests to the log file.
- *
- * <p>
- * Provides the following capabilities:
- * <ul>
- * <li>Allows incoming HTTP requests to be logged at various {@link
Enablement detail levels}.
- * <li>Allows rules to be defined to handle request logging differently
depending on the resulting status code.
- * <li>Allows use of stack trace hashing to eliminate duplication of stack
traces in log files.
- * <li>Allows customization of handling of where requests are logged to.
- * <li>Allows configuration via system properties or environment variables.
- * </ul>
- *
- * <p>
- * The following is an example of a logger that logs errors only when
debugging is not enabled, and everything when
- * logging is enabled.
- *
- * <h5 class='section'>Example:</h5>
- * <p class='bcode w800'>
- * RestLogger
- * .<jsm>create</jsm>()
- * .logger(<js>"MyLogger"</js>) <jc>// Use MyLogger Java
logger.</jc>
- * .normalRules( <jc>// Rules when debugging is not
enabled.</jc>
- * <jsm>createRule</jsm>() <jc>// Log 500+ errors
with status-line and header information.</jc>
- * .statusFilter(x -> x >= 500)
- * .level(<jsf>SEVERE</jsf>)
- * .requestDetail(<jsf>HEADER</jsf>)
- * .responseDetail<jsf>(HEADER</jsf>)
- * .build(),
- * <jsm>createRule</jsm>() <jc>// Log 400-500
errors with just status-line information.</jc>
- * .statusFilter(x -> x >= 400)
- * .level(<jsf>WARNING</jsf>)
- * .requestDetail(<jsf>STATUS_LINE</jsf>)
- * .responseDetail(<jsf>STATUS_LINE</jsf>)
- * .build()
- * )
- * .debugRules( <jc>// Rules when debugging is
enabled.</jc>
- * <jsm>createRule</jsm>() <jc>// Log everything
with full details.</jc>
- * .level(<jsf>SEVERE</jsf>)
- * .requestDetail(<jsf>ENTITY</jsf>)
- * .responseDetail(<jsf>ENTITY</jsf>)
- * .build()
- * )
- * ;
- * </p>
+ * Interface class used for logging HTTP requests.
*
* <p>
- * The {@link RestLoggerBuilder#build(Class)} method has been provided for
easy extension of this class.
+ * The {@link RestLoggerBuilder#implClass(Class)} method has been provided for
easy extension of this class.
*
* <p>
* The following default implementations are also provided:
@@ -102,16 +46,10 @@ import org.apache.juneau.utils.*;
* <li class='link'>{@doc RestLoggingAndDebugging}
* </ul>
*/
-public class RestLogger {
+public interface RestLogger {
/** Represents no logger */
- public static final class Null extends RestLogger {
- Null(RestLoggerBuilder builder) {
- super(create());
- }
- }
-
- private static final RestLoggerRule DEFAULT_RULE =
RestLoggerRule.create().build();
+ public abstract class Null implements RestLogger {}
/**
* System property name for the default logger name to use for {@link
RestLogger} objects.
@@ -193,295 +131,10 @@ public class RestLogger {
}
/**
- * Creates a new rule builder.
- *
- * @return A new rule builder.
- */
- public static RestLoggerRuleBuilder createRule() {
- return new RestLoggerRuleBuilder();
- }
-
- private final Logger logger;
- private final StackTraceStore stackTraceStore;
- private final RestLoggerRule[] normalRules, debugRules;
- private final Enablement enabled;
- private final Predicate<HttpServletRequest> enabledTest;
- private final Level level;
- private final RestLoggingDetail requestDetail, responseDetail;
-
- /**
- * Constructor.
- *
- * @param builder The builder object.
- */
- public RestLogger(RestLoggerBuilder builder) {
- this.logger = firstNonNull(builder.logger,
Logger.getLogger(getProperty(String.class, SP_logger, "global")));
- this.stackTraceStore = builder.stackTraceDatabase;
- this.normalRules = builder.normalRules.toArray(new
RestLoggerRule[builder.normalRules.size()]);
- this.debugRules = builder.debugRules.toArray(new
RestLoggerRule[builder.debugRules.size()]);
- this.enabled = firstNonNull(builder.enabled,
getProperty(Enablement.class, SP_enabled, ALWAYS));
- this.enabledTest = firstNonNull(builder.enabledTest, x ->
false);
- this.requestDetail = firstNonNull(builder.requestDetail,
getProperty(RestLoggingDetail.class, SP_requestDetail, STATUS_LINE));
- this.responseDetail = firstNonNull(builder.responseDetail,
getProperty(RestLoggingDetail.class, SP_responseDetail, STATUS_LINE));
- this.level = firstNonNull(builder.level,
getProperty(Level.class, SP_level, OFF));
- }
-
- /**
* Called at the end of a servlet request to log the request.
*
* @param req The servlet request.
* @param res The servlet response.
*/
- public void log(HttpServletRequest req, HttpServletResponse res) {
-
- RestLoggerRule rule = getRule(req, res);
-
- if (! isEnabled(rule, req))
- return;
-
- Level level = firstNonNull(rule.getLevel(), this.level);
-
- if (level == Level.OFF)
- return;
-
- Throwable e = castOrNull(req.getAttribute("Exception"),
Throwable.class);
- Long execTime = castOrNull(req.getAttribute("ExecTime"),
Long.class);
-
- RestLoggingDetail reqd = firstNonNull(rule.getRequestDetail(),
requestDetail);
- RestLoggingDetail resd = firstNonNull(rule.getResponseDetail(),
responseDetail);
-
- String method = req.getMethod();
- int status = res.getStatus();
- String uri = req.getRequestURI();
- byte[] reqBody = getRequestBody(req);
- byte[] resBody = getResponseBody(req, res);
-
- StringBuilder sb = new StringBuilder();
-
- if (reqd != STATUS_LINE || resd != STATUS_LINE)
- sb.append("\n=== HTTP Call (incoming)
======================================================\n");
-
- StackTraceInfo sti = getStackTraceInfo(e);
-
- sb.append('[').append(status);
-
- if (sti != null) {
- int count = sti.getCount();
-
sb.append(',').append(sti.getHash()).append('.').append(count);
- if (count > 1)
- e = null;
- }
-
- sb.append("] ");
-
- sb.append("HTTP ").append(method).append(' ').append(uri);
-
- if (reqd != STATUS_LINE || resd != STATUS_LINE) {
-
- if (reqd.isOneOf(HEADER, ENTITY)) {
- String qs = req.getQueryString();
- if (qs != null)
- sb.append('?').append(qs);
- }
-
- if (reqBody != null && reqd.isOneOf(HEADER ,ENTITY))
- sb.append("\n\tRequest length:
").append(reqBody.length).append(" bytes");
-
- if (resd.isOneOf(HEADER, ENTITY))
- sb.append("\n\tResponse code: ").append(status);
-
- if (resBody != null && resd.isOneOf(HEADER, ENTITY))
- sb.append("\n\tResponse length:
").append(resBody.length).append(" bytes");
-
- if (execTime != null && resd.isOneOf(HEADER, ENTITY))
- sb.append("\n\tExec time:
").append(execTime).append("ms");
-
- if (reqd.isOneOf(HEADER, ENTITY)) {
- Enumeration<String> hh = req.getHeaderNames();
- if (hh.hasMoreElements()) {
- sb.append("\n---Request Headers---");
- while (hh.hasMoreElements()) {
- String h = hh.nextElement();
-
sb.append("\n\t").append(h).append(": ").append(req.getHeader(h));
- }
- }
- }
-
- if (resd.isOneOf(HEADER, ENTITY)) {
- Collection<String> hh = res.getHeaderNames();
- if (hh.size() > 0) {
- sb.append("\n---Response Headers---");
- for (String h : hh) {
-
sb.append("\n\t").append(h).append(": ").append(res.getHeader(h));
- }
- }
- }
-
- if (reqBody != null && reqBody.length > 0 && reqd ==
ENTITY) {
- try {
- sb.append("\n---Request Body UTF-8---");
- sb.append("\n").append(new
String(reqBody, IOUtils.UTF8));
- sb.append("\n---Request Body Hex---");
-
sb.append("\n").append(toSpacedHex(reqBody));
- } catch (Exception e1) {
-
sb.append("\n").append(e1.getLocalizedMessage());
- }
- }
-
- if (resBody != null && resBody.length > 0 && resd ==
ENTITY) {
- try {
- sb.append("\n---Response Body
UTF-8---");
- sb.append("\n").append(new
String(resBody, IOUtils.UTF8));
- sb.append("\n---Response Body Hex---");
-
sb.append("\n").append(toSpacedHex(resBody));
- } catch (Exception e1) {
- sb.append(e1.getLocalizedMessage());
- }
- }
- sb.append("\n=== END
======================================================================");
- }
-
- if (rule.isLogStackTrace() && e == null)
- e = new Throwable("Stacktrace");
-
- log(level, sb.toString(), e);
-
- }
-
- /**
- * Given the specified servlet request/response, find the rule that
applies to it.
- *
- * <p>
- * This method can be overridden to provide specialized logic for
finding rules.
- *
- * @param req The servlet request.
- * @param res The servlet response.
- * @return The applicable logging rule, or the default rule if not
found. Never <jk>null</jk>.
- */
- protected RestLoggerRule getRule(HttpServletRequest req,
HttpServletResponse res) {
- for (RestLoggerRule r : isDebug(req) ? debugRules : normalRules)
- if (r.matches(req, res))
- return r;
- return DEFAULT_RULE;
- }
-
- /**
- * Returns <jk>true</jk> if debug is enabled on this request.
- *
- * <p>
- * Looks for the request attribute <js>"Debug"</js> to determine
whether debug is enabled.
- *
- * <p>
- * This method can be overridden to provide specialized logic for
determining whether debug mode is enabled on a request.
- *
- * @param req The HTTP request being logged.
- * @return <jk>true</jk> if debug is enabled on this request.
- * @see RestContext#REST_debug
- * @see RestContext#REST_debugOn
- * @see Rest#debug()
- * @see RestOp#debug()
- */
- protected boolean isDebug(HttpServletRequest req) {
- return firstNonNull(castOrNull(req.getAttribute("Debug"),
Boolean.class), false);
- }
-
- /**
- * Returns <jk>true</jk> if logging is enabled for this request.
- *
- * <p>
- * Uses the enabled and enabled-test settings on the matched rule and
this logger to determine whether a REST
- * call should be logged.
- *
- * <p>
- * This method can be overridden to provide specialized logic for
determining whether a REST call should be logged.
- *
- * @param rule The first matching rule. Never <jk>null</jk>.
- * @param req The HTTP request.
- * @return <jk>true</jk> if logging is enabled for this request.
- */
- protected boolean isEnabled(RestLoggerRule rule, HttpServletRequest
req) {
- Enablement enabled = firstNonNull(rule.getEnabled(),
this.enabled);
- Predicate<HttpServletRequest> enabledTest =
firstNonNull(rule.getEnabledTest(), this.enabledTest);
- return enabled.isEnabled(enabledTest.test(req));
- }
-
-
-
//-----------------------------------------------------------------------------------------------------------------
- // Other methods
-
//-----------------------------------------------------------------------------------------------------------------
-
- /**
- * Returns the logger to use for logging REST calls.
- *
- * <p>
- * Returns the logger specified in the builder, or {@link
Logger#getGlobal()} if it wasn't specified.
- *
- * <p>
- * This method can be overridden in subclasses to provide a different
logger.
- *
- * @return The logger to use for logging REST calls.
- */
- protected Logger getLogger() {
- return logger;
- }
-
- /**
- * Logs the specified message to the logger.
- *
- * <p>
- * Subclasses can override this method to capture messages being sent
to the logger and handle it differently.
- *
- * @param level The log level.
- * @param msg The log message.
- * @param e The exception.
- */
- protected void log(Level level, String msg, Throwable e) {
- getLogger().log(level, msg, e);
- }
-
- private byte[] getRequestBody(HttpServletRequest req) {
- if (req instanceof RestRequest)
- req = ((RestRequest)req).getInner();
- if (req instanceof CachingHttpServletRequest)
- return ((CachingHttpServletRequest)req).getBody();
- return castOrNull(req.getAttribute("RequestBody"),
byte[].class);
- }
-
- private byte[] getResponseBody(HttpServletRequest req,
HttpServletResponse res) {
- if (res instanceof RestResponse)
- res = ((RestResponse)res).getInner();
- if (res instanceof CachingHttpServletResponse)
- return ((CachingHttpServletResponse)res).getBody();
- return castOrNull(req.getAttribute("ResponseBody"),
byte[].class);
- }
-
- private StackTraceInfo getStackTraceInfo(Throwable e) {
- if (e == null || stackTraceStore == null)
- return null;
- stackTraceStore.add(e);
- return stackTraceStore.getStackTraceInfo(e);
- }
-
- /**
- * Returns the properties defined on this bean context as a simple map
for debugging purposes.
- *
- * @return A new map containing the properties defined on this context.
- */
- public OMap toMap() {
- return OMap.create()
- .a("logger", logger)
- .a("stackTraceStore", stackTraceStore)
- .a("enabled", enabled)
- .a("level", level)
- .a("requestDetail", requestDetail)
- .a("responseDetail", responseDetail)
- .a("normalRules", normalRules.length == 0 ? null :
normalRules)
- .a("debugRules", debugRules.length == 0 ? null :
debugRules)
- ;
- }
-
- @Override /* Object */
- public String toString() {
- return SimpleJsonSerializer.DEFAULT_READABLE.toString(toMap());
- }
+ public void log(HttpServletRequest req, HttpServletResponse res);
}
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logging/RestLoggerBuilder.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logging/RestLoggerBuilder.java
index 74e4155..41dfefe 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logging/RestLoggerBuilder.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logging/RestLoggerBuilder.java
@@ -13,6 +13,8 @@
package org.apache.juneau.rest.logging;
import static org.apache.juneau.Enablement.*;
+import static org.apache.juneau.internal.ClassUtils.*;
+import static org.apache.juneau.rest.HttpRuntimeException.*;
import java.util.*;
import java.util.function.*;
@@ -22,11 +24,12 @@ import javax.servlet.http.*;
import org.apache.juneau.*;
import org.apache.juneau.collections.*;
-import org.apache.juneau.reflect.*;
+import org.apache.juneau.cp.*;
+import org.apache.juneau.http.exception.*;
import org.apache.juneau.utils.*;
/**
- * Builder class for {@link RestLogger} objects.
+ * Builder class for {@link BasicRestLogger} objects.
*/
public class RestLoggerBuilder {
@@ -37,33 +40,56 @@ public class RestLoggerBuilder {
Predicate<HttpServletRequest> enabledTest;
RestLoggingDetail requestDetail, responseDetail;
Level level;
+ BeanFactory beanFactory;
+ Class<? extends RestLogger> implClass;
/**
- * Create a new {@link RestLogger} using this builder.
+ * Creates a new {@link RestLogger} object from this builder.
*
- * @return A new {@link RestLogger}
+ * <p>
+ * Instantiates an instance of the {@link #implClass(Class)
implementation class} or
+ * else {@link BasicRestLogger} if implementation class was not
specified.
+ *
+ * @return A new {@link RestLogger} object.
*/
public RestLogger build() {
- return new RestLogger(this);
+ try {
+ Class<? extends RestLogger> ic = isConcrete(implClass)
? implClass : getDefaultImplClass();
+ return
BeanFactory.of(beanFactory).addBeans(RestLoggerBuilder.class,
this).createBean(ic);
+ } catch (Exception e) {
+ throw toHttpException(e, InternalServerError.class);
+ }
}
/**
- * Create a new subclass of {@link RestLogger} using this builder.
+ * Specifies the default implementation class if not specified via
{@link #implClass(Class)}.
*
- * <p>
- * The subclass must have a public constructor that optionally takes in
a {@link RestLoggerBuilder} (or subclass) object.
+ * @return The default implementation class if not specified via {@link
#implClass(Class)}.
+ */
+ protected Class<? extends RestLogger> getDefaultImplClass() {
+ return BasicRestLogger.class;
+ }
+
+ /**
+ * Specifies the bean factory to use for instantiating the {@link
RestLogger} object.
*
- * @param c The subclass to instantiate.
- * @param <T> The subclass to instantiate.
- * @return A new subclass of {@link RestLogger}
- * @throws ExecutableException If constructor invocation threw an
exception.
+ * @param value The new value for this setting.
+ * @return This object (for method chaining).
*/
- public <T extends RestLogger> T build(Class<T> c) throws
ExecutableException {
- return ClassInfo
- .of(c)
- .getOptionalPublicConstructorFuzzy(this)
- .orElseThrow(()->new ExecutableException(c.getName() +
"(RestCallLoggerBuilder)"))
- .invoke(this);
+ public RestLoggerBuilder beanFactory(BeanFactory value) {
+ this.beanFactory = value;
+ return this;
+ }
+
+ /**
+ * Specifies a subclass of {@link RestLogger} to create when the {@link
#build()} method is called.
+ *
+ * @param value The new value for this setting.
+ * @return This object (for method chaining).
+ */
+ public RestLoggerBuilder implClass(Class<? extends RestLogger> value) {
+ this.implClass = value;
+ return this;
}
/**
@@ -78,7 +104,7 @@ public class RestLoggerBuilder {
* </ol>
*
* <p>
- * The {@link RestLogger#getLogger()} method can also be overridden to
provide different logic.
+ * The {@link BasicRestLogger#getLogger()} method can also be
overridden to provide different logic.
*
* @param value
* The logger to use for logging the request.
@@ -104,7 +130,7 @@ public class RestLoggerBuilder {
* </ol>
*
* <p>
- * The {@link RestLogger#getLogger()} method can also be overridden to
provide different logic.
+ * The {@link BasicRestLogger#getLogger()} method can also be
overridden to provide different logic.
*
* @param value
* The logger to use for logging the request.
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logging/RestLoggerRuleBuilder.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logging/RestLoggerRuleBuilder.java
index aec9437..8fb4b79 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logging/RestLoggerRuleBuilder.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logging/RestLoggerRuleBuilder.java
@@ -24,7 +24,7 @@ import org.apache.juneau.rest.*;
* Builder for {@link RestLoggerRule} objects.
*
* <p>
- * See the {@link RestLogger} for usage.
+ * See the {@link BasicRestLogger} for usage.
*
* <ul class='seealso'>
* <li class='jf'>{@link RestContext#REST_callLogger}