001package org.avaje.metric.elastic; 002 003import okhttp3.MediaType; 004import okhttp3.OkHttpClient; 005import okhttp3.Request; 006import okhttp3.RequestBody; 007import okhttp3.Response; 008import org.avaje.metric.report.MetricReporter; 009import org.avaje.metric.report.ReportMetrics; 010import org.slf4j.Logger; 011import org.slf4j.LoggerFactory; 012 013import java.io.IOException; 014import java.io.StringWriter; 015import java.net.ConnectException; 016import java.net.UnknownHostException; 017import java.time.LocalDate; 018import java.time.format.DateTimeFormatter; 019import java.time.format.DateTimeFormatterBuilder; 020import java.util.concurrent.TimeUnit; 021 022/** 023 * Http(s) based Reporter that sends JSON formatted metrics directly to Elastic. 024 */ 025public class ElasticHttpReporter implements MetricReporter { 026 027 private static final Logger logger = LoggerFactory.getLogger(ElasticHttpReporter.class); 028 029 private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); 030 031 private static final DateTimeFormatter todayFormat 032 = new DateTimeFormatterBuilder() 033 .appendPattern("yyyy.MM.dd") 034 .toFormatter(); 035 036 037 private final OkHttpClient client; 038 039 private final String bulkUrl; 040 041 private final ElasticReporterConfig config; 042 043 public ElasticHttpReporter(ElasticReporterConfig config) { 044 this.client = getClient(config); 045 this.config = config; 046 this.bulkUrl = config.getUrl() + "/_bulk"; 047 048 // put the template to elastic if it is not already there 049 new TemplateApply(client, config.getUrl(), config.getTemplateName()).run(); 050 } 051 052 private OkHttpClient getClient(ElasticReporterConfig config) { 053 054 OkHttpClient client = config.getClient(); 055 if (client != null) { 056 return client; 057 } else { 058 return new OkHttpClient.Builder() 059 .connectTimeout(config.getConnectTimeout(), TimeUnit.SECONDS) 060 .readTimeout(config.getReadTimeout(), TimeUnit.SECONDS) 061 .writeTimeout(config.getWriteTimeout(), TimeUnit.SECONDS) 062 .build(); 063 } 064 } 065 066 /** 067 * Send the non-empty metrics that were collected to the remote repository. 068 */ 069 @Override 070 public void report(ReportMetrics reportMetrics) { 071 072 StringWriter writer = new StringWriter(1000); 073 BulkJsonWriteVisitor jsonVisitor = new BulkJsonWriteVisitor(writer, reportMetrics, config, today()); 074 try { 075 jsonVisitor.write(); 076 } catch (IOException e) { 077 logger.error("Failed to write Bulk JSON to send", e); 078 return; 079 } 080 081 String json = writer.toString(); 082 if (logger.isTraceEnabled()) { 083 logger.trace("Sending:\n{}", json); 084 } 085 086 RequestBody body = RequestBody.create(JSON, json); 087 Request request = new Request.Builder() 088 .url(bulkUrl) 089 .post(body) 090 .build(); 091 092 try { 093 try (Response response = client.newCall(request).execute()) { 094 if (!response.isSuccessful()) { 095 logger.warn("Unsuccessful sending metrics payload to server - {}", response.body().string()); 096 storeJsonForResend(json); 097 } else if (logger.isTraceEnabled()) { 098 logger.trace("Bulk Response - {}", response.body().string()); 099 } 100 } 101 102 } catch (UnknownHostException e) { 103 logger.info("UnknownHostException trying to sending metrics to server: " + e.getMessage()); 104 storeJsonForResend(json); 105 106 } catch (ConnectException e) { 107 logger.info("Connection error sending metrics to server: " + e.getMessage()); 108 storeJsonForResend(json); 109 110 } catch (Exception e) { 111 logger.error("Unexpected error sending metrics to server", e); 112 storeJsonForResend(json); 113 } 114 } 115 116 private String today() { 117 return todayFormat.format(LocalDate.now()); 118 } 119 120 protected void storeJsonForResend(String json) { 121 // override this to support store and re-send 122 } 123 124 125 @Override 126 public void cleanup() { 127 // Do nothing 128 } 129 130}