Skip to content

Commit 323174a

Browse files
Add metrics to ZipkinSpanExporter (#4501)
* Add metrics to the ZipkinSpanExporter * Add distinct transport name for HTTP/JSON trace metrics * Use :expoters:common * Fix javadoc Co-authored-by: Jack Berg <[email protected]>
1 parent abbafb3 commit 323174a

File tree

8 files changed

+178
-39
lines changed

8 files changed

+178
-39
lines changed
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
Comparing source compatibility of against
2-
No changes.
2+
*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.exporter.zipkin.ZipkinSpanExporterBuilder (not serializable)
3+
=== CLASS FILE FORMAT VERSION: 52.0 <- 52.0
4+
+++ NEW METHOD: PUBLIC(+) io.opentelemetry.exporter.zipkin.ZipkinSpanExporterBuilder setMeterProvider(io.opentelemetry.api.metrics.MeterProvider)

exporters/common/src/main/java/io/opentelemetry/exporter/internal/ExporterMetrics.java

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,21 +58,39 @@ public void addFailed(long value) {
5858
exported.add(value, failedAttrs);
5959
}
6060

61-
/** Create an instance for recording gRPC exporter metrics. */
61+
/**
62+
* Create an instance for recording exporter metrics under the meter {@code
63+
* "io.opentelemetry.exporters." + exporterName + "-grpc}".
64+
*/
6265
public static ExporterMetrics createGrpc(
6366
String exporterName, String type, MeterProvider meterProvider) {
6467
return new ExporterMetrics(meterProvider, exporterName, type, "grpc");
6568
}
6669

67-
/** Create an instance for recording gRPC OkHttp exporter metrics. */
70+
/**
71+
* Create an instance for recording exporter metrics under the meter {@code
72+
* "io.opentelemetry.exporters." + exporterName + "-grpc-okhttp}".
73+
*/
6874
public static ExporterMetrics createGrpcOkHttp(
6975
String exporterName, String type, MeterProvider meterProvider) {
7076
return new ExporterMetrics(meterProvider, exporterName, type, "grpc-okhttp");
7177
}
7278

73-
/** Create an instance for recording http/protobuf exporter metrics. */
79+
/**
80+
* Create an instance for recording exporter metrics under the meter {@code
81+
* "io.opentelemetry.exporters." + exporterName + "-http}".
82+
*/
7483
public static ExporterMetrics createHttpProtobuf(
7584
String exporterName, String type, MeterProvider meterProvider) {
7685
return new ExporterMetrics(meterProvider, exporterName, type, "http");
7786
}
87+
88+
/**
89+
* Create an instance for recording exporter metrics under the meter {@code
90+
* "io.opentelemetry.exporters." + exporterName + "-http-json}".
91+
*/
92+
public static ExporterMetrics createHttpJson(
93+
String exporterName, String type, MeterProvider meterProvider) {
94+
return new ExporterMetrics(meterProvider, exporterName, type, "http-json");
95+
}
7896
}

exporters/common/src/main/java/io/opentelemetry/exporter/internal/okhttp/OkHttpExporter.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,10 @@ public final class OkHttpExporter<T extends Marshaler> {
6868
this.headers = headers;
6969
this.compressionEnabled = compressionEnabled;
7070
this.requestBodyCreator = exportAsJson ? JsonRequestBody::new : ProtoRequestBody::new;
71-
this.exporterMetrics = ExporterMetrics.createHttpProtobuf(exporterName, type, meterProvider);
71+
this.exporterMetrics =
72+
exportAsJson
73+
? ExporterMetrics.createHttpJson(exporterName, type, meterProvider)
74+
: ExporterMetrics.createHttpProtobuf(exporterName, type, meterProvider);
7275
}
7376

