diff --git a/.gitignore b/.gitignore index bacfbd4..78cee17 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ bin **target **.DS_Store **.vscode +Main.java diff --git a/build.gradle b/build.gradle index 09a113f..84c61d5 100644 --- a/build.gradle +++ b/build.gradle @@ -36,6 +36,8 @@ repositories { dependencies { implementation 'com.squareup.okhttp3:okhttp:4.9.2' implementation 'com.google.code.gson:gson:2.8.6' + // https://mvnrepository.com/artifact/com.tdunning/t-digest + implementation group: 'com.tdunning', name: 't-digest', version: '3.3' api 'com.google.code.findbugs:jsr305:3.0.2' testImplementation 'com.github.tomakehurst:wiremock:2.27.2' testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.8.1' diff --git a/src/main/java/io/airbrake/javabrake/AsyncSender.java b/src/main/java/io/airbrake/javabrake/AsyncSender.java index 17fc3f6..4aee530 100644 --- a/src/main/java/io/airbrake/javabrake/AsyncSender.java +++ b/src/main/java/io/airbrake/javabrake/AsyncSender.java @@ -2,8 +2,12 @@ import java.util.concurrent.CompletableFuture; +import okhttp3.Response; + public interface AsyncSender { - void setHost(String host); + void setErrorHost(String host); + void setAPMHost(String host); CompletableFuture send(Notice notice); + CompletableFuture sendRouteStats(Routes object); } diff --git a/src/main/java/io/airbrake/javabrake/Config.java b/src/main/java/io/airbrake/javabrake/Config.java index af43718..649ed5f 100644 --- a/src/main/java/io/airbrake/javabrake/Config.java +++ b/src/main/java/io/airbrake/javabrake/Config.java @@ -6,6 +6,9 @@ public class Config { public int projectId; public String projectKey; public String errorHost = DEFAULT_ERROR_HOST; + public String apmHost = DEFAULT_ERROR_HOST; public Boolean errorNotifications = true; + public Boolean apmNotifications = false; public Boolean remoteConfig = true; + public String environment = ""; } diff --git a/src/main/java/io/airbrake/javabrake/Metrics.java b/src/main/java/io/airbrake/javabrake/Metrics.java new file mode 100644 index 0000000..7038c59 --- /dev/null +++ b/src/main/java/io/airbrake/javabrake/Metrics.java @@ -0,0 +1,128 @@ +package io.airbrake.javabrake; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +public class Metrics { + public static int FLUSH_PERIOD = 15; + + Date startTime = new Date(); + Date endTime; + Map spans = new HashMap<>(); + Span currSpan; + Map groups = new HashMap<>(); + + public void end() { + if (endTime == null) + endTime = new Date(); + } + + protected Span newSpan(String name, Date startTime) { + return new Span(this, name, startTime); + } + + public void startSpan(String name, Date startTime) { + if (this.currSpan != null) { + if (this.currSpan.name == name) { + this.currSpan.level += 1; + return; + } + this.currSpan.pause(); + } + + Span span = this.spans.get(name); + if (span == null) { + span = this.newSpan(name, startTime); + this.spans.put(name, span); + } else + span.resume(); + + span.parent = this.currSpan; + this.currSpan = span; + } + + public void endSpan(String name, Date endTime) { + if (this.currSpan != null && this.currSpan.name == name) { + if (this._endSpan(this.currSpan, endTime)) { + this.currSpan = this.currSpan.parent; + if (this.currSpan != null) + this.currSpan.resume(); + return; + } + } + + Span span = this.spans.get(name); + if (span == null) + return; + this._endSpan(span, endTime); + } + + protected boolean _endSpan(Span span, Date endTime) { + + if (span.level > 0) { + span.level -= 1; + return false; + } + + span.end(endTime); + this.spans.get(span.name); + return true; + + } + + protected void _inc_group(String name, long ms) { + this.groups.put(name, (this.groups.getOrDefault(name, (long) 0) + ms)); + } +} + +class Span { + + Metrics metric; + Span parent; + Date startTime; + Date endTime; + String name; + long dur = 0; + int level = 0; + + public Span(Metrics metric, String name, Date startTime) { + this.metric = metric; + this.startTime = startTime; + this.name = name; + } + + public void init() { + this.startTime = new Date(); + this.endTime = null; + } + + public void end(Date endTime) { + if (endTime != null) + this.endTime = endTime; + else { + this.endTime = new Date(); + } + + this.dur += (this.endTime.getTime() - this.metric.spans.get(this.name).startTime.getTime()); + this.metric._inc_group(this.name, this.dur); + this.metric = null; + } + + protected void pause() { + if (this.paused()) + return; + this.dur += (new Date().getTime() - this.startTime.getTime()); + this.startTime = null; + } + + protected boolean paused() { + return this.startTime == null; + } + + protected void resume() { + if (!this.paused()) + return; + this.startTime = new Date(); + } +} diff --git a/src/main/java/io/airbrake/javabrake/Notifier.java b/src/main/java/io/airbrake/javabrake/Notifier.java index c53628b..45493b3 100644 --- a/src/main/java/io/airbrake/javabrake/Notifier.java +++ b/src/main/java/io/airbrake/javabrake/Notifier.java @@ -1,18 +1,17 @@ package io.airbrake.javabrake; import java.util.concurrent.Future; -// import java.io.IOException; import java.util.ArrayList; import java.util.List; -// import java.util.HashMap; -// import javax.annotation.Nullable; import java.util.concurrent.CompletableFuture; -/** Airbrake notifier. */ +//** Airbrake notifier. */ public class Notifier { AsyncSender asyncSender; SyncSender syncSender; + protected static List routes = new ArrayList<>(); + final List hooks = new ArrayList<>(); final List filters = new ArrayList<>(); @@ -28,7 +27,11 @@ public Notifier(Config config) { this.syncSender = new OkSyncSender(config); if (config.errorHost != null) { - this.setHost(config.errorHost); + this.setErrorHost(config.errorHost); + } + + if (config.apmHost != null) { + this.setAPMHost(config.apmHost); } if (Airbrake.notifier == null) { @@ -46,9 +49,21 @@ public Notifier(Config config) { } } - public Notifier setHost(String host) { - this.asyncSender.setHost(host); - this.syncSender.setHost(host); +// public Config getConfig() { +// return config; +// } + +public Notifier setErrorHost(String host) { + this.config.errorHost = host; + this.asyncSender.setErrorHost(host); + this.syncSender.setErrorHost(host); + return this; +} + + public Notifier setAPMHost(String host) { + this.config.apmHost = host; + this.asyncSender.setAPMHost(host); + this.syncSender.setAPMHost(host); return this; } diff --git a/src/main/java/io/airbrake/javabrake/OkAsyncSender.java b/src/main/java/io/airbrake/javabrake/OkAsyncSender.java index a224211..7224f5e 100644 --- a/src/main/java/io/airbrake/javabrake/OkAsyncSender.java +++ b/src/main/java/io/airbrake/javabrake/OkAsyncSender.java @@ -1,6 +1,12 @@ package io.airbrake.javabrake; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.CompletableFuture; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + import java.io.IOException; import okhttp3.Call; import okhttp3.Callback; @@ -10,6 +16,9 @@ public class OkAsyncSender extends OkSender implements AsyncSender { static final int queuedCallsLimit = 1000; static final IOException queuedCallsLimitException = new IOException("too many HTTP requests queued for execution"); + + List routeList = new ArrayList<>(); + Gson gson = new GsonBuilder().disableHtmlEscaping().create(); public OkAsyncSender(Config config) { super(config); @@ -44,7 +53,7 @@ public CompletableFuture send(Notice notice) { OkAsyncSender sender = this; okhttp - .newCall(this.buildRequest(notice)) + .newCall(this.buildErrorRequest(notice)) .enqueue( new Callback() { @Override @@ -66,4 +75,50 @@ public void onResponse(Call call, Response resp) { }); return future; } + + @Override + public CompletableFuture sendRouteStats(Routes object) { + CompletableFuture future = new CompletableFuture<>(); + + if (!config.apmNotifications) { + future.completeExceptionally(new IOException("apmNotifications is disabled")); + return future; + } + + if (object == null) { + future.completeExceptionally(new IOException("Route is null")); + return future; + } + + if (object.routes == null || object.routes.size() == 0 ) { + future.completeExceptionally(new IOException("Route is null")); + return future; + } + + //OkAsyncSender sender = this; + okhttp + .newCall(this.buildAPMRequest(gson.toJson(object,Routes.class),"routes-stats")) + .enqueue( + new Callback() { + @Override + public void onFailure(Call call, IOException e) { + + future.completeExceptionally(e); + } + + @Override + public void onResponse(Call call, Response resp) { + // try { + // NoticeCode data = sender.parseJson(resp, NoticeCode.class); + // System.out.println(data.message); + // // Notifier.routes.clear(); + // } catch (Exception e) { + + // } + future.complete(resp); + + } + }); + return future; + } } diff --git a/src/main/java/io/airbrake/javabrake/OkSender.java b/src/main/java/io/airbrake/javabrake/OkSender.java index 838b5cf..b6eb68e 100644 --- a/src/main/java/io/airbrake/javabrake/OkSender.java +++ b/src/main/java/io/airbrake/javabrake/OkSender.java @@ -35,7 +35,9 @@ public class OkSender { final Config config; final int projectId; final String projectKey; - String url; + //String url; + String apmUrl; + String errorUrl; final AtomicLong rateLimitReset = new AtomicLong(0); @@ -43,23 +45,54 @@ public OkSender(Config config) { this.config = config; this.projectId = config.projectId; this.projectKey = config.projectKey; - this.url = this.buildUrl(config.errorHost); + this.errorUrl = this.buildErrorUrl(config.errorHost); + this.apmUrl = this.buildAPMUrl(config.errorHost); } public static void setOkHttpClient(OkHttpClient okhttp) { OkSender.okhttp = okhttp; } - public void setHost(String host) { - this.url = this.buildUrl(host); + // public void setHost(String host) { + // this.url = this.buildUrl(host); + // } + + public void setErrorHost(String host) { + this.errorUrl = this.buildErrorUrl(host); } - Request buildRequest(Notice notice) { + public void setAPMHost(String host) { + this.apmUrl = this.buildAPMUrl(host); + } + + // Request buildRequest(Notice notice) { + // String data = this.noticeJson(notice); + // RequestBody body = RequestBody.create(data,JSONType); + // return new Request.Builder() + // .header("Authorization", "Bearer " + this.projectKey) + // .url(this.url) + // .post(body) + // .build(); + // } + + Request buildErrorRequest(Notice notice) { + this.errorUrl = this.buildErrorUrl(config.errorHost); String data = this.noticeJson(notice); RequestBody body = RequestBody.create(data,JSONType); return new Request.Builder() .header("Authorization", "Bearer " + this.projectKey) - .url(this.url) + .url(this.errorUrl) + .post(body) + .build(); + } + + Request buildAPMRequest(String json,String method) { + this.apmUrl = this.buildAPMUrl(config.apmHost); + RequestBody body = RequestBody.create(json,JSONType); + this.apmUrl = this.apmUrl+method; + return new Request.Builder() + .header("Authorization", "Bearer " + this.projectKey) + .url(this.apmUrl) .post(body) .build(); } @@ -84,10 +117,18 @@ String noticeJson(Notice notice) { return data; } - String buildUrl(String host) { + // String buildUrl(String host) { + // return String.format("%s/api/v3/projects/%d/notices", host, this.projectId); + // } + + String buildErrorUrl(String host) { return String.format("%s/api/v3/projects/%d/notices", host, this.projectId); } + String buildAPMUrl(String host) { + return String.format("%s/api/v5/projects/%d/", host, this.projectId); + } + void parseResponse(Response resp, Notice notice) { if (resp.isSuccessful()) { try { diff --git a/src/main/java/io/airbrake/javabrake/OkSyncSender.java b/src/main/java/io/airbrake/javabrake/OkSyncSender.java index 722826c..c99b017 100644 --- a/src/main/java/io/airbrake/javabrake/OkSyncSender.java +++ b/src/main/java/io/airbrake/javabrake/OkSyncSender.java @@ -21,7 +21,7 @@ public Notice send(Notice notice) { return notice; } - Call call = okhttp.newCall(this.buildRequest(notice)); + Call call = okhttp.newCall(this.buildErrorRequest(notice)); try (Response resp = call.execute()) { this.parseResponse(resp, notice); } catch (IOException e) { diff --git a/src/main/java/io/airbrake/javabrake/PollTask.java b/src/main/java/io/airbrake/javabrake/PollTask.java index 2f43fac..a61e35d 100644 --- a/src/main/java/io/airbrake/javabrake/PollTask.java +++ b/src/main/java/io/airbrake/javabrake/PollTask.java @@ -19,6 +19,7 @@ class PollTask extends TimerTask { final private SettingsData data; final private Boolean origErrorNotifications; + final private Boolean origAPMNotifications; final private OkHttpClient client = new OkHttpClient(); final private Gson gson = new Gson(); @@ -47,6 +48,7 @@ public PollTask( this.data = new SettingsData(this.projectId, new RemoteConfigJSON()); this.origErrorNotifications = config.errorNotifications; + this.origAPMNotifications = config.apmNotifications; } public void run() { @@ -56,6 +58,8 @@ public void run() { } catch (IOException e) { this.setErrorHost(this.data); this.processErrorNotifications(this.data); + this.setAPMHost(data); + this.processAPMNotifications(data); return; } @@ -65,11 +69,15 @@ public void run() { } catch (Exception e) { this.setErrorHost(this.data); this.processErrorNotifications(this.data); + this.setAPMHost(data); + this.processAPMNotifications(data); return; } this.setErrorHost(this.data); this.processErrorNotifications(this.data); + this.setAPMHost(data); + this.processAPMNotifications(data); } String request() throws IOException { @@ -90,8 +98,8 @@ String request() throws IOException { void setErrorHost(SettingsData data) { this.config.errorHost = this.getErrorHost(); - this.asyncSender.setHost(this.config.errorHost); - this.syncSender.setHost(this.config.errorHost); + this.asyncSender.setErrorHost(this.config.errorHost); + this.syncSender.setErrorHost(this.config.errorHost); } public String getErrorHost() { @@ -110,4 +118,24 @@ void processErrorNotifications(SettingsData data) { this.config.errorNotifications = data.errorNotifications(); } + + void setAPMHost(SettingsData data) { + String remoteHost = this.data.apmHost(); + if (remoteHost == null) { + this.config.apmHost = Config.DEFAULT_ERROR_HOST; + } else { + this.config.apmHost = remoteHost; + } + + this.asyncSender.setAPMHost(this.config.apmHost); + this.syncSender.setAPMHost(this.config.apmHost); + } + + void processAPMNotifications(SettingsData data) { + if (!this.origAPMNotifications) { + return; + } + + this.config.apmNotifications = data.performanceStats(); + } } diff --git a/src/main/java/io/airbrake/javabrake/RemoteSettings.java b/src/main/java/io/airbrake/javabrake/RemoteSettings.java index 0c63813..be83f09 100644 --- a/src/main/java/io/airbrake/javabrake/RemoteSettings.java +++ b/src/main/java/io/airbrake/javabrake/RemoteSettings.java @@ -26,7 +26,6 @@ public RemoteSettings( this.config = config; this.asyncSender = asyncSender; this.syncSender = syncSender; - this.timer = new Timer(); } diff --git a/src/main/java/io/airbrake/javabrake/RouteMetric.java b/src/main/java/io/airbrake/javabrake/RouteMetric.java new file mode 100644 index 0000000..89d0603 --- /dev/null +++ b/src/main/java/io/airbrake/javabrake/RouteMetric.java @@ -0,0 +1,34 @@ +package io.airbrake.javabrake; + +public class RouteMetric extends Metrics { + public static String HTTP_HANDLER = "http.handler"; + String method; + String route; + int statusCode; + String contentType; + + public RouteMetric(String method, String route, int statusCode, String contentType) { + super(); + this.method = method; + this.route = route; + this.statusCode = statusCode; + this.contentType = contentType; + startSpan(HTTP_HANDLER, this.startTime); + } + + public RouteMetric() { + } + + public void end() { + super.end(); + this.endSpan(HTTP_HANDLER, this.endTime); + } + + public String getResponseType() { + if (this.statusCode >= 500) + return "5xx"; + if (this.statusCode >= 400) + return "4xx"; + return this.contentType.split(";")[0].split("/")[-1]; + } +} diff --git a/src/main/java/io/airbrake/javabrake/RouteStats.java b/src/main/java/io/airbrake/javabrake/RouteStats.java new file mode 100644 index 0000000..76c637d --- /dev/null +++ b/src/main/java/io/airbrake/javabrake/RouteStats.java @@ -0,0 +1,93 @@ +package io.airbrake.javabrake; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.ArrayList; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.CompletableFuture; + +import okhttp3.Response; + +public class RouteStats extends TdigestStat { + String method; + String route; + int statusCode; + String time; + + static transient Config config; + static transient Throwable exception; + + public RouteStats(String method, String route, int statusCode, String time) { + this.method = method; + this.route = route; + this.statusCode = statusCode; + this.time = time; + } + + public static void notify(RouteMetric metrics, Config config) { + try { + + RouteStats.config = config; + String date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX").format(new Date()); + + RouteStats routeStats = new RouteStats(metrics.method, metrics.route, metrics.statusCode, date); + Notifier.routes.add(routeStats); + + long ms = metrics.endTime.getTime() - metrics.startTime.getTime(); + routeStats.add(ms); + routeStats.tdigest = routeStats.getData(); + // routeStats.tdigest = "AAAAAkA0AAAAAAAAAAAAAUdqYAAB"; + + if (!RouteTimerTask.hasStarted) + RouteTimerTask.start(); + } catch (Exception e) { + RouteStats.exception = e; + } + } +} + +class RouteTimerTask extends TimerTask { + static Timer rTimer = new Timer(); + static boolean hasStarted = false; + + public static void start() { + if (!hasStarted) { + hasStarted = true; + rTimer.schedule(new RouteTimerTask(), 0, Metrics.FLUSH_PERIOD * 1000); + } + } + + @Override + public void run() { + // TODO Auto-generated method stub + + hasStarted = true; + Routes routes = new Routes(RouteStats.config.environment, Notifier.routes); + Notifier.routes = new ArrayList<>(); + CompletableFuture future = new OkAsyncSender(RouteStats.config).sendRouteStats(routes); + future.whenComplete( + (value, exception) -> { + if (exception != null) { + RouteStats.exception = exception; + Notifier.routes.addAll(routes.routes); + } + }); + } + + public static void stop() { + rTimer.cancel(); + } +} + +class Routes { + String environment; + List routes = new ArrayList<>(); + + public Routes(String environment, List routes) { + this.environment = environment; + this.routes = routes; + + } +} \ No newline at end of file diff --git a/src/main/java/io/airbrake/javabrake/SyncSender.java b/src/main/java/io/airbrake/javabrake/SyncSender.java index d761104..6cf10c8 100644 --- a/src/main/java/io/airbrake/javabrake/SyncSender.java +++ b/src/main/java/io/airbrake/javabrake/SyncSender.java @@ -1,7 +1,8 @@ package io.airbrake.javabrake; public interface SyncSender { - void setHost(String host); - + //void setHost(String host); + void setErrorHost(String host); + void setAPMHost(String host); Notice send(Notice notice); } diff --git a/src/main/java/io/airbrake/javabrake/Tdigest.java b/src/main/java/io/airbrake/javabrake/Tdigest.java new file mode 100644 index 0000000..1c1aa58 --- /dev/null +++ b/src/main/java/io/airbrake/javabrake/Tdigest.java @@ -0,0 +1,150 @@ +package io.airbrake.javabrake; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Base64; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import com.tdunning.math.stats.Centroid; +import com.tdunning.math.stats.TDigest; + +public class Tdigest { + +} + + +class TdigestStat { + int count = 0; + double sum = 0; + double sumsq = 0; + + transient TDigest td = TDigest.createAvlTreeDigest(10); + String tdigest; + + public void add(long ms) { + this.count += 1; + this.sum += ms; + this.sumsq += ms * ms; + this.td.add(ms); + } + + transient int SMALL_ENCODING = 2; + transient List list = new ArrayList(); + + public String getData() { + + ByteBuffer buf = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN); + buf.putInt(SMALL_ENCODING); + list.add(buf.array()); + + buf= ByteBuffer.allocate(8).order(ByteOrder.BIG_ENDIAN); + buf.putDouble(10); + list.add(buf.array()); + + buf = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN); + buf.putInt((int)td.size()); + list.add(buf.array()); + + + double x = 0; + for (Centroid centroid : td.centroids()) { + buf = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN); + double delta = centroid.mean() - x; + x = centroid.mean(); + buf.putFloat((float) delta); + list.add(buf.array()); + } + + for (Centroid centroid : td.centroids()) { + buf = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN); + int n = centroid.count(); + int k = 0; + while (n < 0 || n > 0x7f) { + byte b = (byte) (0x80 | (0x7f & n)); + buf.put(b); + n = n >>> 7; + k++; + if (k >= 6) { + throw new IllegalStateException("Size is implausibly large"); + } + } + buf.put((byte) n); + list.add(buf.array()); + } + + + int size = 0; + for(int i=0;i b, int num) + { + while(true){ + int c = num & 0x7F; + num >>= 7; + if(num > 0) + { + b.add(new byte[] {(byte)(c | 0x80)}); + } + else + { + b.add(new byte[] {(byte)(c)}); + break; + } + } + } + + public byte[] serialize(TDigest tDigest) { + byte[] bytes = new byte[tDigest.byteSize()]; + tDigest.asSmallBytes(ByteBuffer.wrap(bytes)); + return bytes; + } +} + +class TdigestStatGroup extends TdigestStat { + + Map groups; + + public TdigestStatGroup() { + super(); + groups = new HashMap<>(); + } + + public void addGroups(long total_ms, Map group) { + this.add(total_ms); + for (Map.Entry entry : group.entrySet()) { + this.addGroup(entry.getKey(), group.get(entry.getKey())); + } + + } + + public void addGroup(String name, long ms) { + TdigestStat stat = groups.get(name); + if (stat == null) { + stat = new TdigestStat(); + this.groups.put(name, stat); + } + + stat.add(ms); + } + +} \ No newline at end of file diff --git a/src/test/java/io/airbrake/javabrake/MetricsTest.java b/src/test/java/io/airbrake/javabrake/MetricsTest.java new file mode 100644 index 0000000..fac8e8d --- /dev/null +++ b/src/test/java/io/airbrake/javabrake/MetricsTest.java @@ -0,0 +1,42 @@ +package io.airbrake.javabrake; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.Date; +import org.junit.jupiter.api.Test; + +public class MetricsTest { + @Test + public void test() + { + Metrics metric = new Metrics(); + metric.startSpan("c", new Date()); + try { + Thread.sleep(5000); + + metric.endSpan("c", new Date()); + + Thread.sleep(5000); + + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + metric.startSpan("c1", new Date()); + + try { + + Thread.sleep(1000); + + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + metric.endSpan("c1", new Date()); + metric.end(); + + assertTrue((metric.spans.get("c").endTime.getTime()-metric.spans.get("c").startTime.getTime())==metric.spans.get("c").dur); + assertTrue((metric.spans.get("c1").endTime.getTime()-metric.spans.get("c1").startTime.getTime())==metric.spans.get("c1").dur); + assertTrue((metric.endTime.getTime()-metric.startTime.getTime())>10000); + } + +} diff --git a/src/test/java/io/airbrake/javabrake/NotifierTest.java b/src/test/java/io/airbrake/javabrake/NotifierTest.java index e0ba9b5..4de5085 100644 --- a/src/test/java/io/airbrake/javabrake/NotifierTest.java +++ b/src/test/java/io/airbrake/javabrake/NotifierTest.java @@ -85,12 +85,17 @@ public void testReportAsync() { @Test public void testReportServerDown() { - NotifierTest.notifier.setHost("https://google.com"); + NotifierTest.notifier.setErrorHost("https://google.com"); Notice notice = NotifierTest.notifier.reportSync(this.exc); assertNotNull(notice.exception); - assertEquals( - "com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $", - notice.exception.toString()); + // assertEquals( + // "com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $", + // notice.exception.toString()); + + + assertEquals( + "com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $", + notice.exception.toString()); } @Test @@ -109,7 +114,7 @@ public void testRateLimit() { String apiURL = "/api/v3/projects/0/notices"; - notifier.setHost("http://localhost:8080"); + notifier.setErrorHost("http://localhost:8080"); // long utime = System.currentTimeMillis() / 1000L; stubFor( post(urlEqualTo(apiURL)) diff --git a/src/test/java/io/airbrake/javabrake/RouteTest.java b/src/test/java/io/airbrake/javabrake/RouteTest.java new file mode 100644 index 0000000..5238d88 --- /dev/null +++ b/src/test/java/io/airbrake/javabrake/RouteTest.java @@ -0,0 +1,111 @@ +package io.airbrake.javabrake; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; +import com.github.tomakehurst.wiremock.WireMockServer; + +public class RouteTest { + static WireMockServer wireMockServer = null; + static Notifier notifier; + Throwable exc = new IOException("hello from Java"); + static Config config = null; + + @BeforeAll + public static void init() { + wireMockServer = new WireMockServer(); // No-args constructor will start on port 8080, no HTTPS + wireMockServer.start(); + config = new Config(); + config.remoteConfig = false; + notifier = new Notifier(config); + } + + public RouteStats getRouteStats() { + RouteMetric metric = new RouteMetric("GET", "/test", 200, "application/json"); + + Metrics.FLUSH_PERIOD = 5; + metric.endTime = new Date(); + + String date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX").format(metric.startTime); + RouteStats stat = new RouteStats( + metric.method, + metric.route, + metric.statusCode, + date); + + long ms = metric.endTime.getTime() - metric.startTime.getTime(); + stat.add(ms); + return stat; + } + + @Test + public void testRoutesNotifyTryCatch() { + + config.apmNotifications = false; + RouteMetric metric = new RouteMetric("GET", "/test", 200, "application/json"); + try { + RouteStats.notify(metric,config); + } catch (NullPointerException e) { + assertTrue(true); + } + } + + @Test + public void testRoutesNotify() { + + config.apmNotifications = true; + config.projectId = 0; + notifier.setAPMHost("http://localhost:8080"); + RouteMetric metric = new RouteMetric("GET", "/test", 200, "application/json"); + + RouteMetric.FLUSH_PERIOD = 50; + metric.endTime = new Date(); + + stubFor(post(urlEqualTo("/api/v5/projects/0/routes-stats")).withHeader("Authorization", containing("Bearer ")) + .willReturn(aResponse().withBody("{}") + .withStatus(200))); + + try { + RouteStats.notify(metric,config); + Thread.sleep(5000); + } catch (Exception e) { + assertTrue(false); + } + + assertEquals(RouteStats.exception, null); + } + + @Test + public void testRouteNotifyException() { + + config.apmNotifications = true; + config.projectId = 1; + notifier.setAPMHost("http://localhost:8080"); + + RouteMetric metric = new RouteMetric(); + + stubFor(post(urlEqualTo("/api/v5/projects/1/routes-stats")).withHeader("Authorization", containing("Bearer ")) + .willReturn(aResponse().withBody("{}") + .withStatus(200))); + + try { + RouteStats.notify(metric,config); + Thread.sleep(5000); + + } catch (Exception e) { + assertTrue(false); + } + assertEquals(RouteStats.exception.toString(), "java.lang.NullPointerException"); + } + + @AfterAll + public static void closeWireMockServer() { + wireMockServer.stop(); + } +}