Skip to content

Commit d902a71

Browse files
committed
OTEL in pubsub.
fdfd
1 parent 8d252c4 commit d902a71

2 files changed

Lines changed: 135 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
@@ -58,6 +58,7 @@ 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
6162
implementation library.java.google_api_services_healthcare
6263
implementation library.java.google_api_services_pubsub
6364
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: 134 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,68 @@ 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+
Tracer tracer = null;
1137+
1138+
@ProcessElement
1139+
public void processElement(
1140+
ProcessContext c,
1141+
@Element PubsubMessage message,
1142+
OutputReceiver<PubsubMessage> output,
1143+
PipelineOptions po) {
1144+
Context context = extractSpanContext(message);
1145+
Span psSub =
1146+
Preconditions.checkArgumentNotNull(tracer)
1147+
.spanBuilder("PubSubIO.Read")
1148+
.setParent(context)
1149+
.startSpan();
1150+
try (Scope s = psSub.makeCurrent()) {
1151+
output.output(message);
1152+
} finally {
1153+
psSub.end();
1154+
}
1155+
}
1156+
}
1157+
10821158
/**
10831159
* Causes the source to return a PubsubMessage that includes Pubsub attributes, and uses the
10841160
* given parsing function to transform the PubsubMessage into an output type. A Coder for the
@@ -1205,6 +1281,14 @@ public T apply(PubsubMessage input) {
12051281
}
12061282
};
12071283
PCollection<T> read;
1284+
1285+
if (isEnableOpenTelemetryTracing()) {
1286+
preParse =
1287+
preParse.apply(
1288+
"Extract OpenTelemetry context from Header",
1289+
ParDo.of(new OpenTelemetryHeaderConsumer()));
1290+
}
1291+
12081292
if (getDeadLetterTopicProvider() == null
12091293
&& (getBadRecordRouter() instanceof ThrowingBadRecordRouter)) {
12101294
read = preParse.apply(MapElements.into(typeDescriptor).via(parseFnWrapped));
@@ -1377,6 +1461,8 @@ public abstract static class Write<T> extends PTransform<PCollection<T>, PDone>
13771461

13781462
abstract @Nullable String getPubsubRootUrl();
13791463

1464+
abstract boolean isEnableOpenTelemetryTracing();
1465+
13801466
abstract boolean getPublishWithOrderingKey();
13811467

13821468
abstract BadRecordRouter getBadRecordRouter();
@@ -1396,6 +1482,7 @@ static <T> Builder<T> newBuilder(
13961482
builder.setBadRecordErrorHandler(new DefaultErrorHandler<>());
13971483
builder.setPublishWithOrderingKey(false);
13981484
builder.setValidate(false);
1485+
builder.setEnableOpenTelemetryTracing(false);
13991486
return builder;
14001487
}
14011488

@@ -1420,6 +1507,8 @@ abstract Builder<T> setTopicFunction(
14201507

14211508
abstract Builder<T> setTimestampAttribute(String timestampAttribute);
14221509

1510+
abstract Builder<T> setEnableOpenTelemetryTracing(boolean enableOpenTelemetryTracing);
1511+
14231512
abstract Builder<T> setIdAttribute(String idAttribute);
14241513

14251514
abstract Builder<T> setFormatFn(
@@ -1439,6 +1528,42 @@ abstract Builder<T> setBadRecordErrorHandler(
14391528
abstract Write<T> build();
14401529
}
14411530

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

1642+
public Write<T> withEnableOpenTelemetryTracing() {
1643+
return toBuilder().setEnableOpenTelemetryTracing(true).build();
1644+
}
1645+
15171646
/**
15181647
* Writes to Pub/Sub with each record's ordering key. A subscription with message ordering
15191648
* enabled will receive messages published in the same region with the same ordering key in the
@@ -1620,6 +1749,11 @@ public PDone expand(PCollection<T> input) {
16201749
} else {
16211750
pubsubMessages.setCoder(PubsubMessageWithTopicCoder.of());
16221751
}
1752+
if (isEnableOpenTelemetryTracing()) {
1753+
pubsubMessages =
1754+
pubsubMessages.apply(
1755+
"Propagate OpenTelemetry Tracing", ParDo.of(new OpenTelemetryHeaderPropagator()));
1756+
}
16231757
switch (input.isBounded()) {
16241758
case BOUNDED:
16251759
pubsubMessages.apply(

0 commit comments

Comments
 (0)