7477
public CompletableResultCode export(T exportRequest, int numItems) {

exporters/zipkin/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ dependencies {
1313

1414
api("io.zipkin.reporter2:zipkin-reporter")
1515

16+
implementation(project(":exporters:common"))
1617
implementation(project(":semconv"))
1718

1819
implementation("io.zipkin.reporter2:zipkin-sender-okhttp3")

exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporter.java

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
import io.opentelemetry.api.common.AttributeKey;
1212
import io.opentelemetry.api.common.AttributeType;
1313
import io.opentelemetry.api.common.Attributes;
14+
import io.opentelemetry.api.metrics.MeterProvider;
1415
import io.opentelemetry.api.trace.StatusCode;
16+
import io.opentelemetry.exporter.internal.ExporterMetrics;
1517
import io.opentelemetry.sdk.common.CompletableResultCode;
1618
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
1719
import io.opentelemetry.sdk.internal.ThrottlingLogger;
@@ -35,17 +37,19 @@
3537
import zipkin2.Endpoint;
3638
import zipkin2.Span;
3739
import zipkin2.codec.BytesEncoder;
40+
import zipkin2.codec.Encoding;
3841
import zipkin2.reporter.Sender;
3942

4043
/**
41-
* This class was based on the OpenCensus zipkin exporter code at
42-
* https://github.com/census-instrumentation/opencensus-java/tree/c960b19889de5e4a7b25f90919d28b066590d4f0/exporters/trace/zipkin
44+
* This class was based on the <a
45+
* href="https://github.com/census-instrumentation/opencensus-java/tree/c960b19889de5e4a7b25f90919d28b066590d4f0/exporters/trace/zipkin">OpenCensus
46+
* zipkin exporter</a> code.
4347
*/
4448
public final class ZipkinSpanExporter implements SpanExporter {
45-
public static final String DEFAULT_ENDPOINT = "http://localhost:9411/api/v2/spans";
49+
4650
public static final Logger baseLogger = Logger.getLogger(ZipkinSpanExporter.class.getName());
4751

48-
private final ThrottlingLogger logger = new ThrottlingLogger(baseLogger);
52+
public static final String DEFAULT_ENDPOINT = "http://localhost:9411/api/v2/spans";
4953

5054
static final String OTEL_DROPPED_ATTRIBUTES_COUNT = "otel.dropped_attributes_count";
5155
static final String OTEL_DROPPED_EVENTS_COUNT = "otel.dropped_events_count";
@@ -57,13 +61,20 @@ public final class ZipkinSpanExporter implements SpanExporter {
5761
static final String KEY_INSTRUMENTATION_LIBRARY_NAME = "otel.library.name";
5862
static final String KEY_INSTRUMENTATION_LIBRARY_VERSION = "otel.library.version";
5963

64+
private final ThrottlingLogger logger = new ThrottlingLogger(baseLogger);
65+
6066
private final BytesEncoder<Span> encoder;
6167
private final Sender sender;
68+
private final ExporterMetrics exporterMetrics;
6269
@Nullable private final InetAddress localAddress;
6370

64-
ZipkinSpanExporter(BytesEncoder<Span> encoder, Sender sender) {
71+
ZipkinSpanExporter(BytesEncoder<Span> encoder, Sender sender, MeterProvider meterProvider) {
6572
this.encoder = encoder;
6673
this.sender = sender;
74+
this.exporterMetrics =
75+
sender.encoding() == Encoding.JSON
76+
? ExporterMetrics.createHttpJson("zipkin", "span", meterProvider)
77+
: ExporterMetrics.createHttpProtobuf("zipkin", "span", meterProvider);
6778
localAddress = produceLocalIp();
6879
}
6980

@@ -83,12 +94,13 @@ static InetAddress produceLocalIp() {
8394
}
8495
}
8596
} catch (Exception e) {
86-
// don't crash the caller if there was a problem reading nics.
97+
// don't crash the caller if there was a problem reading nics
8798
baseLogger.log(Level.FINE, "error reading nics", e);
8899
}
89100
return null;
90101
}
91102

103+
// VisibleForTesting
92104
Span generateSpan(SpanData spanData) {
93105
Endpoint endpoint = getEndpoint(spanData);
94106

@@ -119,26 +131,26 @@ Span generateSpan(SpanData spanData) {
119131

120132
StatusData status = spanData.getStatus();
121133

122-
// include status code & error.
134+
// include status code & error
123135
if (status.getStatusCode() != StatusCode.UNSET) {
124136
spanBuilder.putTag(OTEL_STATUS_CODE, status.getStatusCode().toString());
125137

126-
// add the error tag, if it isn't already in the source span.
138+
// add the error tag, if it isn't already in the source span
127139
if (status.getStatusCode() == StatusCode.ERROR && spanAttributes.get(STATUS_ERROR) == null) {
128-
spanBuilder.putTag(STATUS_ERROR.getKey(), nullToEmpty(status.getDescription()));
140+
spanBuilder.putTag(STATUS_ERROR.getKey(), status.getDescription());
129141
}
130142
}
131143

132144
InstrumentationScopeInfo instrumentationScopeInfo = spanData.getInstrumentationScopeInfo();
133145

134146
if (!instrumentationScopeInfo.getName().isEmpty()) {
135147
spanBuilder.putTag(KEY_INSTRUMENTATION_SCOPE_NAME, instrumentationScopeInfo.getName());
136-
// Include instrumentation library name for backwards compatibility
148+
// include instrumentation library name for backwards compatibility
137149
spanBuilder.putTag(KEY_INSTRUMENTATION_LIBRARY_NAME, instrumentationScopeInfo.getName());
138150
}
139151
if (instrumentationScopeInfo.getVersion() != null) {
140152
spanBuilder.putTag(KEY_INSTRUMENTATION_SCOPE_VERSION, instrumentationScopeInfo.getVersion());
141-
// Include instrumentation library name for backwards compatibility
153+
// include instrumentation library name for backwards compatibility
142154
spanBuilder.putTag(
143155
KEY_INSTRUMENTATION_LIBRARY_VERSION, instrumentationScopeInfo.getVersion());
144156
}
@@ -154,21 +166,17 @@ Span generateSpan(SpanData spanData) {
154166
return spanBuilder.build();
155167
}
156168

157-
private static String nullToEmpty(String value) {
158-
return value != null ? value : "";
159-
}
160-
161169
private Endpoint getEndpoint(SpanData spanData) {
162170
Attributes resourceAttributes = spanData.getResource().getAttributes();
163171

164172
Endpoint.Builder endpoint = Endpoint.newBuilder().ip(localAddress);
165173

166-
// use the service.name from the Resource, if it's been set.
174+
// use the service.name from the Resource, if it's been set
167175
String serviceNameValue = resourceAttributes.get(ResourceAttributes.SERVICE_NAME);
168176
if (serviceNameValue == null) {
169177
serviceNameValue = Resource.getDefault().getAttribute(ResourceAttributes.SERVICE_NAME);
170178
}
171-
// In practice should never be null unless the default Resource spec is changed.
179+
// in practice should never be null unless the default Resource spec is changed
172180
if (serviceNameValue != null) {
173181
endpoint.serviceName(serviceNameValue);
174182
}
@@ -226,7 +234,10 @@ private static String commaSeparated(List<?> values) {
226234

227235
@Override
228236
public CompletableResultCode export(Collection<SpanData> spanDataList) {
229-
List<byte[]> encodedSpans = new ArrayList<>(spanDataList.size());
237+
int numItems = spanDataList.size();
238+
exporterMetrics.addSeen(numItems);
239+
240+
List<byte[]> encodedSpans = new ArrayList<>(numItems);
230241
for (SpanData spanData : spanDataList) {
231242
encodedSpans.add(encoder.encode(generateSpan(spanData)));
232243
}
@@ -238,11 +249,13 @@ public CompletableResultCode export(Collection<SpanData> spanDataList) {
238249
new Callback<Void>() {
239250
@Override
240251
public void onSuccess(Void value) {
252+
exporterMetrics.addSuccess(numItems);
241253
result.succeed();
242254
}
243255

244256
@Override
245257
public void onError(Throwable t) {
258+
exporterMetrics.addFailed(numItems);
246259
logger.log(Level.WARNING, "Failed to export spans", t);
247260
result.fail();
248261
}

exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporterBuilder.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import static io.opentelemetry.api.internal.Utils.checkArgument;
99
import static java.util.Objects.requireNonNull;
1010

11+
import io.opentelemetry.api.metrics.MeterProvider;
1112
import java.time.Duration;
1213
import java.util.concurrent.TimeUnit;
1314
import javax.annotation.Nullable;
@@ -23,6 +24,7 @@ public final class ZipkinSpanExporterBuilder {
2324
@Nullable private Sender sender;
2425
private String endpoint = ZipkinSpanExporter.DEFAULT_ENDPOINT;
2526
private long readTimeoutMillis = TimeUnit.SECONDS.toMillis(10);
27+
private MeterProvider meterProvider = MeterProvider.noop();
2628

2729
/**
2830
* Sets the Zipkin sender. Implements the client side of the span transport. A {@link
@@ -92,6 +94,16 @@ public ZipkinSpanExporterBuilder setReadTimeout(Duration timeout) {
9294
return this;
9395
}
9496

97+
/**
98+
* Sets the {@link MeterProvider} to use to collect metrics related to export. If not set, metrics
99+
* will not be collected.
100+
*/
101+
public ZipkinSpanExporterBuilder setMeterProvider(MeterProvider meterProvider) {
102+
requireNonNull(meterProvider, "meterProvider");
103+
this.meterProvider = meterProvider;
104+
return this;
105+
}
106+
95107
/**
96108
* Builds a {@link ZipkinSpanExporter}.
97109
*
@@ -103,6 +115,6 @@ public ZipkinSpanExporter build() {
103115
sender =
104116
OkHttpSender.newBuilder().endpoint(endpoint).readTimeout((int) readTimeoutMillis).build();
105117
}
106-
return new ZipkinSpanExporter(encoder, sender);
118+
return new ZipkinSpanExporter(encoder, sender, meterProvider);
107119
}
108120
}

0 commit comments

Comments
 (0)