diff --git a/wizard-server/src/main/java/org/cobbzilla/wizard/server/config/ErrorApiConfiguration.java b/wizard-server/src/main/java/org/cobbzilla/wizard/server/config/ErrorApiConfiguration.java index 2306b7e..bcce867 100644 --- a/wizard-server/src/main/java/org/cobbzilla/wizard/server/config/ErrorApiConfiguration.java +++ b/wizard-server/src/main/java/org/cobbzilla/wizard/server/config/ErrorApiConfiguration.java @@ -5,9 +5,10 @@ import airbrake.AirbrakeNotifier; import lombok.*; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.buffer.CircularFifoBuffer; -import org.cobbzilla.util.system.CommandShell; +import static java.util.concurrent.TimeUnit.SECONDS; import static org.cobbzilla.util.daemon.ZillaRuntime.empty; +import static org.cobbzilla.util.system.CommandShell.hostname; @NoArgsConstructor @AllArgsConstructor @Slf4j @ToString public class ErrorApiConfiguration { @@ -15,15 +16,19 @@ public class ErrorApiConfiguration { @Getter @Setter private String url; @Getter @Setter private String key; @Setter private String env; + @Getter @Setter private int dupCacheSize = 100; + @Getter @Setter private int bufferSize = 200; + @Getter @Setter private long sendInterval = SECONDS.toMillis(5); @Getter(lazy=true) private final AirbrakeNotifier notifier = initNotifier(); + private AirbrakeNotifier initNotifier() { return new AirbrakeNotifier(getUrl()); } - public String getEnv() { return !empty(env) ? env : CommandShell.hostname(); } + public String getEnv() { return !empty(env) ? env : hostname(); } public boolean isValid() { return !empty(getUrl()) && !empty(getKey()) && !empty(getEnv()); } - private final CircularFifoBuffer cache = new CircularFifoBuffer(20); + private final CircularFifoBuffer cache = new CircularFifoBuffer(dupCacheSize); public void report(Exception e) { if (inCache(e)) return; diff --git a/wizard-server/src/main/java/org/cobbzilla/wizard/server/listener/ErrbitConfigListener.java b/wizard-server/src/main/java/org/cobbzilla/wizard/server/listener/ErrbitConfigListener.java index 7fd4de4..1ffc555 100644 --- a/wizard-server/src/main/java/org/cobbzilla/wizard/server/listener/ErrbitConfigListener.java +++ b/wizard-server/src/main/java/org/cobbzilla/wizard/server/listener/ErrbitConfigListener.java @@ -2,7 +2,7 @@ package org.cobbzilla.wizard.server.listener; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.exception.ExceptionUtils; +import org.apache.commons.collections.buffer.CircularFifoBuffer; import org.cobbzilla.util.daemon.ErrorApi; import org.cobbzilla.util.daemon.ZillaRuntime; import org.cobbzilla.wizard.server.RestServer; @@ -10,6 +10,13 @@ import org.cobbzilla.wizard.server.RestServerBase; import org.cobbzilla.wizard.server.RestServerLifecycleListenerBase; import org.cobbzilla.wizard.server.config.RestServerConfiguration; +import java.util.ArrayList; +import java.util.List; + +import static org.apache.commons.lang3.exception.ExceptionUtils.getStackTrace; +import static org.cobbzilla.util.daemon.ZillaRuntime.shortError; +import static org.cobbzilla.util.system.Sleep.sleep; + @Slf4j public class ErrbitConfigListener extends RestServerLifecycleListenerBase { @@ -17,18 +24,22 @@ public class ErrbitConfigListener extends RestServerLifecycleListenerBase { final ErrbitApi errorApi = new ErrbitApi(server.getConfiguration()); RestServerBase.setErrorApi(errorApi); ZillaRuntime.setErrorApi(errorApi); + errorApi.start(); log.info("onStart: "+server.getConfiguration().getErrorApi()); } @AllArgsConstructor @Slf4j - static class ErrbitApi implements ErrorApi { + static class ErrbitApi implements ErrorApi, Runnable { + + private static final String SLEEP_MESSAGE = ErrbitApi.class.getName()+"waiting for more errors"; private final RestServerConfiguration config; + private final CircularFifoBuffer fifo = new CircularFifoBuffer(config.getErrorApi().getBufferSize()); @Override public void report(Exception e) { if (config.hasErrorApi()) { log.error(e.toString()); - config.getErrorApi().report(e); + synchronized (fifo) { fifo.add(e); } } else { log.warn("report: could not send exception to error reporting API: "+e, e); } @@ -37,14 +48,54 @@ public class ErrbitConfigListener extends RestServerLifecycleListenerBase { @Override public void report(String s) { if (config.hasErrorApi()) { log.error(s); - config.getErrorApi().report(s); + synchronized (fifo) { fifo.add(s); } } else { log.warn("report: could not send exception to error reporting API: "+s); } } @Override public void report(String s, Exception e) { - report(s+"\nException: "+e+"\n"+ExceptionUtils.getStackTrace(e)); + report(s+"\nException: "+e+"\n"+getStackTrace(e)); + } + + public void start() { + final Thread t = new Thread(this); + t.setDaemon(true); + t.setName(getClass().getName()); + t.start(); + } + + @Override public void run() { + while (true) { + try { + final List reports; + synchronized (fifo) { + if (fifo.isEmpty()) { + reports = new ArrayList(fifo); + fifo.clear(); + } else { + reports = null; + } + } + if (reports == null) { + sleep(config.getErrorApi().getSendInterval(), SLEEP_MESSAGE); + continue; + } + for (Object o : reports) { + if (o instanceof Exception) { + config.getErrorApi().report((Exception) o); + } else if (o instanceof String) { + config.getErrorApi().report((String) o); + } else { + final String val = o.toString(); + log.warn("ErrbitApi.run: reporting object that is neither Exception nor String (" + o.getClass().getName() + ") as String: " + val); + config.getErrorApi().report(val); + } + } + } catch (Exception e) { + log.error("ErrbitApi.run: unexpected exception: "+shortError(e)); + } + } } }