Skip to content

Commit 5bc1928

Browse files
committed
OTEL in pubsub
1 parent 8d252c4 commit 5bc1928

2 files changed

Lines changed: 134 additions & 0 deletions

File tree

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ dependencies {
5858
implementation library.java.google_api_client
5959
implementation library.java.google_api_common
6060
implementation library.java.google_api_services_bigquery
61+
implementation library.java.opentelemetry_api
62+
implementation library.java.opentelemetry_context
6163
implementation library.java.google_api_services_healthcare
6264
implementation library.java.google_api_services_pubsub
6365
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: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,17 @@
2626
import com.google.protobuf.DynamicMessage;
2727
import com.google.protobuf.InvalidProtocolBufferException;
2828
import com.google.protobuf.Message;
29+
import io.opentelemetry.api.trace.Span;
30+
import io.opentelemetry.api.trace.Tracer;
31+
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
32+
import io.opentelemetry.context.Context;
33+
import io.opentelemetry.context.Scope;
34+
import io.opentelemetry.context.propagation.TextMapGetter;
35+
import io.opentelemetry.context.propagation.TextMapSetter;
2936
import java.io.IOException;
3037
import java.io.Serializable;
3138
import java.nio.charset.StandardCharsets;
39+
import java.util.HashMap;
3240
import java.util.List;
3341
import java.util.Map;
3442
import java.util.Objects;
@@ -50,6 +58,7 @@
5058
import org.apache.beam.sdk.io.gcp.pubsub.PubsubClient.TopicPath;
5159
import org.apache.beam.sdk.metrics.Lineage;
5260
import org.apache.beam.sdk.options.PipelineOptions;
61+
import org.apache.beam.sdk.options.SdkHarnessOptions;
5362
import org.apache.beam.sdk.options.ValueProvider;
5463
import org.apache.beam.sdk.options.ValueProvider.NestedValueProvider;
5564
import org.apache.beam.sdk.options.ValueProvider.StaticValueProvider;
@@ -854,6 +863,8 @@ public abstract static class Read<T> extends PTransform<PBegin, PCollection<T>>
854863

855864
abstract boolean getNeedsMessageId();
856865

866+
abstract boolean isEnableOpenTelemetryTracing();
867+
857868
abstract boolean getNeedsOrderingKey();
858869

859870
abstract BadRecordRouter getBadRecordRouter();
@@ -874,6 +885,7 @@ static <T> Builder<T> newBuilder(SerializableFunction<PubsubMessage, T> parseFn)
874885
builder.setBadRecordRouter(BadRecordRouter.THROWING_ROUTER);
875886
builder.setBadRecordErrorHandler(new DefaultErrorHandler<>());
876887
builder.setValidate(false);
888+
builder.setEnableOpenTelemetryTracing(false);
877889
return builder;
878890
}
879891

