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}