You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by hl...@apache.org on 2014/05/31 01:57:28 UTC
[1/3] git commit: Include a list of threads in the default exception
report page
Repository: tapestry-5
Updated Branches:
refs/heads/master 5b311716e -> ce6e66ea3
Include a list of threads in the default exception report page
Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/08807a12
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/08807a12
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/08807a12
Branch: refs/heads/master
Commit: 08807a1266b49cc5d76ac38705ab7fe31e893b6e
Parents: 5b31171
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Fri May 30 16:05:33 2014 -0700
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Fri May 30 16:55:24 2014 -0700
----------------------------------------------------------------------
54_RELEASE_NOTES.md | 4 +
.../corelib/pages/ExceptionReport.java | 100 ++++++++++++++++++-
.../META-INF/assets/core/ExceptionReport.css | 13 +++
.../tapestry5/corelib/pages/ExceptionReport.tml | 21 ++++
4 files changed, 135 insertions(+), 3 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/08807a12/54_RELEASE_NOTES.md
----------------------------------------------------------------------
diff --git a/54_RELEASE_NOTES.md b/54_RELEASE_NOTES.md
index 8044c85..23fc912 100644
--- a/54_RELEASE_NOTES.md
+++ b/54_RELEASE_NOTES.md
@@ -155,6 +155,10 @@ The FormFragment component's visibleBound parameter is no longer supported; it w
far up the DOM above the FormFragment's client-side element should be searched when determining visibility. This may
resurface in the future as a CSS expression, but is currently not supported.
+## ExceptionReport Page
+
+The default exception report page has been modified to display a list of threads.
+
## Symbol tapestry.asset-path-prefix
The definition of the symbol 'tapestry.asset-path-prefix' has changed; it no longer includes the leading and trailing
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/08807a12/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ExceptionReport.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ExceptionReport.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ExceptionReport.java
index e6db1ed..1ca7e25 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ExceptionReport.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ExceptionReport.java
@@ -21,16 +21,20 @@ import org.apache.tapestry5.annotations.ContentType;
import org.apache.tapestry5.annotations.Import;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.annotations.UnknownActivationContextCheck;
+import org.apache.tapestry5.func.F;
+import org.apache.tapestry5.func.Mapper;
import org.apache.tapestry5.internal.InternalConstants;
import org.apache.tapestry5.internal.services.PageActivationContextCollector;
import org.apache.tapestry5.internal.services.ReloadHelper;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.ioc.annotations.Symbol;
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
import org.apache.tapestry5.ioc.internal.util.InternalUtils;
import org.apache.tapestry5.services.*;
import java.net.MalformedURLException;
import java.net.URL;
+import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
@@ -99,17 +103,42 @@ public class ExceptionReport implements ExceptionReporter
@Inject
private ReloadHelper reloadHelper;
-
+
@Inject
private URLEncoder urlEncoder;
@Property
private String rootURL;
+ @Property
+ private ThreadInfo thread;
+
+ public class ThreadInfo implements Comparable<ThreadInfo>
+ {
+ public final String className, name, flags;
+
+ public final ThreadGroup group;
+
+ public ThreadInfo(String className, String name, String flags, ThreadGroup group)
+ {
+ this.className = className;
+ this.name = name;
+ this.flags = flags;
+ this.group = group;
+ }
+
+ @Override
+ public int compareTo(ThreadInfo o)
+ {
+ return name.compareTo(o.name);
+ }
+ }
+
private final String pathSeparator = System.getProperty(PATH_SEPARATOR_PROPERTY);
- public boolean isShowActions() {
- return failurePage != null && ! request.isXHR();
+ public boolean isShowActions()
+ {
+ return failurePage != null && !request.isXHR();
}
public void reportException(Throwable exception)
@@ -182,4 +211,69 @@ public class ExceptionReport implements ExceptionReporter
return getPropertyValue().split(pathSeparator);
}
+
+ private Thread[] assembleThreads()
+ {
+ ThreadGroup rootGroup = Thread.currentThread().getThreadGroup();
+
+ while (true)
+ {
+ ThreadGroup parentGroup = rootGroup.getParent();
+ if (parentGroup == null)
+ {
+ break;
+ }
+ rootGroup = parentGroup;
+ }
+
+ Thread[] threads = new Thread[rootGroup.activeCount()];
+
+ while (true)
+ {
+ // A really ugly API. threads.length must be larger than
+ // the actual number of threads, just so we can determine
+ // if we're done.
+ int count = rootGroup.enumerate(threads, true);
+ if (count < threads.length)
+ {
+ return Arrays.copyOf(threads, count);
+ }
+ threads = new Thread[threads.length * 2];
+ }
+ }
+
+ public List<ThreadInfo> getThreads()
+ {
+ return F.flow(assembleThreads()).map(new Mapper<Thread, ThreadInfo>()
+ {
+ @Override
+ public ThreadInfo map(Thread t)
+ {
+ List<String> flags = CollectionFactory.newList();
+
+ if (t.isDaemon())
+ {
+ flags.add("daemon");
+ }
+ if (!t.isAlive())
+ {
+ flags.add("NOT alive");
+ }
+ if (t.isInterrupted())
+ {
+ flags.add("interrupted");
+ }
+
+ if (t.getPriority() != Thread.NORM_PRIORITY)
+ {
+ flags.add("priority " + t.getPriority());
+ }
+
+ return new ThreadInfo(Thread.currentThread() == t ? "active-thread" : "",
+ t.getName(),
+ InternalUtils.join(flags),
+ t.getThreadGroup());
+ }
+ }).sort().toList();
+ }
}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/08807a12/tapestry-core/src/main/resources/META-INF/assets/core/ExceptionReport.css
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/resources/META-INF/assets/core/ExceptionReport.css b/tapestry-core/src/main/resources/META-INF/assets/core/ExceptionReport.css
index e770da3..87fdab6 100644
--- a/tapestry-core/src/main/resources/META-INF/assets/core/ExceptionReport.css
+++ b/tapestry-core/src/main/resources/META-INF/assets/core/ExceptionReport.css
@@ -1,3 +1,16 @@
body {
padding-top: 55px;
+}
+
+table.exception-report-threads td, table.exception-report-threads th {
+ text-align: right;
+}
+
+table.exception-report-threads tr td:last-child,
+table.exception-report-threads tr th:last-child {
+ text-align: left;
+}
+
+table.exception-report-threads tr.active-thread {
+ font-weight: bold;
}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/08807a12/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/ExceptionReport.tml
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/ExceptionReport.tml b/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/ExceptionReport.tml
index c26ebf1..0190b80 100644
--- a/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/ExceptionReport.tml
+++ b/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/ExceptionReport.tml
@@ -94,6 +94,27 @@
</dl>
</t:if>
+ <h3>Threads</h3>
+
+ <table class="table table-compact table-striped exception-report-threads">
+ <thread>
+ <tr>
+ <th>Thread Name</th>
+ <th>Group Name</th>
+ <th>Flags</th>
+ </tr>
+ </thread>
+ <tbody>
+
+ <tr t:type="loop" source="threads" value="thread" class="${thread.className}">
+ <td class="thread-name">${thread.name}</td>
+ <td>${thread.group?.name}</td>
+ <td>${thread.flags}</td>
+ </tr>
+ </tbody>
+ </table>
+
+
<h3>System Properties</h3>
<dl>
<t:loop source="systemProperties" value="propertyName">
[2/3] git commit: Integrate an exception reporting service that will
write exception report text files to the file system
Posted by hl...@apache.org.
Integrate an exception reporting service that will write exception report text files to the file system
Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/511813f3
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/511813f3
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/511813f3
Branch: refs/heads/master
Commit: 511813f381c7724024cc818977e9a85f7eade22a
Parents: 08807a1
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Fri May 30 16:55:07 2014 -0700
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Fri May 30 16:57:01 2014 -0700
----------------------------------------------------------------------
54_RELEASE_NOTES.md | 4 +
.../org/apache/tapestry5/SymbolConstants.java | 14 +-
.../corelib/pages/ExceptionReport.java | 42 +--
.../internal/TapestryInternalUtils.java | 31 +-
.../DefaultRequestExceptionHandler.java | 56 +--
.../exceptions/ExceptionReporterImpl.java | 340 +++++++++++++++++++
.../tapestry5/modules/TapestryModule.java | 3 +
.../services/exceptions/ExceptionReporter.java | 33 ++
.../services/exceptions/package-info.java | 4 +
.../tapestry5/corelib/pages/ExceptionReport.tml | 4 +-
.../DefaultRequestExceptionHandlerTest.java | 117 ++++---
11 files changed, 535 insertions(+), 113 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/511813f3/54_RELEASE_NOTES.md
----------------------------------------------------------------------
diff --git a/54_RELEASE_NOTES.md b/54_RELEASE_NOTES.md
index 23fc912..eda173b 100644
--- a/54_RELEASE_NOTES.md
+++ b/54_RELEASE_NOTES.md
@@ -159,6 +159,10 @@ resurface in the future as a CSS expression, but is currently not supported.
The default exception report page has been modified to display a list of threads.
+## ExceptionReporter Service
+
+A new service, `ExceptionReporter`, will now create a text file on the file system for each runtime request processing exception.
+
## Symbol tapestry.asset-path-prefix
The definition of the symbol 'tapestry.asset-path-prefix' has changed; it no longer includes the leading and trailing
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/511813f3/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java b/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
index d2526ae..6ab3759 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
@@ -1,5 +1,3 @@
-// Copyright 2008-2014 The Apache Software Foundation
-//
// Licensed 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
@@ -474,15 +472,25 @@ public class SymbolConstants
* @since 5.4
*/
public static final String FORM_FIELD_CSS_CLASS = "tapestry.form-field-css-class";
-
+
/**
* Defines whether {@link java.text.DateFormat} instances created by Tapestry should be
* lenient or not by default. The default value is <code>false</code>.
+ *
* @since 5.4
*/
public static final String LENIENT_DATE_FORMAT = "tapestry.lenient-date-format";
/**
+ * The directory to which exception report files should be written. The default is appropriate
+ * for development: {@code build/exceptions}, and should be changed for production.
+ *
+ * @see org.apache.tapestry5.services.exceptions.ExceptionReporter
+ * @since 5.4
+ */
+
+ public static final String EXCEPTION_REPORTS_DIR = "tapestry.exception-reports-dir";
+ /**
* Defines whether {@link CSSURLRewriter} will throw an exception when a CSS file
* references an URL which doesn't exist. The default value is <code>false</code>.
*
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/511813f3/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ExceptionReport.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ExceptionReport.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ExceptionReport.java
index 1ca7e25..bdc853a 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ExceptionReport.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ExceptionReport.java
@@ -1,5 +1,3 @@
-// Copyright 2006-2013 The Apache Software Foundation
-//
// Licensed 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
@@ -24,6 +22,7 @@ import org.apache.tapestry5.annotations.UnknownActivationContextCheck;
import org.apache.tapestry5.func.F;
import org.apache.tapestry5.func.Mapper;
import org.apache.tapestry5.internal.InternalConstants;
+import org.apache.tapestry5.internal.TapestryInternalUtils;
import org.apache.tapestry5.internal.services.PageActivationContextCollector;
import org.apache.tapestry5.internal.services.ReloadHelper;
import org.apache.tapestry5.ioc.annotations.Inject;
@@ -34,7 +33,6 @@ import org.apache.tapestry5.services.*;
import java.net.MalformedURLException;
import java.net.URL;
-import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
@@ -115,14 +113,15 @@ public class ExceptionReport implements ExceptionReporter
public class ThreadInfo implements Comparable<ThreadInfo>
{
- public final String className, name, flags;
+ public final String className, name, state, flags;
public final ThreadGroup group;
- public ThreadInfo(String className, String name, String flags, ThreadGroup group)
+ public ThreadInfo(String className, String name, String state, String flags, ThreadGroup group)
{
this.className = className;
this.name = name;
+ this.state = state;
this.flags = flags;
this.group = group;
}
@@ -212,39 +211,9 @@ public class ExceptionReport implements ExceptionReporter
return getPropertyValue().split(pathSeparator);
}
- private Thread[] assembleThreads()
- {
- ThreadGroup rootGroup = Thread.currentThread().getThreadGroup();
-
- while (true)
- {
- ThreadGroup parentGroup = rootGroup.getParent();
- if (parentGroup == null)
- {
- break;
- }
- rootGroup = parentGroup;
- }
-
- Thread[] threads = new Thread[rootGroup.activeCount()];
-
- while (true)
- {
- // A really ugly API. threads.length must be larger than
- // the actual number of threads, just so we can determine
- // if we're done.
- int count = rootGroup.enumerate(threads, true);
- if (count < threads.length)
- {
- return Arrays.copyOf(threads, count);
- }
- threads = new Thread[threads.length * 2];
- }
- }
-
public List<ThreadInfo> getThreads()
{
- return F.flow(assembleThreads()).map(new Mapper<Thread, ThreadInfo>()
+ return F.flow(TapestryInternalUtils.getAllThreads()).map(new Mapper<Thread, ThreadInfo>()
{
@Override
public ThreadInfo map(Thread t)
@@ -271,6 +240,7 @@ public class ExceptionReport implements ExceptionReporter
return new ThreadInfo(Thread.currentThread() == t ? "active-thread" : "",
t.getName(),
+ t.getState().name(),
InternalUtils.join(flags),
t.getThreadGroup());
}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/511813f3/tapestry-core/src/main/java/org/apache/tapestry5/internal/TapestryInternalUtils.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/TapestryInternalUtils.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/TapestryInternalUtils.java
index 6c6138b..3435d64 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/TapestryInternalUtils.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/TapestryInternalUtils.java
@@ -1,5 +1,3 @@
-// Copyright 2006-2013 The Apache Software Foundation
-//
// Licensed 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
@@ -33,6 +31,7 @@ import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.ref.Reference;
import java.lang.reflect.Type;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
@@ -587,5 +586,33 @@ public class TapestryInternalUtils
return ref == null ? null : ref.get();
}
+
+ /**
+ * Gathers together an array containing all the threads.
+ * @since 5.4 */
+ public static Thread[] getAllThreads() {
+ ThreadGroup rootGroup = Thread.currentThread().getThreadGroup();
+
+ while (true) {
+ ThreadGroup parentGroup = rootGroup.getParent();
+ if (parentGroup == null) {
+ break;
+ }
+ rootGroup = parentGroup;
+ }
+
+ Thread[] threads = new Thread[rootGroup.activeCount()];
+
+ while (true) {
+ // A really ugly API. threads.length must be larger than
+ // the actual number of threads, just so we can determine
+ // if we're done.
+ int count = rootGroup.enumerate(threads, true);
+ if (count < threads.length) {
+ return Arrays.copyOf(threads, count);
+ }
+ threads = new Thread[threads.length * 2];
+ }
+ }
}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/511813f3/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DefaultRequestExceptionHandler.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DefaultRequestExceptionHandler.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DefaultRequestExceptionHandler.java
index 70f72cb..de6b50b 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DefaultRequestExceptionHandler.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DefaultRequestExceptionHandler.java
@@ -1,5 +1,3 @@
-// Copyright 2006-2013 The Apache Software Foundation
-//
// Licensed 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
@@ -44,14 +42,14 @@ import java.util.Map.Entry;
* servlet spec's standard error handling, the default exception handler allows configuring handlers for specific types of
* exceptions. The error-page/exception-type configuration in web.xml does not work in Tapestry application as errors are
* wrapped in Tapestry's exception types (see {@link OperationException} and {@link ComponentEventException} ).
- *
+ * <p/>
* Configurations are flexible. You can either contribute a {@link ExceptionHandlerAssistant} to use arbitrary complex logic
* for error handling or a page class to render for the specific exception. Additionally, exceptions can carry context for the
* error page. Exception context is formed either from the name of Exception (e.g. SmtpNotRespondingException -> ServiceFailure mapping
* would render a page with URL /servicefailure/smtpnotresponding) or they can implement {@link ContextAwareException} interface.
- *
+ * <p/>
* If no configured exception type is found, the default exception page {@link SymbolConstants#EXCEPTION_REPORT_PAGE} is rendered.
- * This fallback exception page must implement the {@link ExceptionReporter} interface.
+ * This fallback exception page must implement the {@link org.apache.tapestry5.services.ExceptionReporter} interface.
*/
public class DefaultRequestExceptionHandler implements RequestExceptionHandler
{
@@ -71,30 +69,28 @@ public class DefaultRequestExceptionHandler implements RequestExceptionHandler
private final LinkSource linkSource;
+ private final org.apache.tapestry5.services.exceptions.ExceptionReporter exceptionReporter;
+
// should be Class<? extends Throwable>, Object but it's not allowed to configure subtypes
private final Map<Class, Object> configuration;
/**
- * @param pageCache
- * @param renderer
- * @param logger
- * @param pageName
- * @param request
- * @param response
- * @param componentClassResolver
- * @param linkSource
- * @param serviceResources
- * @param configuration A map of Exception class and handler values. A handler is either a page class or an ExceptionHandlerAssistant. ExceptionHandlerAssistant can be a class
- * in which case the instance is autobuilt.
+ * @param configuration
+ * A map of Exception class and handler values. A handler is either a page class or an ExceptionHandlerAssistant. ExceptionHandlerAssistant can be a class
*/
@SuppressWarnings("rawtypes")
- public DefaultRequestExceptionHandler(RequestPageCache pageCache, PageResponseRenderer renderer, Logger logger,
-
+ public DefaultRequestExceptionHandler(RequestPageCache pageCache,
+ PageResponseRenderer renderer,
+ Logger logger,
@Symbol(SymbolConstants.EXCEPTION_REPORT_PAGE)
String pageName,
-
- Request request, Response response, ComponentClassResolver componentClassResolver,
- LinkSource linkSource, ServiceResources serviceResources, Map<Class, Object> configuration)
+ Request request,
+ Response response,
+ ComponentClassResolver componentClassResolver,
+ LinkSource linkSource,
+ ServiceResources serviceResources,
+ org.apache.tapestry5.services.exceptions.ExceptionReporter exceptionReporter,
+ Map<Class, Object> configuration)
{
this.pageCache = pageCache;
this.renderer = renderer;
@@ -104,6 +100,7 @@ public class DefaultRequestExceptionHandler implements RequestExceptionHandler
this.response = response;
this.componentClassResolver = componentClassResolver;
this.linkSource = linkSource;
+ this.exceptionReporter = exceptionReporter;
Map<Class<ExceptionHandlerAssistant>, ExceptionHandlerAssistant> handlerAssistants = new HashMap<Class<ExceptionHandlerAssistant>, ExceptionHandlerAssistant>();
@@ -131,14 +128,14 @@ public class DefaultRequestExceptionHandler implements RequestExceptionHandler
/**
* Handles the exception thrown at some point the request was being processed
- *
+ * <p/>
* First checks if there was a specific exception handler/page configured for this exception type, it's super class or super-super class.
* Renders the default exception page if none was configured.
*
- * @param exception The exception that was thrown
- *
+ * @param exception
+ * The exception that was thrown
*/
- @SuppressWarnings({ "rawtypes", "unchecked" })
+ @SuppressWarnings({"rawtypes", "unchecked"})
public void handleRequestException(Throwable exception) throws IOException
{
// skip handling of known exceptions if there are none configured
@@ -235,6 +232,10 @@ public class DefaultRequestExceptionHandler implements RequestExceptionHandler
{
logger.error(String.format("Processing of request failed with uncaught exception: %s", exception), exception);
+ // In the case where one of the contributed rules, above, changes the behavior, then we don't report the
+ // exception. This is just for exceptions that are going to be rendered, real failures.
+ exceptionReporter.reportException(exception);
+
// TAP5-233: Make sure the client knows that an error occurred.
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
@@ -249,7 +250,7 @@ public class DefaultRequestExceptionHandler implements RequestExceptionHandler
Page page = pageCache.get(pageName);
- ExceptionReporter rootComponent = (ExceptionReporter) page.getRootComponent();
+ org.apache.tapestry5.services.ExceptionReporter rootComponent = (org.apache.tapestry5.services.ExceptionReporter) page.getRootComponent();
// Let the page set up for the new exception.
@@ -262,7 +263,8 @@ public class DefaultRequestExceptionHandler implements RequestExceptionHandler
* Form exception context either from the name of the exception, or the context the exception contains if it's of type
* {@link ContextAwareException}
*
- * @param exception The exception that the context is formed for
+ * @param exception
+ * The exception that the context is formed for
* @return Returns an array of objects to be used as the exception context
*/
@SuppressWarnings({"unchecked", "rawtypes"})
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/511813f3/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/exceptions/ExceptionReporterImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/exceptions/ExceptionReporterImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/exceptions/ExceptionReporterImpl.java
new file mode 100644
index 0000000..f3dd3c5
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/exceptions/ExceptionReporterImpl.java
@@ -0,0 +1,340 @@
+// Licensed 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.tapestry5.internal.services.exceptions;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.tapestry5.SymbolConstants;
+import org.apache.tapestry5.func.F;
+import org.apache.tapestry5.func.Flow;
+import org.apache.tapestry5.func.Mapper;
+import org.apache.tapestry5.func.Reducer;
+import org.apache.tapestry5.internal.TapestryInternalUtils;
+import org.apache.tapestry5.ioc.annotations.Inject;
+import org.apache.tapestry5.ioc.annotations.Symbol;
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
+import org.apache.tapestry5.ioc.internal.util.InternalUtils;
+import org.apache.tapestry5.ioc.services.ExceptionAnalysis;
+import org.apache.tapestry5.ioc.services.ExceptionAnalyzer;
+import org.apache.tapestry5.ioc.services.ExceptionInfo;
+import org.apache.tapestry5.ioc.util.ExceptionUtils;
+import org.apache.tapestry5.services.Request;
+import org.apache.tapestry5.services.RequestGlobals;
+import org.apache.tapestry5.services.exceptions.ExceptionReporter;
+import org.slf4j.Logger;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.reflect.Array;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@SuppressWarnings("ResultOfMethodCallIgnored")
+public class ExceptionReporterImpl implements ExceptionReporter
+{
+ private static final Reducer<Integer, Integer> MAX = new Reducer<Integer, Integer>()
+ {
+ @Override
+ public Integer reduce(Integer accumulator, Integer element)
+ {
+ return Math.max(accumulator, element);
+ }
+ };
+
+ private static final Mapper<String, Integer> STRING_TO_LENGTH = new Mapper<String, Integer>()
+ {
+ @Override
+ public Integer map(String element)
+ {
+ return element.length();
+ }
+ };
+
+ @Inject
+ @Symbol(SymbolConstants.EXCEPTION_REPORTS_DIR)
+ private File logDir;
+
+ @Inject
+ @Symbol(SymbolConstants.CONTEXT_PATH)
+ private String contextPath;
+
+ @Inject
+ private ExceptionAnalyzer analyzer;
+
+ private final AtomicInteger uid = new AtomicInteger();
+
+ @Inject
+ private Logger logger;
+
+ @Inject
+ private RequestGlobals requestGlobals;
+
+ @Override
+ public void reportException(Throwable exception)
+ {
+ Date date = new Date();
+ String folderName = String.format("%tY/%<tm/%<td/%<tH/%<tM", date);
+ String fileName = String.format(
+ "exception-%tY%<tm%<td-%<tH%<tM%<tS-%<tL.%d.txt", date,
+ uid.getAndIncrement());
+
+ try
+ {
+ File folder = new File(logDir, folderName);
+ folder.mkdirs();
+
+ File log = new File(folder, fileName);
+
+ writeExceptionToFile(exception, log);
+
+ logger.warn(String.format("Wrote exception report to %s", toURI(log)));
+ } catch (Exception ex)
+ {
+ logger.error(String.format("Unable to write exception report %s: %s",
+ fileName, ExceptionUtils.toMessage(ex)));
+
+ logger.error("Original exception:", exception);
+ }
+ }
+
+ private String toURI(File file)
+ {
+ try
+ {
+ return file.toURI().toString();
+ } catch (Exception e)
+ {
+ return file.toString();
+ }
+ }
+
+ private void writeExceptionToFile(Throwable exception, File log) throws IOException
+ {
+ log.createNewFile();
+ ExceptionAnalysis analysis = analyzer.analyze(exception);
+ PrintWriter writer = null;
+ try
+ {
+ writer = new PrintWriter(log);
+ writeException(writer, analysis);
+ } finally
+ {
+ IOUtils.closeQuietly(writer);
+ }
+ }
+
+ interface PropertyWriter
+ {
+ void write(String name, Object value);
+ }
+
+ private final static Mapper<ExceptionInfo, Flow<String>> EXCEPTION_INFO_TO_PROPERTY_NAMES =
+ new Mapper<ExceptionInfo, Flow<String>>()
+ {
+ @Override
+ public Flow<String> map(ExceptionInfo element)
+ {
+ return F.flow(element.getPropertyNames());
+ }
+ };
+
+ private void writeException(final PrintWriter writer, ExceptionAnalysis analysis)
+ {
+ final Formatter f = new Formatter(writer);
+ writer.print("EXCEPTION STACK:\n\n");
+ Request request = requestGlobals.getRequest();
+
+ // Figure out what all the property names are so that we can set the width of the column that lists
+ // property names.
+ Flow<String> propertyNames = F.flow(analysis.getExceptionInfos())
+ .mapcat(EXCEPTION_INFO_TO_PROPERTY_NAMES).append("Exception type", "Message");
+
+ if (request != null)
+ {
+ propertyNames = propertyNames.concat(request.getParameterNames()).concat(request.getHeaderNames());
+ }
+
+ final int maxPropertyNameLength = propertyNames.map(STRING_TO_LENGTH).reduce(MAX, 0);
+
+ final String propertyNameFormat = " %" + maxPropertyNameLength + "s: %s\n";
+
+ PropertyWriter pw = new PropertyWriter()
+ {
+ @SuppressWarnings("rawtypes")
+ @Override
+ public void write(String name, Object value)
+ {
+ if (value.getClass().isArray())
+ {
+ write(name, toList(value));
+ return;
+ }
+
+ if (value instanceof Iterable)
+ {
+ boolean first = true;
+ Iterable iterable = (Iterable) value;
+ Iterator i = iterable.iterator();
+ while (i.hasNext())
+ {
+ if (first)
+ {
+ f.format(propertyNameFormat, name, i.next());
+ first = false;
+ } else
+ {
+ for (int j = 0; j < maxPropertyNameLength + 4; j++)
+ writer.write(' ');
+
+ writer.println(i.next());
+ }
+ }
+ return;
+ }
+
+ // TODO: Handling of arrays & collections
+ f.format(propertyNameFormat, name, value);
+ }
+
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ private List toList(Object array)
+ {
+ int count = Array.getLength(array);
+ List result = new ArrayList(count);
+ for (int i = 0; i < count; i++)
+ {
+ result.add(Array.get(array, i));
+ }
+ return result;
+ }
+ };
+
+ boolean first = true;
+
+ for (ExceptionInfo info : analysis.getExceptionInfos())
+ {
+ if (first)
+ {
+ writer.println();
+ first = false;
+ }
+ pw.write("Exception type", info.getClassName());
+ pw.write("Message", info.getMessage());
+ for (String name : info.getPropertyNames())
+ {
+ pw.write(name, info.getProperty(name));
+ }
+ if (!info.getStackTrace().isEmpty())
+ {
+ writer.write("\n Stack trace:\n");
+ for (StackTraceElement e : info.getStackTrace())
+ {
+ f.format(" - %s\n", e.toString());
+ }
+ }
+ writer.println();
+ }
+
+ if (request != null)
+ {
+ writer.print("REQUEST:\n\nBasic Information:\n");
+ List<String> flags = CollectionFactory.newList();
+ if (request.isXHR())
+ {
+ flags.add("XHR");
+ }
+ if (request.isRequestedSessionIdValid())
+ {
+ flags.add("requestedSessionIdValid");
+ }
+ if (request.isSecure())
+ {
+ flags.add("secure");
+ }
+ pw.write("contextPath", contextPath);
+ if (!flags.isEmpty())
+ {
+ pw.write("flags", InternalUtils.joinSorted(flags));
+ }
+ pw.write("method", request.getMethod());
+ pw.write("path", request.getPath());
+ pw.write("locale", request.getLocale());
+ pw.write("serverName", request.getServerName());
+ writer.print("\nHeaders:\n");
+ for (String name : request.getHeaderNames())
+ {
+ pw.write(name, request.getHeader(name));
+ }
+ if (!request.getParameterNames().isEmpty())
+ {
+ writer.print("\nParameters:\n");
+ for (String name : request.getParameterNames())
+ {
+ // TODO: Support multi-value parameters
+ pw.write(name, request.getParameters(name));
+ }
+ }
+ // TODO: Session if it exists
+ }
+
+ writer.print("\nSYSTEM INFORMATION:");
+
+ Runtime runtime = Runtime.getRuntime();
+
+ f.format("\n\nMemory:\n %,15d bytes free\n %,15d bytes total\n %,15d bytes max\n",
+ runtime.freeMemory(),
+ runtime.totalMemory(),
+ runtime.maxMemory());
+
+ Thread[] threads = TapestryInternalUtils.getAllThreads();
+
+ int maxThreadNameLength = 0;
+
+ for (Thread t : threads)
+ {
+ maxThreadNameLength = Math.max(maxThreadNameLength, t.getName().length());
+ }
+
+ String format = "\n%s %" + maxThreadNameLength + "s %s";
+
+ f.format("\n%,d Threads:", threads.length);
+
+ for (Thread t : threads)
+ {
+ f.format(format,
+ Thread.currentThread() == t ? "*" : " ",
+ t.getName(),
+ t.getState().name());
+ if (t.isDaemon())
+ {
+ writer.write(", daemon");
+ }
+ if (!t.isAlive())
+ {
+ writer.write(", NOT alive");
+ }
+ if (t.isInterrupted())
+ {
+ writer.write(", interrupted");
+ }
+ if (t.getPriority() != Thread.NORM_PRIORITY)
+ {
+ f.format(", priority %d", t.getPriority());
+ }
+ }
+ writer.println();
+
+ f.close();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/511813f3/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
index d13c974..78e1523 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
@@ -38,6 +38,7 @@ import org.apache.tapestry5.internal.services.*;
import org.apache.tapestry5.internal.services.ajax.AjaxFormUpdateFilter;
import org.apache.tapestry5.internal.services.ajax.AjaxResponseRendererImpl;
import org.apache.tapestry5.internal.services.ajax.MultiZoneUpdateEventResultProcessor;
+import org.apache.tapestry5.internal.services.exceptions.ExceptionReporterImpl;
import org.apache.tapestry5.internal.services.linktransform.LinkTransformerImpl;
import org.apache.tapestry5.internal.services.linktransform.LinkTransformerInterceptor;
import org.apache.tapestry5.internal.services.messages.PropertiesFileParserImpl;
@@ -370,6 +371,7 @@ public final class TapestryModule
binder.bind(PathConstructor.class, PathConstructorImpl.class);
binder.bind(DateUtilities.class, DateUtilitiesImpl.class);
binder.bind(PartialTemplateRenderer.class, PartialTemplateRendererImpl.class);
+ binder.bind(org.apache.tapestry5.services.exceptions.ExceptionReporter.class, ExceptionReporterImpl.class);
}
// ========================================================================
@@ -2126,6 +2128,7 @@ public final class TapestryModule
// TAP5-2187
configuration.add(SymbolConstants.STRICT_CSS_URL_REWRITING, "false");
+ configuration.add(SymbolConstants.EXCEPTION_REPORTS_DIR, "build/exceptions");
}
/**
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/511813f3/tapestry-core/src/main/java/org/apache/tapestry5/services/exceptions/ExceptionReporter.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/exceptions/ExceptionReporter.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/exceptions/ExceptionReporter.java
new file mode 100644
index 0000000..97a94ec
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/exceptions/ExceptionReporter.java
@@ -0,0 +1,33 @@
+// Licensed 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.tapestry5.services.exceptions;
+
+/**
+ * Services used to report request handling exceptions. This is invoked <em>before</em> the exception report page is rendered.
+ * The default implementation converts the exception into a well formatted text file, with content similar to the default
+ * {@link org.apache.tapestry5.corelib.pages.ExceptionReport} page, and stores this file on the file system.
+ * <p/>
+ * Exception report files are stored beneath a root directory, with intermediate folders for year, month, day, hour, and minute.
+ * <p/>
+ * Directories are created as necessary; however, there is nothing in place to delete exceptions.
+ *
+ * @see org.apache.tapestry5.SymbolConstants#EXCEPTION_REPORTS_DIR
+ * @since 5.4
+ */
+public interface ExceptionReporter
+{
+ /**
+ * Records the exception.
+ */
+ void reportException(Throwable exception);
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/511813f3/tapestry-core/src/main/java/org/apache/tapestry5/services/exceptions/package-info.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/exceptions/package-info.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/exceptions/package-info.java
new file mode 100644
index 0000000..20dfa5f
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/exceptions/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Services related to the generation of exception reports.
+ */
+package org.apache.tapestry5.services.exceptions;
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/511813f3/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/ExceptionReport.tml
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/ExceptionReport.tml b/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/ExceptionReport.tml
index 0190b80..9bd1f25 100644
--- a/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/ExceptionReport.tml
+++ b/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/ExceptionReport.tml
@@ -96,11 +96,12 @@
<h3>Threads</h3>
- <table class="table table-compact table-striped exception-report-threads">
+ <table class="table table-condensed table-hover table-striped exception-report-threads">
<thread>
<tr>
<th>Thread Name</th>
<th>Group Name</th>
+ <th>State</th>
<th>Flags</th>
</tr>
</thread>
@@ -109,6 +110,7 @@
<tr t:type="loop" source="threads" value="thread" class="${thread.className}">
<td class="thread-name">${thread.name}</td>
<td>${thread.group?.name}</td>
+ <td>${thread.state}</td>
<td>${thread.flags}</td>
</tr>
</tbody>
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/511813f3/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/DefaultRequestExceptionHandlerTest.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/DefaultRequestExceptionHandlerTest.java b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/DefaultRequestExceptionHandlerTest.java
index 4370020..ddab492 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/DefaultRequestExceptionHandlerTest.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/DefaultRequestExceptionHandlerTest.java
@@ -1,5 +1,3 @@
-// Copyright 2006, 2008, 2010, 2011 The Apache Software Foundation
-//
// Licensed 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
@@ -14,12 +12,6 @@
package org.apache.tapestry5.internal.services;
-import java.io.IOException;
-import java.security.AccessControlException;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
import org.apache.tapestry5.ContextAwareException;
import org.apache.tapestry5.ExceptionHandlerAssistant;
import org.apache.tapestry5.Link;
@@ -28,13 +20,21 @@ import org.apache.tapestry5.ioc.ServiceResources;
import org.apache.tapestry5.services.ComponentClassResolver;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.Response;
+import org.apache.tapestry5.services.exceptions.ExceptionReporter;
import org.easymock.EasyMock;
import org.slf4j.Logger;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
+import java.io.IOException;
+import java.security.AccessControlException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
@SuppressWarnings("serial")
-public class DefaultRequestExceptionHandlerTest extends InternalBaseTestCase {
+public class DefaultRequestExceptionHandlerTest extends InternalBaseTestCase
+{
private Map<Class, Object> mockConfiguration = new HashMap<Class, Object>();
RequestPageCache pageCache;
PageResponseRenderer renderer;
@@ -44,25 +44,31 @@ public class DefaultRequestExceptionHandlerTest extends InternalBaseTestCase {
ComponentClassResolver componentClassResolver;
LinkSource linkSource;
ServiceResources serviceResources;
+
private DefaultRequestExceptionHandler exceptionHandler;
- private static class MyContextAwareException extends Throwable implements ContextAwareException {
+
+ private static class MyContextAwareException extends Throwable implements ContextAwareException
+ {
private Object[] context;
- public MyContextAwareException(Object[] context) {
+ public MyContextAwareException(Object[] context)
+ {
this.context = context;
}
- public Object[] getContext() {
+ public Object[] getContext()
+ {
return context;
}
}
-
- private static class MyPage {
-
+
+ private static class MyPage
+ {
+
}
-
+
@BeforeMethod
public void setup_tests() throws Exception
{
@@ -75,28 +81,45 @@ public class DefaultRequestExceptionHandlerTest extends InternalBaseTestCase {
componentClassResolver = mockComponentClassResolver();
linkSource = mockLinkSource();
serviceResources = mockServiceResources();
- mockConfiguration.put(AccessControlException.class, MyPage.class);
- mockConfiguration.put(MyContextAwareException.class, new ExceptionHandlerAssistant() {
- public Object handleRequestException(Throwable exception, List<Object> exceptionContext)
- throws IOException {
- return null;
- }
- });
- exceptionHandler = new DefaultRequestExceptionHandler(pageCache, renderer, logger, "exceptionpage", request, response, componentClassResolver, linkSource, serviceResources, mockConfiguration);
+ mockConfiguration.put(AccessControlException.class, MyPage.class);
+ mockConfiguration.put(MyContextAwareException.class, new ExceptionHandlerAssistant()
+ {
+ public Object handleRequestException(Throwable exception, List<Object> exceptionContext)
+ throws IOException
+ {
+ return null;
+ }
+ });
+
+ ExceptionReporter noopExceptionReporter = new ExceptionReporter()
+ {
+ @Override
+ public void reportException(Throwable exception)
+ {
+
+ }
+ };
+
+ exceptionHandler = new DefaultRequestExceptionHandler(pageCache, renderer, logger, "exceptionpage", request, response, componentClassResolver, linkSource, serviceResources, noopExceptionReporter, mockConfiguration);
}
-
+
@Test
- public void noContextWhenExceptionDoesntContainMessage() {
- Object[] context = exceptionHandler.formExceptionContext(new RuntimeException() {
+ public void noContextWhenExceptionDoesntContainMessage()
+ {
+ Object[] context = exceptionHandler.formExceptionContext(new RuntimeException()
+ {
});
assertEquals(context.length, 0);
}
@Test
- public void contextIsExceptionMessage() {
- Object[] context = exceptionHandler.formExceptionContext(new RuntimeException() {
- public String getMessage() {
+ public void contextIsExceptionMessage()
+ {
+ Object[] context = exceptionHandler.formExceptionContext(new RuntimeException()
+ {
+ public String getMessage()
+ {
return "HelloWorld";
}
});
@@ -105,7 +128,8 @@ public class DefaultRequestExceptionHandlerTest extends InternalBaseTestCase {
}
@Test
- public void contextIsExceptionType() {
+ public void contextIsExceptionType()
+ {
Object[] context = exceptionHandler.formExceptionContext(new IllegalArgumentException("Value not allowed"));
assertEquals(context.length, 1);
assertTrue(context[0] instanceof String);
@@ -113,42 +137,47 @@ public class DefaultRequestExceptionHandlerTest extends InternalBaseTestCase {
}
@Test
- public void contextIsProvidedByContextAwareException() {
- Object[] sourceContext = new Object[] { new Integer(10), this };
+ public void contextIsProvidedByContextAwareException()
+ {
+ Object[] sourceContext = new Object[]{new Integer(10), this};
- Object[] context = exceptionHandler.formExceptionContext(new MyContextAwareException(sourceContext) {
+ Object[] context = exceptionHandler.formExceptionContext(new MyContextAwareException(sourceContext)
+ {
});
assertEquals(context, sourceContext);
}
-
+
@Test
- public void handleRequestExceptionWithConfiguredPage() throws IOException {
- train_resolvePageClassNameToPageName(componentClassResolver, MyPage.class.getName(), "mypage" );
+ public void handleRequestExceptionWithConfiguredPage() throws IOException
+ {
+ train_resolvePageClassNameToPageName(componentClassResolver, MyPage.class.getName(), "mypage");
Link link = mockLink();
expect(linkSource.createPageRenderLink("mypage", false, new Object[]{"accesscontrol"})).andReturn(link);
expect(request.isXHR()).andReturn(false);
response.sendRedirect(link);
EasyMock.expectLastCall();
replay();
-
+
exceptionHandler.handleRequestException(new AccessControlException("No permission"));
}
-
+
@Test
- public void handleRequestExceptionWithConfiguredAssistant() throws IOException {
- ExceptionHandlerAssistant assistant = new ExceptionHandlerAssistant() {
+ public void handleRequestExceptionWithConfiguredAssistant() throws IOException
+ {
+ ExceptionHandlerAssistant assistant = new ExceptionHandlerAssistant()
+ {
public Object handleRequestException(Throwable exception, List<Object> exceptionContext)
throws IOException
{
return null;
}
};
-
+
mockConfiguration.put(MyContextAwareException.class, assistant);
replay();
-
+
exceptionHandler.handleRequestException(new MyContextAwareException(new Object[]{}));
}
-
+
}
[3/3] git commit: Integrate an exception reporting service that will
write exception report text files to the file system
Posted by hl...@apache.org.
Integrate an exception reporting service that will write exception report text files to the file system
Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/ce6e66ea
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/ce6e66ea
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/ce6e66ea
Branch: refs/heads/master
Commit: ce6e66ea388455fea5363b718d8323299a601854
Parents: 511813f
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Fri May 30 16:57:31 2014 -0700
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Fri May 30 16:57:31 2014 -0700
----------------------------------------------------------------------
.../src/main/java/org/apache/tapestry5/SymbolConstants.java | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/ce6e66ea/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java b/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
index 6ab3759..4208479 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
@@ -488,12 +488,12 @@ public class SymbolConstants
* @see org.apache.tapestry5.services.exceptions.ExceptionReporter
* @since 5.4
*/
-
+
public static final String EXCEPTION_REPORTS_DIR = "tapestry.exception-reports-dir";
/**
- * Defines whether {@link CSSURLRewriter} will throw an exception when a CSS file
+ * Defines whether {@link org.apache.tapestry5.internal.services.assets.CSSURLRewriter} will throw an exception when a CSS file
* references an URL which doesn't exist. The default value is <code>false</code>.
- *
+ *
* @since 5.4
*/
public static final String STRICT_CSS_URL_REWRITING = "tapestry.strict-css-url-rewriting";
[2/3] git commit: Integrate an exception reporting service that will
write exception report text files to the file system
Posted by hl...@apache.org.
Integrate an exception reporting service that will write exception report text files to the file system
Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/511813f3
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/511813f3
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/511813f3
Branch: refs/heads/master
Commit: 511813f381c7724024cc818977e9a85f7eade22a
Parents: 08807a1
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Fri May 30 16:55:07 2014 -0700
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Fri May 30 16:57:01 2014 -0700
----------------------------------------------------------------------
54_RELEASE_NOTES.md | 4 +
.../org/apache/tapestry5/SymbolConstants.java | 14 +-
.../corelib/pages/ExceptionReport.java | 42 +--
.../internal/TapestryInternalUtils.java | 31 +-
.../DefaultRequestExceptionHandler.java | 56 +--
.../exceptions/ExceptionReporterImpl.java | 340 +++++++++++++++++++
.../tapestry5/modules/TapestryModule.java | 3 +
.../services/exceptions/ExceptionReporter.java | 33 ++
.../services/exceptions/package-info.java | 4 +
.../tapestry5/corelib/pages/ExceptionReport.tml | 4 +-
.../DefaultRequestExceptionHandlerTest.java | 117 ++++---
11 files changed, 535 insertions(+), 113 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/511813f3/54_RELEASE_NOTES.md
----------------------------------------------------------------------
diff --git a/54_RELEASE_NOTES.md b/54_RELEASE_NOTES.md
index 23fc912..eda173b 100644
--- a/54_RELEASE_NOTES.md
+++ b/54_RELEASE_NOTES.md
@@ -159,6 +159,10 @@ resurface in the future as a CSS expression, but is currently not supported.
The default exception report page has been modified to display a list of threads.
+## ExceptionReporter Service
+
+A new service, `ExceptionReporter`, will now create a text file on the file system for each runtime request processing exception.
+
## Symbol tapestry.asset-path-prefix
The definition of the symbol 'tapestry.asset-path-prefix' has changed; it no longer includes the leading and trailing
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/511813f3/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java b/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
index d2526ae..6ab3759 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
@@ -1,5 +1,3 @@
-// Copyright 2008-2014 The Apache Software Foundation
-//
// Licensed 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
@@ -474,15 +472,25 @@ public class SymbolConstants
* @since 5.4
*/
public static final String FORM_FIELD_CSS_CLASS = "tapestry.form-field-css-class";
-
+
/**
* Defines whether {@link java.text.DateFormat} instances created by Tapestry should be
* lenient or not by default. The default value is <code>false</code>.
+ *
* @since 5.4
*/
public static final String LENIENT_DATE_FORMAT = "tapestry.lenient-date-format";
/**
+ * The directory to which exception report files should be written. The default is appropriate
+ * for development: {@code build/exceptions}, and should be changed for production.
+ *
+ * @see org.apache.tapestry5.services.exceptions.ExceptionReporter
+ * @since 5.4
+ */
+
+ public static final String EXCEPTION_REPORTS_DIR = "tapestry.exception-reports-dir";
+ /**
* Defines whether {@link CSSURLRewriter} will throw an exception when a CSS file
* references an URL which doesn't exist. The default value is <code>false</code>.
*
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/511813f3/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ExceptionReport.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ExceptionReport.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ExceptionReport.java
index 1ca7e25..bdc853a 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ExceptionReport.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/pages/ExceptionReport.java
@@ -1,5 +1,3 @@
-// Copyright 2006-2013 The Apache Software Foundation
-//
// Licensed 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
@@ -24,6 +22,7 @@ import org.apache.tapestry5.annotations.UnknownActivationContextCheck;
import org.apache.tapestry5.func.F;
import org.apache.tapestry5.func.Mapper;
import org.apache.tapestry5.internal.InternalConstants;
+import org.apache.tapestry5.internal.TapestryInternalUtils;
import org.apache.tapestry5.internal.services.PageActivationContextCollector;
import org.apache.tapestry5.internal.services.ReloadHelper;
import org.apache.tapestry5.ioc.annotations.Inject;
@@ -34,7 +33,6 @@ import org.apache.tapestry5.services.*;
import java.net.MalformedURLException;
import java.net.URL;
-import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
@@ -115,14 +113,15 @@ public class ExceptionReport implements ExceptionReporter
public class ThreadInfo implements Comparable<ThreadInfo>
{
- public final String className, name, flags;
+ public final String className, name, state, flags;
public final ThreadGroup group;
- public ThreadInfo(String className, String name, String flags, ThreadGroup group)
+ public ThreadInfo(String className, String name, String state, String flags, ThreadGroup group)
{
this.className = className;
this.name = name;
+ this.state = state;
this.flags = flags;
this.group = group;
}
@@ -212,39 +211,9 @@ public class ExceptionReport implements ExceptionReporter
return getPropertyValue().split(pathSeparator);
}
- private Thread[] assembleThreads()
- {
- ThreadGroup rootGroup = Thread.currentThread().getThreadGroup();
-
- while (true)
- {
- ThreadGroup parentGroup = rootGroup.getParent();
- if (parentGroup == null)
- {
- break;
- }
- rootGroup = parentGroup;
- }
-
- Thread[] threads = new Thread[rootGroup.activeCount()];
-
- while (true)
- {
- // A really ugly API. threads.length must be larger than
- // the actual number of threads, just so we can determine
- // if we're done.
- int count = rootGroup.enumerate(threads, true);
- if (count < threads.length)
- {
- return Arrays.copyOf(threads, count);
- }
- threads = new Thread[threads.length * 2];
- }
- }
-
public List<ThreadInfo> getThreads()
{
- return F.flow(assembleThreads()).map(new Mapper<Thread, ThreadInfo>()
+ return F.flow(TapestryInternalUtils.getAllThreads()).map(new Mapper<Thread, ThreadInfo>()
{
@Override
public ThreadInfo map(Thread t)
@@ -271,6 +240,7 @@ public class ExceptionReport implements ExceptionReporter
return new ThreadInfo(Thread.currentThread() == t ? "active-thread" : "",
t.getName(),
+ t.getState().name(),
InternalUtils.join(flags),
t.getThreadGroup());
}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/511813f3/tapestry-core/src/main/java/org/apache/tapestry5/internal/TapestryInternalUtils.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/TapestryInternalUtils.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/TapestryInternalUtils.java
index 6c6138b..3435d64 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/TapestryInternalUtils.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/TapestryInternalUtils.java
@@ -1,5 +1,3 @@
-// Copyright 2006-2013 The Apache Software Foundation
-//
// Licensed 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
@@ -33,6 +31,7 @@ import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.ref.Reference;
import java.lang.reflect.Type;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
@@ -587,5 +586,33 @@ public class TapestryInternalUtils
return ref == null ? null : ref.get();
}
+
+ /**
+ * Gathers together an array containing all the threads.
+ * @since 5.4 */
+ public static Thread[] getAllThreads() {
+ ThreadGroup rootGroup = Thread.currentThread().getThreadGroup();
+
+ while (true) {
+ ThreadGroup parentGroup = rootGroup.getParent();
+ if (parentGroup == null) {
+ break;
+ }
+ rootGroup = parentGroup;
+ }
+
+ Thread[] threads = new Thread[rootGroup.activeCount()];
+
+ while (true) {
+ // A really ugly API. threads.length must be larger than
+ // the actual number of threads, just so we can determine
+ // if we're done.
+ int count = rootGroup.enumerate(threads, true);
+ if (count < threads.length) {
+ return Arrays.copyOf(threads, count);
+ }
+ threads = new Thread[threads.length * 2];
+ }
+ }
}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/511813f3/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DefaultRequestExceptionHandler.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DefaultRequestExceptionHandler.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DefaultRequestExceptionHandler.java
index 70f72cb..de6b50b 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DefaultRequestExceptionHandler.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DefaultRequestExceptionHandler.java
@@ -1,5 +1,3 @@
-// Copyright 2006-2013 The Apache Software Foundation
-//
// Licensed 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
@@ -44,14 +42,14 @@ import java.util.Map.Entry;
* servlet spec's standard error handling, the default exception handler allows configuring handlers for specific types of
* exceptions. The error-page/exception-type configuration in web.xml does not work in Tapestry application as errors are
* wrapped in Tapestry's exception types (see {@link OperationException} and {@link ComponentEventException} ).
- *
+ * <p/>
* Configurations are flexible. You can either contribute a {@link ExceptionHandlerAssistant} to use arbitrary complex logic
* for error handling or a page class to render for the specific exception. Additionally, exceptions can carry context for the
* error page. Exception context is formed either from the name of Exception (e.g. SmtpNotRespondingException -> ServiceFailure mapping
* would render a page with URL /servicefailure/smtpnotresponding) or they can implement {@link ContextAwareException} interface.
- *
+ * <p/>
* If no configured exception type is found, the default exception page {@link SymbolConstants#EXCEPTION_REPORT_PAGE} is rendered.
- * This fallback exception page must implement the {@link ExceptionReporter} interface.
+ * This fallback exception page must implement the {@link org.apache.tapestry5.services.ExceptionReporter} interface.
*/
public class DefaultRequestExceptionHandler implements RequestExceptionHandler
{
@@ -71,30 +69,28 @@ public class DefaultRequestExceptionHandler implements RequestExceptionHandler
private final LinkSource linkSource;
+ private final org.apache.tapestry5.services.exceptions.ExceptionReporter exceptionReporter;
+
// should be Class<? extends Throwable>, Object but it's not allowed to configure subtypes
private final Map<Class, Object> configuration;
/**
- * @param pageCache
- * @param renderer
- * @param logger
- * @param pageName
- * @param request
- * @param response
- * @param componentClassResolver
- * @param linkSource
- * @param serviceResources
- * @param configuration A map of Exception class and handler values. A handler is either a page class or an ExceptionHandlerAssistant. ExceptionHandlerAssistant can be a class
- * in which case the instance is autobuilt.
+ * @param configuration
+ * A map of Exception class and handler values. A handler is either a page class or an ExceptionHandlerAssistant. ExceptionHandlerAssistant can be a class
*/
@SuppressWarnings("rawtypes")
- public DefaultRequestExceptionHandler(RequestPageCache pageCache, PageResponseRenderer renderer, Logger logger,
-
+ public DefaultRequestExceptionHandler(RequestPageCache pageCache,
+ PageResponseRenderer renderer,
+ Logger logger,
@Symbol(SymbolConstants.EXCEPTION_REPORT_PAGE)
String pageName,
-
- Request request, Response response, ComponentClassResolver componentClassResolver,
- LinkSource linkSource, ServiceResources serviceResources, Map<Class, Object> configuration)
+ Request request,
+ Response response,
+ ComponentClassResolver componentClassResolver,
+ LinkSource linkSource,
+ ServiceResources serviceResources,
+ org.apache.tapestry5.services.exceptions.ExceptionReporter exceptionReporter,
+ Map<Class, Object> configuration)
{
this.pageCache = pageCache;
this.renderer = renderer;
@@ -104,6 +100,7 @@ public class DefaultRequestExceptionHandler implements RequestExceptionHandler
this.response = response;
this.componentClassResolver = componentClassResolver;
this.linkSource = linkSource;
+ this.exceptionReporter = exceptionReporter;
Map<Class<ExceptionHandlerAssistant>, ExceptionHandlerAssistant> handlerAssistants = new HashMap<Class<ExceptionHandlerAssistant>, ExceptionHandlerAssistant>();
@@ -131,14 +128,14 @@ public class DefaultRequestExceptionHandler implements RequestExceptionHandler
/**
* Handles the exception thrown at some point the request was being processed
- *
+ * <p/>
* First checks if there was a specific exception handler/page configured for this exception type, it's super class or super-super class.
* Renders the default exception page if none was configured.
*
- * @param exception The exception that was thrown
- *
+ * @param exception
+ * The exception that was thrown
*/
- @SuppressWarnings({ "rawtypes", "unchecked" })
+ @SuppressWarnings({"rawtypes", "unchecked"})
public void handleRequestException(Throwable exception) throws IOException
{
// skip handling of known exceptions if there are none configured
@@ -235,6 +232,10 @@ public class DefaultRequestExceptionHandler implements RequestExceptionHandler
{
logger.error(String.format("Processing of request failed with uncaught exception: %s", exception), exception);
+ // In the case where one of the contributed rules, above, changes the behavior, then we don't report the
+ // exception. This is just for exceptions that are going to be rendered, real failures.
+ exceptionReporter.reportException(exception);
+
// TAP5-233: Make sure the client knows that an error occurred.
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
@@ -249,7 +250,7 @@ public class DefaultRequestExceptionHandler implements RequestExceptionHandler
Page page = pageCache.get(pageName);
- ExceptionReporter rootComponent = (ExceptionReporter) page.getRootComponent();
+ org.apache.tapestry5.services.ExceptionReporter rootComponent = (org.apache.tapestry5.services.ExceptionReporter) page.getRootComponent();
// Let the page set up for the new exception.
@@ -262,7 +263,8 @@ public class DefaultRequestExceptionHandler implements RequestExceptionHandler
* Form exception context either from the name of the exception, or the context the exception contains if it's of type
* {@link ContextAwareException}
*
- * @param exception The exception that the context is formed for
+ * @param exception
+ * The exception that the context is formed for
* @return Returns an array of objects to be used as the exception context
*/
@SuppressWarnings({"unchecked", "rawtypes"})
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/511813f3/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/exceptions/ExceptionReporterImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/exceptions/ExceptionReporterImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/exceptions/ExceptionReporterImpl.java
new file mode 100644
index 0000000..f3dd3c5
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/exceptions/ExceptionReporterImpl.java
@@ -0,0 +1,340 @@
+// Licensed 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.tapestry5.internal.services.exceptions;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.tapestry5.SymbolConstants;
+import org.apache.tapestry5.func.F;
+import org.apache.tapestry5.func.Flow;
+import org.apache.tapestry5.func.Mapper;
+import org.apache.tapestry5.func.Reducer;
+import org.apache.tapestry5.internal.TapestryInternalUtils;
+import org.apache.tapestry5.ioc.annotations.Inject;
+import org.apache.tapestry5.ioc.annotations.Symbol;
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
+import org.apache.tapestry5.ioc.internal.util.InternalUtils;
+import org.apache.tapestry5.ioc.services.ExceptionAnalysis;
+import org.apache.tapestry5.ioc.services.ExceptionAnalyzer;
+import org.apache.tapestry5.ioc.services.ExceptionInfo;
+import org.apache.tapestry5.ioc.util.ExceptionUtils;
+import org.apache.tapestry5.services.Request;
+import org.apache.tapestry5.services.RequestGlobals;
+import org.apache.tapestry5.services.exceptions.ExceptionReporter;
+import org.slf4j.Logger;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.reflect.Array;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@SuppressWarnings("ResultOfMethodCallIgnored")
+public class ExceptionReporterImpl implements ExceptionReporter
+{
+ private static final Reducer<Integer, Integer> MAX = new Reducer<Integer, Integer>()
+ {
+ @Override
+ public Integer reduce(Integer accumulator, Integer element)
+ {
+ return Math.max(accumulator, element);
+ }
+ };
+
+ private static final Mapper<String, Integer> STRING_TO_LENGTH = new Mapper<String, Integer>()
+ {
+ @Override
+ public Integer map(String element)
+ {
+ return element.length();
+ }
+ };
+
+ @Inject
+ @Symbol(SymbolConstants.EXCEPTION_REPORTS_DIR)
+ private File logDir;
+
+ @Inject
+ @Symbol(SymbolConstants.CONTEXT_PATH)
+ private String contextPath;
+
+ @Inject
+ private ExceptionAnalyzer analyzer;
+
+ private final AtomicInteger uid = new AtomicInteger();
+
+ @Inject
+ private Logger logger;
+
+ @Inject
+ private RequestGlobals requestGlobals;
+
+ @Override
+ public void reportException(Throwable exception)
+ {
+ Date date = new Date();
+ String folderName = String.format("%tY/%<tm/%<td/%<tH/%<tM", date);
+ String fileName = String.format(
+ "exception-%tY%<tm%<td-%<tH%<tM%<tS-%<tL.%d.txt", date,
+ uid.getAndIncrement());
+
+ try
+ {
+ File folder = new File(logDir, folderName);
+ folder.mkdirs();
+
+ File log = new File(folder, fileName);
+
+ writeExceptionToFile(exception, log);
+
+ logger.warn(String.format("Wrote exception report to %s", toURI(log)));
+ } catch (Exception ex)
+ {
+ logger.error(String.format("Unable to write exception report %s: %s",
+ fileName, ExceptionUtils.toMessage(ex)));
+
+ logger.error("Original exception:", exception);
+ }
+ }
+
+ private String toURI(File file)
+ {
+ try
+ {
+ return file.toURI().toString();
+ } catch (Exception e)
+ {
+ return file.toString();
+ }
+ }
+
+ private void writeExceptionToFile(Throwable exception, File log) throws IOException
+ {
+ log.createNewFile();
+ ExceptionAnalysis analysis = analyzer.analyze(exception);
+ PrintWriter writer = null;
+ try
+ {
+ writer = new PrintWriter(log);
+ writeException(writer, analysis);
+ } finally
+ {
+ IOUtils.closeQuietly(writer);
+ }
+ }
+
+ interface PropertyWriter
+ {
+ void write(String name, Object value);
+ }
+
+ private final static Mapper<ExceptionInfo, Flow<String>> EXCEPTION_INFO_TO_PROPERTY_NAMES =
+ new Mapper<ExceptionInfo, Flow<String>>()
+ {
+ @Override
+ public Flow<String> map(ExceptionInfo element)
+ {
+ return F.flow(element.getPropertyNames());
+ }
+ };
+
+ private void writeException(final PrintWriter writer, ExceptionAnalysis analysis)
+ {
+ final Formatter f = new Formatter(writer);
+ writer.print("EXCEPTION STACK:\n\n");
+ Request request = requestGlobals.getRequest();
+
+ // Figure out what all the property names are so that we can set the width of the column that lists
+ // property names.
+ Flow<String> propertyNames = F.flow(analysis.getExceptionInfos())
+ .mapcat(EXCEPTION_INFO_TO_PROPERTY_NAMES).append("Exception type", "Message");
+
+ if (request != null)
+ {
+ propertyNames = propertyNames.concat(request.getParameterNames()).concat(request.getHeaderNames());
+ }
+
+ final int maxPropertyNameLength = propertyNames.map(STRING_TO_LENGTH).reduce(MAX, 0);
+
+ final String propertyNameFormat = " %" + maxPropertyNameLength + "s: %s\n";
+
+ PropertyWriter pw = new PropertyWriter()
+ {
+ @SuppressWarnings("rawtypes")
+ @Override
+ public void write(String name, Object value)
+ {
+ if (value.getClass().isArray())
+ {
+ write(name, toList(value));
+ return;
+ }
+
+ if (value instanceof Iterable)
+ {
+ boolean first = true;
+ Iterable iterable = (Iterable) value;
+ Iterator i = iterable.iterator();
+ while (i.hasNext())
+ {
+ if (first)
+ {
+ f.format(propertyNameFormat, name, i.next());
+ first = false;
+ } else
+ {
+ for (int j = 0; j < maxPropertyNameLength + 4; j++)
+ writer.write(' ');
+
+ writer.println(i.next());
+ }
+ }
+ return;
+ }
+
+ // TODO: Handling of arrays & collections
+ f.format(propertyNameFormat, name, value);
+ }
+
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ private List toList(Object array)
+ {
+ int count = Array.getLength(array);
+ List result = new ArrayList(count);
+ for (int i = 0; i < count; i++)
+ {
+ result.add(Array.get(array, i));
+ }
+ return result;
+ }
+ };
+
+ boolean first = true;
+
+ for (ExceptionInfo info : analysis.getExceptionInfos())
+ {
+ if (first)
+ {
+ writer.println();
+ first = false;
+ }
+ pw.write("Exception type", info.getClassName());
+ pw.write("Message", info.getMessage());
+ for (String name : info.getPropertyNames())
+ {
+ pw.write(name, info.getProperty(name));
+ }
+ if (!info.getStackTrace().isEmpty())
+ {
+ writer.write("\n Stack trace:\n");
+ for (StackTraceElement e : info.getStackTrace())
+ {
+ f.format(" - %s\n", e.toString());
+ }
+ }
+ writer.println();
+ }
+
+ if (request != null)
+ {
+ writer.print("REQUEST:\n\nBasic Information:\n");
+ List<String> flags = CollectionFactory.newList();
+ if (request.isXHR())
+ {
+ flags.add("XHR");
+ }
+ if (request.isRequestedSessionIdValid())
+ {
+ flags.add("requestedSessionIdValid");
+ }
+ if (request.isSecure())
+ {
+ flags.add("secure");
+ }
+ pw.write("contextPath", contextPath);
+ if (!flags.isEmpty())
+ {
+ pw.write("flags", InternalUtils.joinSorted(flags));
+ }
+ pw.write("method", request.getMethod());
+ pw.write("path", request.getPath());
+ pw.write("locale", request.getLocale());
+ pw.write("serverName", request.getServerName());
+ writer.print("\nHeaders:\n");
+ for (String name : request.getHeaderNames())
+ {
+ pw.write(name, request.getHeader(name));
+ }
+ if (!request.getParameterNames().isEmpty())
+ {
+ writer.print("\nParameters:\n");
+ for (String name : request.getParameterNames())
+ {
+ // TODO: Support multi-value parameters
+ pw.write(name, request.getParameters(name));
+ }
+ }
+ // TODO: Session if it exists
+ }
+
+ writer.print("\nSYSTEM INFORMATION:");
+
+ Runtime runtime = Runtime.getRuntime();
+
+ f.format("\n\nMemory:\n %,15d bytes free\n %,15d bytes total\n %,15d bytes max\n",
+ runtime.freeMemory(),
+ runtime.totalMemory(),
+ runtime.maxMemory());
+
+ Thread[] threads = TapestryInternalUtils.getAllThreads();
+
+ int maxThreadNameLength = 0;
+
+ for (Thread t : threads)
+ {
+ maxThreadNameLength = Math.max(maxThreadNameLength, t.getName().length());
+ }
+
+ String format = "\n%s %" + maxThreadNameLength + "s %s";
+
+ f.format("\n%,d Threads:", threads.length);
+
+ for (Thread t : threads)
+ {
+ f.format(format,
+ Thread.currentThread() == t ? "*" : " ",
+ t.getName(),
+ t.getState().name());
+ if (t.isDaemon())
+ {
+ writer.write(", daemon");
+ }
+ if (!t.isAlive())
+ {
+ writer.write(", NOT alive");
+ }
+ if (t.isInterrupted())
+ {
+ writer.write(", interrupted");
+ }
+ if (t.getPriority() != Thread.NORM_PRIORITY)
+ {
+ f.format(", priority %d", t.getPriority());
+ }
+ }
+ writer.println();
+
+ f.close();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/511813f3/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
index d13c974..78e1523 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
@@ -38,6 +38,7 @@ import org.apache.tapestry5.internal.services.*;
import org.apache.tapestry5.internal.services.ajax.AjaxFormUpdateFilter;
import org.apache.tapestry5.internal.services.ajax.AjaxResponseRendererImpl;
import org.apache.tapestry5.internal.services.ajax.MultiZoneUpdateEventResultProcessor;
+import org.apache.tapestry5.internal.services.exceptions.ExceptionReporterImpl;
import org.apache.tapestry5.internal.services.linktransform.LinkTransformerImpl;
import org.apache.tapestry5.internal.services.linktransform.LinkTransformerInterceptor;
import org.apache.tapestry5.internal.services.messages.PropertiesFileParserImpl;
@@ -370,6 +371,7 @@ public final class TapestryModule
binder.bind(PathConstructor.class, PathConstructorImpl.class);
binder.bind(DateUtilities.class, DateUtilitiesImpl.class);
binder.bind(PartialTemplateRenderer.class, PartialTemplateRendererImpl.class);
+ binder.bind(org.apache.tapestry5.services.exceptions.ExceptionReporter.class, ExceptionReporterImpl.class);
}
// ========================================================================
@@ -2126,6 +2128,7 @@ public final class TapestryModule
// TAP5-2187
configuration.add(SymbolConstants.STRICT_CSS_URL_REWRITING, "false");
+ configuration.add(SymbolConstants.EXCEPTION_REPORTS_DIR, "build/exceptions");
}
/**
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/511813f3/tapestry-core/src/main/java/org/apache/tapestry5/services/exceptions/ExceptionReporter.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/exceptions/ExceptionReporter.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/exceptions/ExceptionReporter.java
new file mode 100644
index 0000000..97a94ec
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/exceptions/ExceptionReporter.java
@@ -0,0 +1,33 @@
+// Licensed 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.tapestry5.services.exceptions;
+
+/**
+ * Services used to report request handling exceptions. This is invoked <em>before</em> the exception report page is rendered.
+ * The default implementation converts the exception into a well formatted text file, with content similar to the default
+ * {@link org.apache.tapestry5.corelib.pages.ExceptionReport} page, and stores this file on the file system.
+ * <p/>
+ * Exception report files are stored beneath a root directory, with intermediate folders for year, month, day, hour, and minute.
+ * <p/>
+ * Directories are created as necessary; however, there is nothing in place to delete exceptions.
+ *
+ * @see org.apache.tapestry5.SymbolConstants#EXCEPTION_REPORTS_DIR
+ * @since 5.4
+ */
+public interface ExceptionReporter
+{
+ /**
+ * Records the exception.
+ */
+ void reportException(Throwable exception);
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/511813f3/tapestry-core/src/main/java/org/apache/tapestry5/services/exceptions/package-info.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/exceptions/package-info.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/exceptions/package-info.java
new file mode 100644
index 0000000..20dfa5f
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/exceptions/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Services related to the generation of exception reports.
+ */
+package org.apache.tapestry5.services.exceptions;
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/511813f3/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/ExceptionReport.tml
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/ExceptionReport.tml b/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/ExceptionReport.tml
index 0190b80..9bd1f25 100644
--- a/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/ExceptionReport.tml
+++ b/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/pages/ExceptionReport.tml
@@ -96,11 +96,12 @@
<h3>Threads</h3>
- <table class="table table-compact table-striped exception-report-threads">
+ <table class="table table-condensed table-hover table-striped exception-report-threads">
<thread>
<tr>
<th>Thread Name</th>
<th>Group Name</th>
+ <th>State</th>
<th>Flags</th>
</tr>
</thread>
@@ -109,6 +110,7 @@
<tr t:type="loop" source="threads" value="thread" class="${thread.className}">
<td class="thread-name">${thread.name}</td>
<td>${thread.group?.name}</td>
+ <td>${thread.state}</td>
<td>${thread.flags}</td>
</tr>
</tbody>
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/511813f3/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/DefaultRequestExceptionHandlerTest.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/DefaultRequestExceptionHandlerTest.java b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/DefaultRequestExceptionHandlerTest.java
index 4370020..ddab492 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/DefaultRequestExceptionHandlerTest.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/DefaultRequestExceptionHandlerTest.java
@@ -1,5 +1,3 @@
-// Copyright 2006, 2008, 2010, 2011 The Apache Software Foundation
-//
// Licensed 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
@@ -14,12 +12,6 @@
package org.apache.tapestry5.internal.services;
-import java.io.IOException;
-import java.security.AccessControlException;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
import org.apache.tapestry5.ContextAwareException;
import org.apache.tapestry5.ExceptionHandlerAssistant;
import org.apache.tapestry5.Link;
@@ -28,13 +20,21 @@ import org.apache.tapestry5.ioc.ServiceResources;
import org.apache.tapestry5.services.ComponentClassResolver;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.Response;
+import org.apache.tapestry5.services.exceptions.ExceptionReporter;
import org.easymock.EasyMock;
import org.slf4j.Logger;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
+import java.io.IOException;
+import java.security.AccessControlException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
@SuppressWarnings("serial")
-public class DefaultRequestExceptionHandlerTest extends InternalBaseTestCase {
+public class DefaultRequestExceptionHandlerTest extends InternalBaseTestCase
+{
private Map<Class, Object> mockConfiguration = new HashMap<Class, Object>();
RequestPageCache pageCache;
PageResponseRenderer renderer;
@@ -44,25 +44,31 @@ public class DefaultRequestExceptionHandlerTest extends InternalBaseTestCase {
ComponentClassResolver componentClassResolver;
LinkSource linkSource;
ServiceResources serviceResources;
+
private DefaultRequestExceptionHandler exceptionHandler;
- private static class MyContextAwareException extends Throwable implements ContextAwareException {
+
+ private static class MyContextAwareException extends Throwable implements ContextAwareException
+ {
private Object[] context;
- public MyContextAwareException(Object[] context) {
+ public MyContextAwareException(Object[] context)
+ {
this.context = context;
}
- public Object[] getContext() {
+ public Object[] getContext()
+ {
return context;
}
}
-
- private static class MyPage {
-
+
+ private static class MyPage
+ {
+
}
-
+
@BeforeMethod
public void setup_tests() throws Exception
{
@@ -75,28 +81,45 @@ public class DefaultRequestExceptionHandlerTest extends InternalBaseTestCase {
componentClassResolver = mockComponentClassResolver();
linkSource = mockLinkSource();
serviceResources = mockServiceResources();
- mockConfiguration.put(AccessControlException.class, MyPage.class);
- mockConfiguration.put(MyContextAwareException.class, new ExceptionHandlerAssistant() {
- public Object handleRequestException(Throwable exception, List<Object> exceptionContext)
- throws IOException {
- return null;
- }
- });
- exceptionHandler = new DefaultRequestExceptionHandler(pageCache, renderer, logger, "exceptionpage", request, response, componentClassResolver, linkSource, serviceResources, mockConfiguration);
+ mockConfiguration.put(AccessControlException.class, MyPage.class);
+ mockConfiguration.put(MyContextAwareException.class, new ExceptionHandlerAssistant()
+ {
+ public Object handleRequestException(Throwable exception, List<Object> exceptionContext)
+ throws IOException
+ {
+ return null;
+ }
+ });
+
+ ExceptionReporter noopExceptionReporter = new ExceptionReporter()
+ {
+ @Override
+ public void reportException(Throwable exception)
+ {
+
+ }
+ };
+
+ exceptionHandler = new DefaultRequestExceptionHandler(pageCache, renderer, logger, "exceptionpage", request, response, componentClassResolver, linkSource, serviceResources, noopExceptionReporter, mockConfiguration);
}
-
+
@Test
- public void noContextWhenExceptionDoesntContainMessage() {
- Object[] context = exceptionHandler.formExceptionContext(new RuntimeException() {
+ public void noContextWhenExceptionDoesntContainMessage()
+ {
+ Object[] context = exceptionHandler.formExceptionContext(new RuntimeException()
+ {
});
assertEquals(context.length, 0);
}
@Test
- public void contextIsExceptionMessage() {
- Object[] context = exceptionHandler.formExceptionContext(new RuntimeException() {
- public String getMessage() {
+ public void contextIsExceptionMessage()
+ {
+ Object[] context = exceptionHandler.formExceptionContext(new RuntimeException()
+ {
+ public String getMessage()
+ {
return "HelloWorld";
}
});
@@ -105,7 +128,8 @@ public class DefaultRequestExceptionHandlerTest extends InternalBaseTestCase {
}
@Test
- public void contextIsExceptionType() {
+ public void contextIsExceptionType()
+ {
Object[] context = exceptionHandler.formExceptionContext(new IllegalArgumentException("Value not allowed"));
assertEquals(context.length, 1);
assertTrue(context[0] instanceof String);
@@ -113,42 +137,47 @@ public class DefaultRequestExceptionHandlerTest extends InternalBaseTestCase {
}
@Test
- public void contextIsProvidedByContextAwareException() {
- Object[] sourceContext = new Object[] { new Integer(10), this };
+ public void contextIsProvidedByContextAwareException()
+ {
+ Object[] sourceContext = new Object[]{new Integer(10), this};
- Object[] context = exceptionHandler.formExceptionContext(new MyContextAwareException(sourceContext) {
+ Object[] context = exceptionHandler.formExceptionContext(new MyContextAwareException(sourceContext)
+ {
});
assertEquals(context, sourceContext);
}
-
+
@Test
- public void handleRequestExceptionWithConfiguredPage() throws IOException {
- train_resolvePageClassNameToPageName(componentClassResolver, MyPage.class.getName(), "mypage" );
+ public void handleRequestExceptionWithConfiguredPage() throws IOException
+ {
+ train_resolvePageClassNameToPageName(componentClassResolver, MyPage.class.getName(), "mypage");
Link link = mockLink();
expect(linkSource.createPageRenderLink("mypage", false, new Object[]{"accesscontrol"})).andReturn(link);
expect(request.isXHR()).andReturn(false);
response.sendRedirect(link);
EasyMock.expectLastCall();
replay();
-
+
exceptionHandler.handleRequestException(new AccessControlException("No permission"));
}
-
+
@Test
- public void handleRequestExceptionWithConfiguredAssistant() throws IOException {
- ExceptionHandlerAssistant assistant = new ExceptionHandlerAssistant() {
+ public void handleRequestExceptionWithConfiguredAssistant() throws IOException
+ {
+ ExceptionHandlerAssistant assistant = new ExceptionHandlerAssistant()
+ {
public Object handleRequestException(Throwable exception, List<Object> exceptionContext)
throws IOException
{
return null;
}
};
-
+
mockConfiguration.put(MyContextAwareException.class, assistant);
replay();
-
+
exceptionHandler.handleRequestException(new MyContextAwareException(new Object[]{}));
}
-
+
}
[3/3] git commit: Integrate an exception reporting service that will
write exception report text files to the file system
Posted by hl...@apache.org.
Integrate an exception reporting service that will write exception report text files to the file system
Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/ce6e66ea
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/ce6e66ea
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/ce6e66ea
Branch: refs/heads/master
Commit: ce6e66ea388455fea5363b718d8323299a601854
Parents: 511813f
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Fri May 30 16:57:31 2014 -0700
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Fri May 30 16:57:31 2014 -0700
----------------------------------------------------------------------
.../src/main/java/org/apache/tapestry5/SymbolConstants.java | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/ce6e66ea/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java b/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
index 6ab3759..4208479 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
@@ -488,12 +488,12 @@ public class SymbolConstants
* @see org.apache.tapestry5.services.exceptions.ExceptionReporter
* @since 5.4
*/
-
+
public static final String EXCEPTION_REPORTS_DIR = "tapestry.exception-reports-dir";
/**
- * Defines whether {@link CSSURLRewriter} will throw an exception when a CSS file
+ * Defines whether {@link org.apache.tapestry5.internal.services.assets.CSSURLRewriter} will throw an exception when a CSS file
* references an URL which doesn't exist. The default value is <code>false</code>.
- *
+ *
* @since 5.4
*/
public static final String STRICT_CSS_URL_REWRITING = "tapestry.strict-css-url-rewriting";