Skip to content

Commit f258e3e

Browse files
authored
OTEL in pubsub (#39150)
1 parent 2434619 commit f258e3e

2 files changed

Lines changed: 131 additions & 0 deletions

File tree

sdks/java/io/google-cloud-platform/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ dependencies {
6565
implementation library.java.google_api_common
6666
implementation library.java.google_api_services_bigquery
6767
implementation library.java.opentelemetry_api
68+
implementation library.java.opentelemetry_context
6869
implementation library.java.google_api_services_healthcare
6970
implementation library.java.google_api_services_pubsub
7071
implementation library.java.google_api_services_storage

sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/pubsub/PubsubIO.java

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,17 @@
2828
import com.google.protobuf.DynamicMessage;
2929
import com.google.protobuf.InvalidProtocolBufferException;
3030
import com.google.protobuf.Message;
31+
import io.opentelemetry.api.trace.Span;
32+
import io.opentelemetry.api.trace.Tracer;
33+
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
34+
import io.opentelemetry.context.Context;
35+
import io.opentelemetry.context.Scope;
36+
import io.opentelemetry.context.propagation.TextMapGetter;
37+
import io.opentelemetry.context.propagation.TextMapSetter;
3138
import java.io.IOException;
3239
import java.io.Serializable;
3340
import java.nio.charset.StandardCharsets;
41+
import java.util.HashMap;
3442
import java.util.List;
3543
import java.util.Map;
3644
import java.util.Objects;
@@ -52,6 +60,7 @@
5260
import org.apache.beam.sdk.io.gcp.pubsub.PubsubClient.TopicPath;
5361
import org.apache.beam.sdk.metrics.Lineage;
5462
import org.apache.beam.sdk.options.PipelineOptions;
63+
import org.apache.beam.sdk.options.SdkHarnessOptions;
5564
import org.apache.beam.sdk.options.ValueProvider;
5665
import org.apache.beam.sdk.options.ValueProvider.NestedValueProvider;
5766
import org.apache.beam.sdk.options.ValueProvider.StaticValueProvider;
@@ -90,6 +99,7 @@
9099
import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.ImmutableMap;
91100
import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.Lists;
92101
import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.Maps;
102+
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
93103
import org.checkerframework.checker.nullness.qual.Nullable;
94104
import org.joda.time.Instant;
95105
import org.slf4j.Logger;
@@ -880,6 +890,8 @@ public abstract static class Read<T> extends PTransform<PBegin, PCollection<T>>
880890

881891
abstract boolean getNeedsMessageId();
882892

893+
abstract boolean isEnableOpenTelemetryTracing();
894+
883895
abstract boolean getNeedsOrderingKey();
884896

885897
abstract BadRecordRouter getBadRecordRouter();
@@ -900,6 +912,7 @@ static <T> Builder<T> newBuilder(SerializableFunction<PubsubMessage, T> parseFn)
900912
builder.setBadRecordRouter(BadRecordRouter.THROWING_ROUTER);
901913
builder.setBadRecordErrorHandler(new DefaultErrorHandler<>());
902914
builder.setValidate(false);
915+
builder.setEnableOpenTelemetryTracing(false);
903916
return builder;
904917
}
905918

@@ -922,6 +935,8 @@ abstract static class Builder<T> {
922935

923936
abstract Builder<T> setIdAttribute(String idAttribute);
924937

938+
abstract Builder<T> setEnableOpenTelemetryTracing(boolean enableOpenTelemetryTracing);
939+
925940
abstract Builder<T> setCoder(Coder<T> coder);
926941

927942
abstract Builder<T> setParseFn(SerializableFunction<PubsubMessage, T> parseFn);
@@ -1105,6 +1120,66 @@ public Read<T> withIdAttribute(String idAttribute) {
11051120
return toBuilder().setIdAttribute(idAttribute).build();
11061121
}
11071122

1123+
public Read<T> withEnableOpenTelemetryTracing() {
1124+
return toBuilder().setEnableOpenTelemetryTracing(true).setNeedsAttributes(true).build();
1125+
}
1126+
1127+
static class OpenTelemetryHeaderConsumer extends DoFn<PubsubMessage, PubsubMessage> {
1128+
1129+
Context extractSpanContext(PubsubMessage message) {
1130+
TextMapGetter<PubsubMessage> extractMessageAttributes =
1131+
new TextMapGetter<PubsubMessage>() {
1132+
@Override
1133+
public @Nullable String get(@Nullable PubsubMessage carrier, String key) {
1134+
if (carrier == null) {
1135+
return null;
1136+
}
1137+
return carrier.getAttribute("googclient_" + key);
1138+
}
1139+
1140+
@Override
1141+
public Iterable<String> keys(PubsubMessage carrier) {
1142+
Map<String, String> attributeMap = carrier.getAttributeMap();
1143+
if (attributeMap == null) {
1144+
return ImmutableList.of();
1145+
}
1146+
List<String> keys = new java.util.ArrayList<>();
1147+
for (String key : attributeMap.keySet()) {
1148+
if (key.startsWith("googclient_")) {
1149+
keys.add(key.substring("googclient_".length()));
1150+
}
1151+
}
1152+
return keys;
1153+
}
1154+
};
1155+
return W3CTraceContextPropagator.getInstance()
1156+
.extract(Context.current(), message, extractMessageAttributes);
1157+
}
1158+
1159+
@Setup
1160+
public void setup(PipelineOptions po) {
1161+
tracer = po.as(SdkHarnessOptions.class).getOpenTelemetry().getTracer("PubSubIO");
1162+
}
1163+
1164+
private transient @MonotonicNonNull Tracer tracer = null;
1165+
1166+
@ProcessElement
1167+
public void processElement(
1168+
@Element PubsubMessage message, OutputReceiver<PubsubMessage> output) {
1169+
@Nullable Context context = extractSpanContext(message);
1170+
Span span =
1171+
checkArgumentNotNull(tracer)
1172+
.spanBuilder("PubSubIO.Read")
1173+
.setParent(context)
1174+
.startSpan();
1175+
try (Scope s = span.makeCurrent()) {
1176+
output.output(message);
1177+
} finally {
1178+
span.end();
1179+
}
1180+
}
1181+
}
1182+
11081183
/**
11091184
* Causes the source to return a PubsubMessage that includes Pubsub attributes, and uses the
11101185
* given parsing function to transform the PubsubMessage into an output type. A Coder for the
@@ -1234,6 +1309,12 @@ public T apply(PubsubMessage input) {
12341309
};
12351310
ValueProvider<PubsubTopic> deadLetterTopicProvider = getDeadLetterTopicProvider();
12361311
PCollection<T> read;
1312+
if (isEnableOpenTelemetryTracing()) {
1313+
preParse =
1314+
preParse.apply(
1315+
"Extract OpenTelemetry context from Header",
1316+
ParDo.of(new OpenTelemetryHeaderConsumer()));
1317+
}
12371318
if (deadLetterTopicProvider == null
12381319
&& (getBadRecordRouter() instanceof ThrowingBadRecordRouter)) {
12391320
read = preParse.apply(MapElements.into(typeDescriptor).via(parseFnWrapped));
@@ -1413,6 +1494,8 @@ public abstract static class Write<T> extends PTransform<PCollection<T>, PDone>
14131494

14141495
abstract @Nullable String getPubsubRootUrl();
14151496

1497+
abstract boolean isEnableOpenTelemetryTracing();
1498+
14161499
abstract boolean getPublishWithOrderingKey();
14171500

14181501
abstract BadRecordRouter getBadRecordRouter();
@@ -1432,6 +1515,7 @@ static <T> Builder<T> newBuilder(
14321515
builder.setBadRecordErrorHandler(new DefaultErrorHandler<>());
14331516
builder.setPublishWithOrderingKey(false);
14341517
builder.setValidate(false);
1518+
builder.setEnableOpenTelemetryTracing(false);
14351519
return builder;
14361520
}
14371521

@@ -1456,6 +1540,8 @@ abstract Builder<T> setTopicFunction(
14561540

14571541
abstract Builder<T> setTimestampAttribute(String timestampAttribute);
14581542

1543+
abstract Builder<T> setEnableOpenTelemetryTracing(boolean enableOpenTelemetryTracing);
1544+
14591545
abstract Builder<T> setIdAttribute(String idAttribute);
14601546

14611547
abstract Builder<T> setFormatFn(
@@ -1475,6 +1561,41 @@ abstract Builder<T> setBadRecordErrorHandler(
14751561
abstract Write<T> build();
14761562
}
14771563

1564+
static class OpenTelemetryHeaderPropagator extends DoFn<PubsubMessage, PubsubMessage> {
1565+
void injectSpanContext(Map<String, String> attr) {
1566+
TextMapSetter<Map<String, String>> inject =
1567+
new TextMapSetter<Map<String, String>>() {
1568+
@Override
1569+
public void set(@Nullable Map<String, String> attr, String key, String value) {
1570+
if (attr != null) {
1571+
attr.put("googclient_" + key, value);
1572+
}
1573+
}
1574+
};
1575+
W3CTraceContextPropagator.getInstance().inject(Context.current(), attr, inject);
1576+
}
1577+
1578+
@ProcessElement
1579+
public void processElement(
1580+
@Element PubsubMessage message, OutputReceiver<PubsubMessage> output) {
1581+
Map<String, String> attributeMap = message.getAttributeMap();
1582+
Map<String, String> attr =
1583+
attributeMap == null ? new HashMap<>() : new HashMap<>(attributeMap);
1584+
injectSpanContext(attr);
1585+
1586+
// copy the message, multiple fields
1587+
PubsubMessage ps =
1588+
new PubsubMessage(
1589+
message.getPayload(), attr, message.getMessageId(), message.getOrderingKey());
1590+
1591+
// topic is copied seperately, not via constructor
1592+
if (message.getTopic() != null) {
1593+
ps = ps.withTopic(message.getTopic());
1594+
}
1595+
output.output(ps);
1596+
}
1597+
}
1598+
14781599
/**
14791600
* Publishes to the specified topic.
14801601
*
@@ -1550,6 +1671,10 @@ public Write<T> withMaxBatchBytesSize(int maxBatchBytesSize) {
15501671
return toBuilder().setMaxBatchBytesSize(maxBatchBytesSize).build();
15511672
}
15521673

1674+
public Write<T> withEnableOpenTelemetryTracing() {
1675+
return toBuilder().setEnableOpenTelemetryTracing(true).build();
1676+
}
1677+
15531678
/**
15541679
* Writes to Pub/Sub with each record's ordering key. A subscription with message ordering
15551680
* enabled will receive messages published in the same region with the same ordering key in the
@@ -1657,6 +1782,11 @@ public PDone expand(PCollection<T> input) {
16571782
} else {
16581783
pubsubMessages.setCoder(PubsubMessageWithTopicCoder.of());
16591784
}
1785+
if (isEnableOpenTelemetryTracing()) {
1786+
pubsubMessages =
1787+
pubsubMessages.apply(
1788+
"Propagate OpenTelemetry Tracing", ParDo.of(new OpenTelemetryHeaderPropagator()));
1789+
}
16601790
switch (input.isBounded()) {
16611791
case BOUNDED:
16621792
pubsubMessages.apply(

0 commit comments

Comments
 (0)