Global Exception Handler?

Discussion in 'Plugin Development' started by RjSowden, Apr 21, 2013.

Thread Status:
Not open for further replies.
  1. Offline

    RjSowden

    Hi,

    So, I have a plugin which I know (because I'm not perfect) must throw a lot of runtime exceptions, but that I'm getting a relatively low amount of tickets being submitted for. So I want to introduce a global exception handler for my plugin that will automatically send (user-setting permitted) information to my webserver about the issue, so I can fix it without them actually having to post anything.

    The plugin has 3000+ lines of code and so it's not practical or even good practice to place try-catch statements everywhere. I've tried using Thread.setDefaultUnhandledExceptionHandler (example below) but that does nothing and does not trigger the handler when a runtime error occures (i'm testing it by making the plugin attempt to divide by zero):

    Code:
    class ExceptionHandler implements Thread.UncaughtExceptionHandler {
      public void uncaughtException(Thread t, Throwable e) {
        handle(e);
      }
     
      public void handle(Throwable throwable) {
        try {
          //code to report error to webserver on different thread is here
        } catch (Throwable t) {
          log.info("Sorry, couldn't report error :(");
        }
      }
     
      public static void registerExceptionHandler() {
        Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler());
        System.setProperty("sun.awt.exception.handler", ExceptionHandler.class.getName());
      }
    }
    So, yeah, I can't get it to trigger on the error. Does bukkit catch and 'mirror' exceptions before my plugin can get the them, hence it not working, or am I doing something wrong?

    Thanks a bunch1
     
  2. Offline

    ferrybig

    you know it isnt recommend by the java tutorials to use runtime exception for things that should happen?

    also, bukkit catches the errors internally so it wont crash if 1 plugin forgets to catch the error

    Runtine exception: something is wrong, like getting a index that does not exists

    Checked Exception: something goes wrong and it is not excepted at normal run, like networking error

    No Exception when something is excepted always: ie, you except that there is always a end of bytes at a file, so it returns -1
     
  3. Offline

    ZeusAllMighty11 Retired Staff

    If you surround the areas you KNOW will throw errors with Try Catch fields, then you can easily print out the stacktrace to the user, which they can submit with the ticket. No need for a whole new exception thing.

    And if you're wondering watch to catch, just catch 'Exception'
     
  4. Offline

    RjSowden

    I didn't want to do that since I've got 60+ voids where errors could pop up to a class and each one would need their own try-catch after their throws and that would make the code just look messy, but I guess if that's the only way...
     
  5. Offline

    Comphenix

    You could use Aspect Oriented Programming to achieve this, but it's a bit difficult to set up. There's also a runtime component of about 213 kB - provided you use compile time weaving - which will definitely have a higher memory footprint than simply adding a try-catch to all your methods.

    Instead, why not add the exception handler around every event handler instead. This is presumably the most frequent unique entry point for your plugin - unless you schedule a ton of tasks instead - and ripe for automation. The rest - onLoad(), onEnable() and onDisable() - can be addressed manually.

    I've written a fairly small class that can achieve this (download):
    Code:java
    1. package com.comphenix.example;
    2.  
    3. import java.lang.reflect.Method;
    4.  
    5. import java.util.*;
    6. import org.bukkit.event.*;
    7. import org.bukkit.plugin.*;
    8. import org.apache.commons.lang.Validate;
    9.  
    10. import com.google.common.collect.Lists;
    11.  
    12. public abstract class EventExceptionHandler {
    13. // For wrapping a registered listener
    14. private static class ExceptionRegisteredListener extends RegisteredListener {
    15. /**
    16.   * Represents an event executor that does nothing. This is not really necessary in the current
    17.   * implementation of CraftBukkit, but we will take no chances.
    18.   */
    19. private static EventExecutor NULL_EXECUTOR = new EventExecutor() {
    20. @Override
    21. public void execute(Listener listener, Event event) throws EventException {
    22. // Do nothing
    23. }
    24. };
    25.  
    26. private final RegisteredListener delegate;
    27. private final EventExceptionHandler handler;
    28.  
    29. public ExceptionRegisteredListener(RegisteredListener delegate, EventExceptionHandler handler) {
    30. super(delegate.getListener(), NULL_EXECUTOR, delegate.getPriority(),
    31. delegate.getPlugin(), delegate.isIgnoringCancelled());
    32. this.delegate = delegate;
    33. this.handler = handler;
    34. }
    35.  
    36. @Override
    37. public void callEvent(Event event) throws EventException {
    38. try {
    39. delegate.callEvent(event);
    40. } catch (EventException e) {
    41. if (!handler.handle(e.getCause(), event)) {
    42. throw e;
    43. }
    44. } catch (Throwable e) {
    45. if (!handler.handle(e, event)) {
    46. doThrow(e);
    47. }
    48. }
    49. }
    50.  
    51. // WARNING: HORRIBLE, HORRIBLE HACK to get around checked exceptions
    52. private static void doThrow(Throwable e) {
    53. ExceptionRegisteredListener.<RuntimeException> doThrowInner(e);
    54. }
    55.  
    56. @SuppressWarnings("unchecked")
    57. private static <E extends Throwable> void doThrowInner(Throwable e) throws E {
    58. throw (E) e;
    59. }
    60. }
    61.  
    62. /**
    63.   * Register Bukkit event handlers with a given exception handler.
    64.   * @param listener - a class of event handlers.
    65.   * @param plugin - the current plugin.
    66.   * @param handler - exception handler.
    67.   */
    68. public static void registerEvents(Listener listener, Plugin plugin, EventExceptionHandler handler) {
    69. Validate.notNull(plugin, "Plugin cannot be NULL.");
    70.  
    71. registerEvents(plugin.getServer().getPluginManager(), listener, plugin, handler);
    72. }
    73.  
    74. /**
    75.   * Register Bukkit event handlers with a given exception handler.
    76.   * @param manager - the current plugin manager.
    77.   * @param listener - a class of event handlers.
    78.   * @param plugin - the current plugin.
    79.   * @param handler - exception handler.
    80.   */
    81. public static void registerEvents(PluginManager manager, Listener listener, Plugin plugin, EventExceptionHandler handler) {
    82. Validate.notNull(manager, "Manager cannot be NULL.");
    83. Validate.notNull(listener, "Listener cannot be NULL.");
    84. Validate.notNull(plugin, "Plugin cannot be NULL.");
    85. Validate.notNull(handler, "Handler cannot be NULL.");
    86.  
    87. if (!plugin.isEnabled()) {
    88. throw new IllegalPluginAccessException("Plugin attempted to register " + listener + " while not enabled");
    89. }
    90.  
    91. // Create normal listeners
    92. for (Map.Entry<Class<? extends Event>, Set<RegisteredListener>> entry :
    93. plugin.getPluginLoader().createRegisteredListeners(listener, plugin).entrySet()) {
    94.  
    95. // Wrap these listeners in our exception handler
    96. getHandlerList(entry.getKey()).registerAll(wrapAll(entry.getValue(), handler));
    97. }
    98. }
    99.  
    100. /**
    101.   * Wrap every listener in the given collection around an exception handler.
    102.   * @param listeners - the listeners to wrap.
    103.   * @param handler - the exception handler to add.
    104.   * @return The wrapped listeners.
    105.   */
    106. private static Collection<RegisteredListener> wrapAll(Collection<RegisteredListener> listeners, EventExceptionHandler handler) {
    107. List<RegisteredListener> output = Lists.newArrayList();
    108.  
    109. for (RegisteredListener listener : listeners) {
    110. output.add(new ExceptionRegisteredListener(listener, handler));
    111. }
    112. return output;
    113. }
    114.  
    115. /**
    116.   * Retrieve the handler list associated with the given class.
    117.   * @param clazz - given event class.
    118.   * @return Associated handler list.
    119.   */
    120. private static HandlerList getHandlerList(Class<? extends Event> clazz) {
    121. // Class must have Event as its superclass
    122. while (clazz.getSuperclass() != null && Event.class.isAssignableFrom(clazz.getSuperclass())) {
    123. try {
    124. Method method = clazz.getDeclaredMethod("getHandlerList");
    125. method.setAccessible(true);
    126. return (HandlerList) method.invoke(null);
    127. } catch (NoSuchMethodException e) {
    128. // Keep on searching
    129. clazz = clazz.getSuperclass().asSubclass(Event.class);
    130. } catch (Exception e) {
    131. throw new IllegalPluginAccessException(e.getMessage());
    132. }
    133. }
    134. throw new IllegalPluginAccessException("Unable to find handler list for event " + clazz.getName());
    135. }
    136.  
    137. /**
    138.   * Handle a given exception.
    139.   * @param ex - the exception to handle.
    140.   * @param event - the event that was being handled.
    141.   * @return TRUE to indicate that the exception has been handled, FALSE to rethrow it.
    142.   */
    143. public abstract boolean handle(Throwable ex, Event event);
    144. }

    To use it, simply replace all calls to registerEvents with the following:
    Code:java
    1. EventExceptionHandler.registerEvents(this, this, new EventExceptionHandler() {
    2. @Override
    3. public boolean handle(Throwable ex, Event event) {
    4. getLogger().log(Level.SEVERE, "Error " + ex.getMessage() + " occured for " + event, ex);
    5.  
    6. // Don't pass it on
    7. return true;
    8. // Use Bukkit's default exception handler
    9. // return false;
    10. }
    11. });


    I should probably mention that there's already a simple library for this: ErrorLogger.

    I even forgot I'd written a version myself, that also handles tasks.

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: Jun 1, 2016
Thread Status:
Not open for further replies.

Share This Page