All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sirius.kernel.health.Exceptions Maven / Gradle / Ivy

Go to download

Provides common core classes and the microkernel powering all Sirius applications

There is a newer version: 12.9.1
Show newest version
/*
 * Made with all the love in the world
 * by scireum in Remshalden, Germany
 *
 * Copyright by scireum GmbH
 * http://www.scireum.de - [email protected]
 */

package sirius.kernel.health;

import com.google.common.collect.Maps;
import sirius.kernel.async.CallContext;
import sirius.kernel.commons.Explain;
import sirius.kernel.commons.Strings;
import sirius.kernel.di.PartCollection;
import sirius.kernel.di.std.Parts;
import sirius.kernel.nls.NLS;

import javax.annotation.Nullable;
import java.util.Map;

/**
 * Central point for handling all system errors and exceptions.
 * 

* Provides various methods to handle errors and exceptions. Each method returns a {@link HandledException} which * signals the developer that no further action is required (error is logged and reacted upon). Also, those * exceptions always contain a translated error message which can be directly shown to the user */ public class Exceptions { /** * Used as a fallback logger, if no logger was provided. Logs everything as "errors". */ protected static final Log LOG = Log.get("errors"); /** * Used to log exceptions which are normally just discarded. This are either exception handled with * {@link #ignore(Throwable)} or exceptions created by {@link #createHandled()} */ protected static final Log IGNORED_EXCEPTIONS_LOG = Log.get("ignored"); /** * Used to log warnings if deprecated APIs are called. * * @see #logDeprecatedMethodUse() */ protected static final Log DEPRECATION_LOG = Log.get("deprecated"); /* * Filled by the Injector - contains all handles which participate in the exception handling process */ @Parts(ExceptionHandler.class) private static PartCollection handlers; /* * Used to cut endless loops while handling errors */ private static ThreadLocal frozen = new ThreadLocal<>(); private Exceptions() { } /** * Fluent API to create a HandledException based on given parameters *

* The intention is to use a call like: *

     * {@code
     *    Exceptions.handler()
     *      .error(anException)     // Sets the exception to handle
     *      .to(aLogger)            // Sets the logger to use for logging
     *      .withNLSKey("nls.key")  // Sets the i18n key to create the error message
     *      .set("param",value)     // Sets a named parameter which occurs in the message
     *      .handle();              // logs an creates the HandledException
     * }
     * 
*

* Since none of the methods must be called (except handle() of course), this provides a lot of * flexibility and permits to handle several different error situations without having methods with long * parameter lists and lots of null values. *

* The {@link #set(String, Object)} method can be called several times to set different parameters. The reason * why named parameters are used is because the resulting messages in the .properties files are easier to * translate and also the order of the parameters can be different in different languages. */ public static class ErrorHandler { private Log log = LOG; private Throwable ex; private String systemErrorMessage; private Object[] systemErrorMessageParams; private boolean processError = true; private String key = "HandledException.exception"; private Map params = Maps.newTreeMap(); /** * Use {@link Exceptions#handle()} to create an ErrorHandler * * @param processError determines if the error should be processed * (logged and sent to all {@link ExceptionHandler}) or if just a * {@link sirius.kernel.health.HandledException} is to be created */ protected ErrorHandler(boolean processError) { super(); this.processError = processError; } /** * Specifies which exception leaded to the error being handled. * * @param e the exception which needs to be attached to this error handler * @return this in order to fluently call more methods on this handler */ public ErrorHandler error(Throwable e) { this.ex = e; return this; } /** * Specifies the logger which is used to log the generated exception. * * @param log the logger used to log the generated HandledException * @return this in order to fluently call more methods on this handler */ public ErrorHandler to(Log log) { this.log = log; return this; } /** * Specifies the i18n key which is passed to {@link NLS#fmtr(String)} to create the internal formatter * used to generate the translated error message. *

* This message may contain two parameters which don't need to be passed in: errorMessage * and errorClass which contain the message of the exception being handled * as well as the type name of it. * * @param key the translation key used to fetch the translated error message * @return this in order to fluently call more methods on this handler */ public ErrorHandler withNLSKey(String key) { this.key = key; return this; } /** * Sets an untranslated error message, used by rare system errors. *

* Still a translated message will be created, which notifies the user about the system error and provides * the untranslated error message, generated by this method. These messages should be in english. * * @param englishMessagePattern contains a pattern used to generate the error message. May contain * placeholders as understood by {@link Strings#apply(String, Object...)}. * @param params parameters used to format the resulting error message based on the given * pattern * @return this in order to fluently call more methods on this handler */ public ErrorHandler withSystemErrorMessage(String englishMessagePattern, Object... params) { this.systemErrorMessage = englishMessagePattern; this.systemErrorMessageParams = params; return this; } /** * Specifies a parameter which is replaced in the generated error message. * * @param parameter the name of the parameter which should be replaced. This must occur as * {@code ${parameter}} in the translated message to be replaced * @param value the value to be used as replacement for the parameter. The given value will be converted * to a string using {@link NLS#toUserString(Object)} * @return this in order to fluently call more methods on this handler */ public ErrorHandler set(String parameter, Object value) { this.params.put(parameter, value); return this; } /** * Generates and logs the resulting HandledException. *

* The generated exception can be either thrown (it subclasses RuntimeException and therefore * needs no throws clause). Alternatively it may be passed along or even be just discarded. * * @return a HandledException which notifies surrounding calls that an error occurred, which has * already been taken care of. */ @SuppressWarnings("squid:S1148") @Explain("This log statement is our last restor when we're in deep trouble.") public HandledException handle() { if (ex instanceof HandledException) { return (HandledException) ex; } if (Exceptions.getRootCause(ex) instanceof HandledException) { processError = false; LOG.FINE("Did not process the exception %s because its root (%s) was already handled", ex.getMessage(), Exceptions.getRootCause(ex).getMessage()); } try { String message = computeMessage(); HandledException result = new HandledException(message, ex); if (processError) { log.SEVERE(result); notifyHandlers(result); } else { IGNORED_EXCEPTIONS_LOG.INFO(result); } return result; } catch (Exception t) { // We call as few external methods a possible here, since things are really messed up right now t.printStackTrace(); return new HandledException("Kernel Panic: Exception-Handling threw another exception: " + t.getMessage() + " (" + t.getClass().getName() + ")", t); } } private String computeMessage() { if (Strings.isFilled(systemErrorMessage)) { // Generate system error message and prefix with translated info about the system error return NLS.fmtr("HandledException.systemError") .set("error", Strings.apply(systemErrorMessage, extendParams(ex, systemErrorMessageParams))) .format(); } else { // Add exception infos set("errorMessage", ex == null ? NLS.get("HandledException.unknownError") : ex.getMessage()); set("errorClass", ex == null ? "UnknownError" : ex.getClass().getName()); // Format resulting error message return NLS.fmtr(key).set(params).format(); } } private void notifyHandlers(HandledException result) { // Injector might not have run yet if (handlers == null || Boolean.TRUE.equals(frozen.get())) { return; } try { frozen.set(Boolean.TRUE); String location = computeLocation(result); for (ExceptionHandler handler : handlers) { try { handler.handle(new Incident(log.getName(), location, CallContext.getCurrent().getMDC(), result)); } catch (Exception e) { // Just log the exception - anything else might call a rather long infinite loop LOG.SEVERE(new Exception(Strings.apply( "An error occurred while calling the ExceptionHandler: %s - %s (%s)", handler, e.getMessage(), e.getClass().getName()), e)); } } } finally { frozen.set(Boolean.FALSE); } } private String computeLocation(HandledException result) { String location = null; if (ex != null && ex.getStackTrace().length > 0) { location = formatStackTraceElement(ex.getStackTrace()[0]); } else if (result.getStackTrace().length > 0) { StackTraceElement[] trace = Thread.currentThread().getStackTrace(); int index = 1; while ((location == null || location.startsWith("sirius.kernel.health.Exceptions")) && index < trace.length) { location = formatStackTraceElement(trace[index]); index++; } } return location; } /* * Adds the exception message and the exception class to the given params array. Handles null values for * e gracefully */ private Object[] extendParams(Throwable e, Object[] params) { Object[] newParams; if (params == null) { newParams = new Object[2]; } else { newParams = new Object[params.length + 2]; System.arraycopy(params, 0, newParams, 0, params.length); } if (e != null) { newParams[newParams.length - 2] = e.getMessage(); newParams[newParams.length - 1] = e.getClass().getName(); } else { newParams[newParams.length - 2] = NLS.get("HandledException.unknownError"); newParams[newParams.length - 1] = "UnknownError"; } return newParams; } /* * Formats a given StackTraceElement as [class].[method] ([file]:[line]) */ private static String formatStackTraceElement(StackTraceElement element) { if (element == null) { return null; } return element.getClassName() + "." + element.getMethodName() + " (" + element.getFileName() + ":" + element .getLineNumber() + ")"; } @Override public String toString() { return "ErrorHandler{" + "params=" + params + ", key='" + key + '\'' + ", systemErrorMessage='" + systemErrorMessage + '\'' + ", ex=" + ex + '}'; } } /** * Generates a new {@link ErrorHandler} which gracefully handles all kinds of errors * * @return a new ErrorHandler to handle an error or exception */ public static ErrorHandler handle() { return new ErrorHandler(true); } /** * Boilerplate method the directly handle the given exception without a special message or logger * * @param e the exception to handle * @return a HandledException which notifies surrounding calls that an error occurred, which has * already been taken care of. */ public static HandledException handle(Throwable e) { return handle().error(e).handle(); } /** * Boilerplate method the directly handle the given exception without a special message * * @param log the logger used to log the exception * @param e the exception to handle * @return a HandledException which notifies surrounding calls that an error occurred, which has * already been taken care of. */ public static HandledException handle(Log log, Throwable e) { return handle().error(e).to(log).handle(); } /** * Generates a new {@link ErrorHandler} which creates a HandledException without actually logging or * processing it. *

* This can be used to generate a HandledException based on a user error (invalid input) * which doesn't need to be logged. * * @return a new ErrorHandler to handle an error or exception */ public static ErrorHandler createHandled() { return new ErrorHandler(false); } /** * Can be used to mark an exception as ignored. *

* Instead of leading a try / catch block empty, the method can be invoked. Therefore it is known, that the * exception is wanted to be ignored. Additionally, the ignoredExceptions logger can be turned on, * to still see those exceptions. * * @param t the exception to be ignored. This exception will be discarded unless the ignoredExceptions * logger is set to INFO. */ public static void ignore(Throwable t) { IGNORED_EXCEPTIONS_LOG.INFO(t); } /** * Can be used to log if a deprecated method has been called. *

* This method must be called from the deprecated one and will report the name of the deprecated method * and its caller. */ public static void logDeprecatedMethodUse() { StackTraceElement[] stack = Thread.currentThread().getStackTrace(); if (stack.length < 4) { Exceptions.handle().withSystemErrorMessage("Cannot log deprecated API call for short stacktrace!").handle(); } StackTraceElement deprecatedMethod = stack[2]; StackTraceElement caller = stack[3]; DEPRECATION_LOG.WARN("The deprecated method '%s.%s' was called by '%s.%s'", deprecatedMethod.getClassName(), deprecatedMethod.getMethodName(), caller.getClassName(), caller.getMethodName()); } /** * Retrieves the actual root {@link Throwable} which ended in the given exception. * * @param e the throwable to begin with * @return the root {@link Throwable} of the given one */ public static Throwable getRootCause(@Nullable Throwable e) { if (e == null) { return null; } Throwable cause = e; int circuitBreaker = 11; while (circuitBreaker > 0 && cause.getCause() != null) { cause = cause.getCause(); circuitBreaker--; } return cause; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy