/********************************************************************************** * $URL: https://source.sakaiproject.org/svn/portal/trunk/portal-util/util/src/java/org/sakaiproject/util/ErrorReporter.java $ * $Id: ErrorReporter.java 14756 2006-09-16 15:37:38Z csev@umich.edu $ *********************************************************************************** * * Copyright (c) 2006 The Sakai Foundation. * * Licensed under the Educational Community License, Version 1.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.opensource.org/licenses/ecl1.php * * 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.sakaiproject.util; import java.io.IOException; import java.io.PrintWriter; import java.security.MessageDigest; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.sakaiproject.component.cover.ServerConfigurationService; import org.sakaiproject.email.cover.EmailService; import org.sakaiproject.event.cover.UsageSessionService; import org.sakaiproject.event.api.UsageSession; import org.sakaiproject.time.api.Time; import org.sakaiproject.time.cover.TimeService; import org.sakaiproject.tool.cover.SessionManager; import org.sakaiproject.user.api.User; import org.sakaiproject.user.cover.UserDirectoryService; /** *

* ErrorReporter helps with end-user formatted error reporting, user feedback collection, logging and emailing for uncaught throwable based errors. *

*/ public class ErrorReporter { /** Our log (commons). */ private static Log M_log = LogFactory.getLog(ErrorReporter.class); /** messages. */ private static ResourceLoader rb = new ResourceLoader("portal-util"); public ErrorReporter() { } /** Following two methods borrowed from RWikiObjectImpl.java **/ private static MessageDigest shatemplate = null; public static String computeSha1(String content) { String digest = ""; try { if (shatemplate == null) { shatemplate = MessageDigest.getInstance("SHA"); } MessageDigest shadigest = (MessageDigest) shatemplate.clone(); byte[] bytedigest = shadigest.digest(content.getBytes("UTF8")); digest = byteArrayToHexStr(bytedigest); } catch (Exception ex) { System.err.println("Unable to create SHA hash of content"); ex.printStackTrace(); } return digest; } private static String byteArrayToHexStr(byte[] data) { String output = ""; String tempStr = ""; int tempInt = 0; for (int cnt = 0; cnt < data.length; cnt++) { // Deposit a byte into the 8 lsb of an int. tempInt = data[cnt] & 0xFF; // Get hex representation of the int as a // string. tempStr = Integer.toHexString(tempInt); // Append a leading 0 if necessary so that // each hex string will contain two // characters. if (tempStr.length() == 1) tempStr = "0" + tempStr; // Concatenate the two characters to the // output string. output = output + tempStr; }// end for loop return output.toUpperCase(); }// end byteArrayToHexStr /** * Format the full stack trace. * * @param t * The throwable. * @return A display of the full stack trace for the throwable. */ protected String getStackTrace(Throwable t) { StackTraceElement[] st = t.getStackTrace(); StringBuffer buf = new StringBuffer(); if (st != null) { for (int i = 0; i < st.length; i++) { buf.append("\n at " + st[i].getClassName() + "." + st[i].getMethodName() + "(" + ((st[i].isNativeMethod()) ? "Native Method" : (st[i].getFileName() + ":" + st[i].getLineNumber())) + ")"); } buf.append("\n"); } return buf.toString(); } /** * Format a one-level stack trace, just showing the place where the exception occurred (the first entry in the stack trace). * * @param t * The throwable. * @return A display of the first stack trace entry for the throwable. */ protected String getOneTrace(Throwable t) { StackTraceElement[] st = t.getStackTrace(); StringBuffer buf = new StringBuffer(); if (st != null && st.length > 0) { buf.append("\n at " + st[1].getClassName() + "." + st[1].getMethodName() + "(" + ((st[1].isNativeMethod()) ? "Native Method" : (st[1].getFileName() + ":" + st[1].getLineNumber())) + ")\n"); } return buf.toString(); } /** * Compute the cause of a throwable, checking for the special ServletException case, and the points-to-self case. * * @param t * The throwable. * @return The cause of the throwable, or null if there is none. */ protected Throwable getCause(Throwable t) { Throwable rv = null; // ServletException is non-standard if (t instanceof ServletException) { rv = ((ServletException) t).getRootCause(); } // try for the standard cause if (rv == null) rv = t.getCause(); // clear it if the cause is pointing at the throwable if ((rv != null) && (rv == t)) rv = null; return rv; } /** * Format a throwable for display: list the various throwables drilling down the cause, and full stacktrack for the final cause. * * @param t * The throwable. * @return The string display of the throwable. */ protected String throwableDisplay(Throwable t) { StringBuffer buf = new StringBuffer(); buf.append(t.toString() + ((getCause(t) == null) ? (getStackTrace(t)) : getOneTrace(t))); while (getCause(t) != null) { t = getCause(t); buf.append("caused by: "); buf.append(t.toString() + ((getCause(t) == null) ? (getStackTrace(t)) : getOneTrace(t))); } return buf.toString(); } /** * Log and email the error report details. * * @param usageSessionId * The end-user's usage session id. * @param userId * The end-user's user id. * @param time * The time of the error. * @param problem * The stacktrace of the error. * @param problemdigest * The sha1 digest of the stacktrace. * @param requestURI * The request URI. * @param userReport * The end user comments. */ protected void logAndMail(String usageSessionId, String userId, String time, String problem, String problemdigest, String requestURI, String userReport) { // log M_log.warn(rb.getString("bugreport.bugreport") + " " + rb.getString("bugreport.user") + ": " + userId + " " + rb.getString("bugreport.usagesession") + ": " + usageSessionId + " " + rb.getString("bugreport.time") + ": " + time + " " + rb.getString("bugreport.usercomment") + ": " + userReport + " " + rb.getString("bugreport.stacktrace") + "\n" + problem); // mail String emailAddr = ServerConfigurationService.getString("portal.error.email"); if (emailAddr != null) { String uSessionInfo = ""; UsageSession usageSession = UsageSessionService.getSession(); if (usageSession != null ) { uSessionInfo = rb.getString("bugreport.useragent") + ": " + usageSession.getUserAgent() + "\n" + rb.getString("bugreport.browserid") + ": " + usageSession.getBrowserId()+ "\n" + rb.getString("bugreport.ip") + ": " + usageSession.getIpAddress() + "\n"; } String pathInfo = ""; if (requestURI != null) { pathInfo = rb.getString("bugreport.path") + ": " + requestURI + "\n"; } User user = null; String userName = null; String userMail = null; String userEid = null; if (userId != null) { try { user = UserDirectoryService.getUser(userId); userName = user.getDisplayName(); userMail = user.getEmail(); userEid = user.getEid(); } catch (Exception e) { // nothing } } String userComment = ""; if (userReport != null) { userComment = rb.getString("bugreport.usercomment") + ":\n\n" + userReport + "\n\n\n"; } String from = "\"" + ServerConfigurationService.getString("ui.service", "Sakai") + "\""; String subject = rb.getString("bugreport.bugreport") + ": " + problemdigest + " / " + usageSessionId; String body = rb.getString("bugreport.user") + ": " + userEid + " (" + userName + ")\n" + rb.getString("bugreport.email") + ": " + userMail + "\n" + rb.getString("bugreport.usagesession") + ": " + usageSessionId + "\n" + rb.getString("bugreport.digest") + ": " + problemdigest + "\n" + rb.getString("bugreport.version-sakai") + ": " + ServerConfigurationService.getString("version.sakai") + "\n" + rb.getString("bugreport.version-service") + ": " + ServerConfigurationService.getString("version.service") + "\n" + rb.getString("bugreport.appserver") + ": " + ServerConfigurationService.getServerId() + "\n" + uSessionInfo + pathInfo + rb.getString("bugreport.time") + ": " + time + "\n\n\n" + userComment + rb.getString("bugreport.stacktrace") + ":\n\n" + problem; EmailService.send(from, emailAddr, subject, body, null, null, null); } } /** * Handle the inital report of an error, from an uncaught throwable, with a user display. * * @param req * The request. * @param res * The response. * @param t * The uncaught throwable. */ public void report(HttpServletRequest req, HttpServletResponse res, Throwable t) { String headInclude = (String) req.getAttribute("sakai.html.head"); String bodyOnload = (String) req.getAttribute("sakai.html.body.onload"); Time reportTime = TimeService.newTime(); String time = reportTime.toStringLocalDate() + " " + reportTime.toStringLocalTime24(); String usageSessionId = UsageSessionService.getSessionId(); String userId = SessionManager.getCurrentSessionUserId(); String problem = throwableDisplay(t); String problemdigest = computeSha1(problem); String postAddr = ServerConfigurationService.getPortalUrl() + "/error-report"; String requestURI = req.getRequestURI(); if (bodyOnload == null) { bodyOnload = ""; } else { bodyOnload = " onload=\"" + bodyOnload + "\""; } try { // headers res.setContentType("text/html; charset=UTF-8"); res.addDateHeader("Expires", System.currentTimeMillis() - (1000L * 60L * 60L * 24L * 365L)); res.addDateHeader("Last-Modified", System.currentTimeMillis()); res.addHeader("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0"); res.addHeader("Pragma", "no-cache"); PrintWriter out = res.getWriter(); out .println(""); out.println(""); if (headInclude != null) { out.println(""); out.println(headInclude); out.println(""); } out.println(""); out.println("
"); out.println("

" + rb.getString("bugreport.error") + "

"); out.println("

" + rb.getString("bugreport.statement") + "

"); out.println("

" + rb.getString("bugreport.sendtitle") + "

"); out .println("

" + rb.getString("bugreport.sendinstructions") + "

"); out.println("
"); out.println(""); out.println(""); out.println(""); out.println(""); out.println(""); out.println(""); out.println(""); out.println(""); out.println(""); out.println(""); out.println(""); out.println("
"); out.println("
"); out.println(""); out.println("
"); out.println("

"); out.println("

" + rb.getString("bugreport.recoverytitle") + "

"); out.println("

" + rb.getString("bugreport.recoveryinstructions") + ""); out.println("

  • " + rb.getString("bugreport.recoveryinstructions1") + "
  • "); out.println("
  • " + rb.getString("bugreport.recoveryinstructions2") + "
  • "); out.println("
  • " + rb.getString("bugreport.recoveryinstructions3") + "


"); out.println("

" + rb.getString("bugreport.detailstitle") + "

"); out.println("

" + rb.getString("bugreport.detailsnote") + "

"); out.println("

");
			out.println(problem);
			out.println();
			out.println(rb.getString("bugreport.user") + ": " + userId + "\n");
			out.println(rb.getString("bugreport.usagesession") + ": " + usageSessionId + "\n");
			out.println(rb.getString("bugreport.time") + ": " + time + "\n");
			out.println("

"); out.println(""); out.println(""); // log and send the preliminary email logAndMail(usageSessionId, userId, time, problem, problemdigest, requestURI, null); } catch (Throwable any) { M_log.warn(rb.getString("bugreport.troublereporting"), any); } } /** * Accept the user feedback post. * * @param req * The request. * @param res * The response. */ public void postResponse(HttpServletRequest req, HttpServletResponse res) { String session = req.getParameter("session"); String user = req.getParameter("user"); String time = req.getParameter("time"); String comment = req.getParameter("comment"); String problem = req.getParameter("problem"); String problemdigest = req.getParameter("problemdigest"); // log and send the followup email logAndMail(session, user, time, problem, problemdigest, null, comment); // always redirect from a post try { res.sendRedirect(res.encodeRedirectURL(ServerConfigurationService.getPortalUrl() + "/error-reported")); } catch (IOException e) { M_log.warn(rb.getString("bugreport.troubleredirecting"), e); } } /** * Accept the user feedback post. * * @param req * The request. * @param res * The response. */ public void thanksResponse(HttpServletRequest req, HttpServletResponse res) { String headInclude = (String) req.getAttribute("sakai.html.head"); String bodyOnload = (String) req.getAttribute("sakai.html.body.onload"); if (bodyOnload == null) { bodyOnload = ""; } else { bodyOnload = " onload=\"" + bodyOnload + "\""; } try { // headers res.setContentType("text/html; charset=UTF-8"); res.addDateHeader("Expires", System.currentTimeMillis() - (1000L * 60L * 60L * 24L * 365L)); res.addDateHeader("Last-Modified", System.currentTimeMillis()); res.addHeader("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0"); res.addHeader("Pragma", "no-cache"); PrintWriter out = res.getWriter(); out .println(""); out.println(""); if (headInclude != null) { out.println(""); out.println(headInclude); out.println(""); } out.println(""); out.println("
"); out.println("

" + rb.getString("bugreport.error") + "

"); out.println("

" + rb.getString("bugreport.statement") + "

"); out.println("

" + rb.getString("bugreport.senttitle") + "

"); out.println("

" + rb.getString("bugreport.sentnote") + "

"); out.println("

" + rb.getString("bugreport.recoverytitle") + "

"); out.println("

" + rb.getString("bugreport.recoveryinstructions") + ""); out.println("

  • " + rb.getString("bugreport.recoveryinstructions1") + "
  • "); out.println("
  • " + rb.getString("bugreport.recoveryinstructions2") + "
  • "); out.println("
  • " + rb.getString("bugreport.recoveryinstructions3") + "


"); out.println(""); out.println(""); } catch (Throwable any) { M_log.warn(rb.getString("bugreport.troublethanks"), any); } } }