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