Skip to content

Commit b2d9daf

Browse files
committed
OTEL in pubsub
1 parent 8d252c4 commit b2d9daf

2 files changed

Lines changed: 133 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: 131 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,65 @@ 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, OutputReceiver<PubsubMessage> output) {
1141+
Context context = extractSpanContext(message);
1142+
Span psSub =
1143+
Preconditions.checkArgumentNotNull(tracer)
1144+
.spanBuilder("PubSubIO.Read")
1145+
.setParent(context)
1146+
.startSpan();
1147+
try (Scope s = psSub.makeCurrent()) {
1148+
output.output(message);
1149+
} finally {
1150+
psSub.end();
1151+
}
1152+
}
1153+
}
1154+
10821155
/**
10831156
* Causes the source to return a PubsubMessage that includes Pubsub attributes, and uses the
10841157
* given parsing function to transform the PubsubMessage into an output type. A Coder for the
@@ -1205,6 +1278,14 @@ public T apply(PubsubMessage input) {
12051278
}
12061279
};
12071280
PCollection<T> read;
1281+
1282+
if (isEnableOpenTelemetryTracing()) {
1283+
preParse =
1284+
preParse.apply(
1285+
"Extract OpenTelemetry context from Header",
1286+
ParDo.of(new OpenTelemetryHeaderConsumer()));
1287+
}
1288+
12081289
if (getDeadLetterTopicProvider() == null
12091290
&& (getBadRecordRouter() instanceof ThrowingBadRecordRouter)) {
12101291
read = preParse.apply(MapElements.into(typeDescriptor).via(parseFnWrapped));
@@ -1377,6 +1458,8 @@ public abstract static class Write<T> extends PTransform<PCollection<T>, PDone>
13771458

13781459
abstract @Nullable String getPubsubRootUrl();
13791460

1461+
abstract boolean isEnableOpenTelemetryTracing();
1462+
13801463
abstract boolean getPublishWithOrderingKey();
13811464

13821465
abstract BadRecordRouter getBadRecordRouter();
@@ -1396,6 +1479,7 @@ static <T> Builder<T> newBuilder(
13961479
builder.setBadRecordErrorHandler(new DefaultErrorHandler<>());
13971480
builder.setPublishWithOrderingKey(false);
13981481
builder.setValidate(false);
1482+
builder.setEnableOpenTelemetryTracing(false);
13991483
return builder;
14001484
}
14011485

@@ -1420,6 +1504,8 @@ abstract Builder<T> setTopicFunction(
14201504

14211505
abstract Builder<T> setTimestampAttribute(String timestampAttribute);
14221506

1507+
abstract Builder<T> setEnableOpenTelemetryTracing(boolean enableOpenTelemetryTracing);
1508+
14231509
abstract Builder<T> setIdAttribute(String idAttribute);
14241510

14251511
abstract Builder<T> setFormatFn(
@@ -1439,6 +1525,42 @@ abstract Builder<T> setBadRecordErrorHandler(
14391525
abstract Write<T> build();
14401526
}
14411527

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

1639+
public Write<T> withEnableOpenTelemetryTracing() {
1640+
return toBuilder().setEnableOpenTelemetryTracing(true).build();
1641+
}
1642+
15171643
/**
15181644
* Writes to Pub/Sub with each record's ordering key. A subscription with message ordering
15191645
* enabled will receive messages published in the same region with the same ordering key in the
@@ -1620,6 +1746,11 @@ public PDone expand(PCollection<T> input) {
16201746
} else {
16211747
pubsubMessages.setCoder(PubsubMessageWithTopicCoder.of());
16221748
}
1749+
if (isEnableOpenTelemetryTracing()) {
1750+
pubsubMessages =
1751+
pubsubMessages.apply(
1752+
"Propagate OpenTelemetry Tracing", ParDo.of(new OpenTelemetryHeaderPropagator()));
1753+
}
16231754
switch (input.isBounded()) {
16241755
case BOUNDED:
16251756
pubsubMessages.apply(

0 commit comments

Comments
 (0)