AzureMonitorExporterBuilder.java
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.opentelemetry.exporter.azuremonitor;
import com.microsoft.opentelemetry.exporter.azuremonitor.implementation.ApplicationInsightsClientImpl;
import com.microsoft.opentelemetry.exporter.azuremonitor.implementation.ApplicationInsightsClientImplBuilder;
import com.microsoft.opentelemetry.exporter.azuremonitor.implementation.NdJsonSerializer;
import com.azure.core.http.HttpClient;
import com.azure.core.http.HttpPipeline;
import com.azure.core.http.policy.HttpLogDetailLevel;
import com.azure.core.http.policy.HttpLogOptions;
import com.azure.core.http.policy.HttpPipelinePolicy;
import com.azure.core.http.policy.RetryPolicy;
import com.azure.core.util.Configuration;
import com.azure.core.util.logging.ClientLogger;
import com.azure.core.util.serializer.JacksonAdapter;
import com.fasterxml.jackson.databind.module.SimpleModule;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* This class provides a fluent builder API to instantiate {@link AzureMonitorExporter} that implements
* {@link SpanExporter} interface defined by OpenTelemetry API specification.
*/
public final class AzureMonitorExporterBuilder {
private final ClientLogger logger = new ClientLogger(AzureMonitorExporterBuilder.class);
private final ApplicationInsightsClientImplBuilder restServiceClientBuilder;
private String instrumentationKey;
/**
* Creates an instance of {@link AzureMonitorExporterBuilder}.
*/
public AzureMonitorExporterBuilder() {
restServiceClientBuilder = new ApplicationInsightsClientImplBuilder();
}
/**
* Sets the service endpoint for the Azure Monitor Exporter.
* @param endpoint The URL of the Azure Monitor Exporter endpoint.
* @return The updated {@link AzureMonitorExporterBuilder} object.
* @throws NullPointerException if {@code endpoint} is null.
* @throws IllegalArgumentException if {@code endpoint} cannot be parsed into a valid URL.
*/
AzureMonitorExporterBuilder endpoint(String endpoint) {
Objects.requireNonNull(endpoint, "'endpoint' cannot be null.");
try {
URL url = new URL(endpoint);
restServiceClientBuilder.host(url.getProtocol() + "://" + url.getHost());
} catch (MalformedURLException ex) {
throw logger.logExceptionAsWarning(
new IllegalArgumentException("'endpoint' must be a valid URL.", ex));
}
return this;
}
/**
* Sets the HTTP pipeline to use for the service client. If {@code pipeline} is set, all other settings are
* ignored, apart from {@link #endpoint(String) endpoint}.
*
* @param httpPipeline The HTTP pipeline to use for sending service requests and receiving responses.
* @return The updated {@link AzureMonitorExporterBuilder} object.
*/
public AzureMonitorExporterBuilder pipeline(HttpPipeline httpPipeline) {
restServiceClientBuilder.pipeline(httpPipeline);
return this;
}
/**
* Sets the HTTP client to use for sending and receiving requests to and from the service.
*
* @param client The HTTP client to use for requests.
* @return The updated {@link AzureMonitorExporterBuilder} object.
*/
public AzureMonitorExporterBuilder httpClient(HttpClient client) {
restServiceClientBuilder.httpClient(client);
return this;
}
/**
* Sets the logging configuration for HTTP requests and responses.
*
* <p> If logLevel is not provided, default value of {@link HttpLogDetailLevel#NONE} is set. </p>
* @param logOptions The logging configuration to use when sending and receiving HTTP requests/responses.
*
* @return The updated {@link AzureMonitorExporterBuilder} object.
*/
public AzureMonitorExporterBuilder httpLogOptions(HttpLogOptions logOptions) {
restServiceClientBuilder.httpLogOptions(logOptions);
return this;
}
/**
* Sets the {@link RetryPolicy} that is used when each request is sent.
* <p>
* The default retry policy will be used if not provided to build {@link AzureMonitorExporterBuilder} .
* @param retryPolicy user's retry policy applied to each request.
*
* @return The updated {@link AzureMonitorExporterBuilder} object.
*/
public AzureMonitorExporterBuilder retryPolicy(RetryPolicy retryPolicy) {
restServiceClientBuilder.retryPolicy(retryPolicy);
return this;
}
/**
* Adds a policy to the set of existing policies that are executed after required policies.
* @param policy The retry policy for service requests.
*
* @return The updated {@link AzureMonitorExporterBuilder} object.
* @throws NullPointerException If {@code policy} is {@code null}.
*/
public AzureMonitorExporterBuilder addPolicy(HttpPipelinePolicy policy) {
restServiceClientBuilder.addPolicy(Objects.requireNonNull(policy, "'policy' cannot be null."));
return this;
}
/**
* Sets the configuration store that is used during construction of the service client.
* <p>
* The default configuration store is a clone of the {@link Configuration#getGlobalConfiguration() global
* configuration store}, use {@link Configuration#NONE} to bypass using configuration settings during construction.
*
* @param configuration The configuration store used to
* @return The updated {@link AzureMonitorExporterBuilder} object.
*/
public AzureMonitorExporterBuilder configuration(Configuration configuration) {
restServiceClientBuilder.configuration(configuration);
return this;
}
/**
* The connection string to use for exporting telemetry events to Azure Monitor.
* @param connectionString The connection string for the Azure Monitor resource.
* @return The updated {@link AzureMonitorExporterBuilder} object.
* @throws NullPointerException If the connection string is {@code null}.
* @throws IllegalArgumentException If the connection string is invalid.
*/
public AzureMonitorExporterBuilder connectionString(String connectionString) {
Map<String, String> keyValues = extractKeyValuesFromConnectionString(connectionString);
if (!keyValues.containsKey("InstrumentationKey")) {
throw logger.logExceptionAsError(
new IllegalArgumentException("InstrumentationKey not found in connectionString"));
}
this.instrumentationKey = keyValues.get("InstrumentationKey");
String endpoint = keyValues.get("IngestionEndpoint");
if (endpoint != null) {
this.endpoint(endpoint);
}
return this;
}
private Map<String, String> extractKeyValuesFromConnectionString(String connectionString) {
Objects.requireNonNull(connectionString);
Map<String, String> keyValues = new HashMap<>();
String[] splits = connectionString.split(";");
for (String split : splits) {
String[] keyValPair = split.split("=");
if (keyValPair.length == 2) {
keyValues.put(keyValPair[0], keyValPair[1]);
}
}
return keyValues;
}
/**
* Creates a {@link MonitorExporterClient} based on options set in the builder. Every time {@code
* buildAsyncClient()} is called a new instance of {@link MonitorExporterClient} is created.
*
* <p>
* If {@link #pipeline(HttpPipeline) pipeline} is set, then the {@code pipeline} and {@link #endpoint(String)
* endpoint} are used to create the {@link MonitorExporterAsyncClient client}. All other builder settings are
* ignored.
* </p>
* @return A {@link MonitorExporterClient} with the options set from the builder.
* @throws NullPointerException if {@link #endpoint(String) endpoint} has not been set.
*/
MonitorExporterClient buildClient() {
return new MonitorExporterClient(buildAsyncClient());
}
/**
* Creates a {@link MonitorExporterAsyncClient} based on options set in the builder. Every time {@code
* buildAsyncClient()} is called a new instance of {@link MonitorExporterAsyncClient} is created.
*
* <p>
* If {@link #pipeline(HttpPipeline) pipeline} is set, then the {@code pipeline} and {@link #endpoint(String)
* endpoint} are used to create the {@link MonitorExporterAsyncClient client}. All other builder settings are
* ignored.
* </p>
* @return A {@link MonitorExporterAsyncClient} with the options set from the builder.
*/
MonitorExporterAsyncClient buildAsyncClient() {
// Customize serializer to use NDJSON
final SimpleModule ndjsonModule = new SimpleModule("Ndjson List Serializer");
JacksonAdapter jacksonAdapter = new JacksonAdapter();
jacksonAdapter.serializer().registerModule(ndjsonModule);
ndjsonModule.addSerializer(new NdJsonSerializer());
restServiceClientBuilder.serializerAdapter(jacksonAdapter);
ApplicationInsightsClientImpl restServiceClient = restServiceClientBuilder.buildClient();
return new MonitorExporterAsyncClient(restServiceClient);
}
/**
* Creates an {@link AzureMonitorExporter} based on the options set in the builder. This exporter is an
* implementation of OpenTelemetry {@link SpanExporter}.
*
* @return An instance of {@link AzureMonitorExporter}.
* @throws NullPointerException if the instrumentation key is not set.
*/
public AzureMonitorExporter buildExporter() {
// instrumentationKey is extracted from connectionString, so, if instrumentationKey is null
// then the error message should read "connectionString cannot be null".
Objects.requireNonNull(instrumentationKey, "'connectionString' cannot be null");
return new AzureMonitorExporter(buildClient(), instrumentationKey);
}
}