@@ -896,6 +908,8 @@ abstract static class Builder<T> {
896908

897909
abstract Builder<T> setIdAttribute(String idAttribute);
898910

911+
abstract Builder<T> setEnableOpenTelemetryTracing(boolean enableOpenTelemetryTracing);
912+
899913
abstract Builder<T> setCoder(Coder<T> coder);
900914

901915
abstract Builder<T> setParseFn(SerializableFunction<PubsubMessage, T> parseFn);
@@ -1079,6 +1093,66 @@ public Read<T> withIdAttribute(String idAttribute) {
10791093
return toBuilder().setIdAttribute(idAttribute).build();
10801094
}
10811095

1096+
public Read<T> withEnableOpenTelemetryTracing() {
1097+
return toBuilder().setEnableOpenTelemetryTracing(true).setNeedsAttributes(true).build();
1098+
}
1099+
1100+
static class OpenTelemetryHeaderConsumer extends DoFn<PubsubMessage, PubsubMessage> {
1101+
Context extractSpanContext(PubsubMessage message) {
1102+
TextMapGetter<PubsubMessage> extractMessageAttributes =
1103+
new TextMapGetter<PubsubMessage>() {
1104+
@Override
1105+
public String get(@Nullable PubsubMessage carrier, String key) {
1106+
if (carrier == null) {
1107+
return null;
1108+
}
1109+
return carrier.getAttribute("googclient_" + key);
1110+
}
1111+
1112+
@Override
1113+
public Iterable<String> keys(PubsubMessage carrier) {
1114+
Map<String, String> attributeMap = carrier.getAttributeMap();
1115+
if (attributeMap == null) {
1116+
return ImmutableList.of();
1117+
}
1118+
List<String> keys = new java.util.ArrayList<>();
1119+
for (String key : attributeMap.keySet()) {
1120+
if (key.startsWith("googclient_")) {
1121+
keys.add(key.substring("googclient_".length()));
1122+
}
1123+
}
1124+
return keys;
1125+
}
1126+
};
1127+
return W3CTraceContextPropagator.getInstance()
1128+
.extract(Context.current(), message, extractMessageAttributes);
1129+
}
1130+
1131+
@Setup
1132+
public void setup(PipelineOptions po) {
1133+
tracer = po.as(SdkHarnessOptions.class).getOpenTelemetry().getTracer("PubSubIO");
1134+
}
1135+
1136+
private transient Tracer tracer = null;
1137+
1138+
@ProcessElement
1139+
public void processElement(
1140+
@Element PubsubMessage message,
1141+
OutputReceiver<PubsubMessage> output) {
1142+
Context context = extractSpanContext(message);
1143+
Span psSub =
1144+
Preconditions.checkArgumentNotNull(tracer)
1145+
.spanBuilder("PubSubIO.Read")
1146+
.setParent(context)
1147+
.startSpan();
1148+
try (Scope s = psSub.makeCurrent()) {
1149+
output.output(message);
1150+
} finally {
1151+
psSub.end();
1152+
}
1153+
}
1154+
}
1155+
10821156
/**
10831157
* Causes the source to return a PubsubMessage that includes Pubsub attributes, and uses the
10841158
* given parsing function to transform the PubsubMessage into an output type. A Coder for the
@@ -1205,6 +1279,14 @@ public T apply(PubsubMessage input) {
12051279
}
12061280
};
12071281
PCollection<T> read;
1282+
1283+
if (isEnableOpenTelemetryTracing()) {
1284+
preParse =
1285+
preParse.apply(
1286+
"Extract OpenTelemetry context from Header",
1287+
ParDo.of(new OpenTelemetryHeaderConsumer()));
1288+
}
1289+
12081290
if (getDeadLetterTopicProvider() == null
12091291
&& (getBadRecordRouter() instanceof ThrowingBadRecordRouter)) {
12101292
read = preParse.apply(MapElements.into(typeDescriptor).via(parseFnWrapped));
@@ -1377,6 +1459,8 @@ public abstract static class Write<T> extends PTransform<PCollection<T>, PDone>
13771459

13781460
abstract @Nullable String getPubsubRootUrl();
13791461

1462+
abstract boolean isEnableOpenTelemetryTracing();
1463+
13801464
abstract boolean getPublishWithOrderingKey();
13811465

13821466
abstract BadRecordRouter getBadRecordRouter();
@@ -1396,6 +1480,7 @@ static <T> Builder<T> newBuilder(
13961480
builder.setBadRecordErrorHandler(new DefaultErrorHandler<>());
13971481
builder.setPublishWithOrderingKey(false);
13981482
builder.setValidate(false);
1483+
builder.setEnableOpenTelemetryTracing(false);
13991484
return builder;
14001485
}
14011486

@@ -1420,6 +1505,8 @@ abstract Builder<T> setTopicFunction(
14201505

14211506
abstract Builder<T> setTimestampAttribute(String timestampAttribute);
14221507

1508+
abstract Builder<T> setEnableOpenTelemetryTracing(boolean enableOpenTelemetryTracing);
1509+
14231510
abstract Builder<T> setIdAttribute(String idAttribute);
14241511

14251512
abstract Builder<T> setFormatFn(
@@ -1439,6 +1526,42 @@ abstract Builder<T> setBadRecordErrorHandler(
14391526
abstract Write<T> build();
14401527
}
14411528

1529+
static class OpenTelemetryHeaderPropagator extends DoFn<PubsubMessage, PubsubMessage> {
1530+
void injectSpanContext(Map<String, String> attr) {
1531+
TextMapSetter<Map<String, String>> inject =
1532+
new TextMapSetter<Map<String, String>>() {
1533+
@Override
1534+
public void set(@Nullable Map<String, String> attr, String key, String value) {
1535+
if (attr != null) {
1536+
attr.put("googclient_" + key, value);
1537+
}
1538+
}
1539+
};
1540+
W3CTraceContextPropagator.getInstance().inject(Context.current(), attr, inject);
1541+
}
1542+
1543+
@ProcessElement
1544+
public void processElement(
1545+
ProcessContext c,
1546+
@Element PubsubMessage message,
1547+
OutputReceiver<PubsubMessage> output,
1548+
PipelineOptions po) {
1549+
Map<String, String> attributeMap = message.getAttributeMap();
1550+
Map<String, String> attr =
1551+
attributeMap == null ? new HashMap<>() : new HashMap<>(attributeMap);
1552+
injectSpanContext(attr);
1553+
1554+
// copy the message, multiple fields
1555+
PubsubMessage ps =
1556+
new PubsubMessage(
1557+
message.getPayload(), attr, message.getMessageId(), message.getOrderingKey());
1558+
1559+
// topic is copied seperately, not via constructor
1560+
ps = ps.withTopic(message.getTopic());
1561+
output.output(ps);
1562+
}
1563+
}
1564+
14421565
/**
14431566
* Publishes to the specified topic.
14441567
*
@@ -1514,6 +1637,10 @@ public Write<T> withMaxBatchBytesSize(int maxBatchBytesSize) {
15141637
return toBuilder().setMaxBatchBytesSize(maxBatchBytesSize).build();
15151638
}
15161639

1640+
public Write<T> withEnableOpenTelemetryTracing() {
1641+
return toBuilder().setEnableOpenTelemetryTracing(true).build();
1642+
}
1643+
15171644
/**
15181645
* Writes to Pub/Sub with each record's ordering key. A subscription with message ordering
15191646
* enabled will receive messages published in the same region with the same ordering key in the
@@ -1620,6 +1747,11 @@ public PDone expand(PCollection<T> input) {
16201747
} else {
16211748
pubsubMessages.setCoder(PubsubMessageWithTopicCoder.of());
16221749
}
1750+
if (isEnableOpenTelemetryTracing()) {
1751+
pubsubMessages =
1752+
pubsubMessages.apply(
1753+
"Propagate OpenTelemetry Tracing", ParDo.of(new OpenTelemetryHeaderPropagator()));
1754+
}
16231755
switch (input.isBounded()) {
16241756
case BOUNDED:
16251757
pubsubMessages.apply(

0 commit comments

Comments
 (0)