Skip to content

Commit 94de1df

Browse files
committed
OTEL in pubsub
1 parent dec8d23 commit 94de1df

2 files changed

Lines changed: 130 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: 129 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;
@@ -880,6 +889,8 @@ public abstract static class Read<T> extends PTransform<PBegin, PCollection<T>>
880889

881890
abstract boolean getNeedsMessageId();
882891

892+
abstract boolean isEnableOpenTelemetryTracing();
893+
883894
abstract boolean getNeedsOrderingKey();
884895

885896
abstract BadRecordRouter getBadRecordRouter();
@@ -900,6 +911,7 @@ static <T> Builder<T> newBuilder(SerializableFunction<PubsubMessage, T> parseFn)
900911
builder.setBadRecordRouter(BadRecordRouter.THROWING_ROUTER);
901912
builder.setBadRecordErrorHandler(new DefaultErrorHandler<>());
902913
builder.setValidate(false);
914+
builder.setEnableOpenTelemetryTracing(false);
903915
return builder;
904916
}
905917

@@ -922,6 +934,8 @@ abstract static class Builder<T> {
922934

923935
abstract Builder<T> setIdAttribute(String idAttribute);
924936

937+
abstract Builder<T> setEnableOpenTelemetryTracing(boolean enableOpenTelemetryTracing);
938+
925939
abstract Builder<T> setCoder(Coder<T> coder);
926940

927941
abstract Builder<T> setParseFn(SerializableFunction<PubsubMessage, T> parseFn);
@@ -1105,6 +1119,65 @@ public Read<T> withIdAttribute(String idAttribute) {
11051119
return toBuilder().setIdAttribute(idAttribute).build();
11061120
}
11071121

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

14141493
abstract @Nullable String getPubsubRootUrl();
14151494

1495+
abstract boolean isEnableOpenTelemetryTracing();
1496+
14161497
abstract boolean getPublishWithOrderingKey();
14171498

14181499
abstract BadRecordRouter getBadRecordRouter();
@@ -1432,6 +1513,7 @@ static <T> Builder<T> newBuilder(
14321513
builder.setBadRecordErrorHandler(new DefaultErrorHandler<>());
14331514
builder.setPublishWithOrderingKey(false);
14341515
builder.setValidate(false);
1516+
builder.setEnableOpenTelemetryTracing(false);
14351517
return builder;
14361518
}
14371519

@@ -1456,6 +1538,8 @@ abstract Builder<T> setTopicFunction(
14561538

14571539
abstract Builder<T> setTimestampAttribute(String timestampAttribute);
14581540

1541+
abstract Builder<T> setEnableOpenTelemetryTracing(boolean enableOpenTelemetryTracing);
1542+
14591543
abstract Builder<T> setIdAttribute(String idAttribute);
14601544

14611545
abstract Builder<T> setFormatFn(
@@ -1475,6 +1559,42 @@ abstract Builder<T> setBadRecordErrorHandler(
14751559
abstract Write<T> build();
14761560
}
14771561

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

1673+
public Write<T> withEnableOpenTelemetryTracing() {
1674+
return toBuilder().setEnableOpenTelemetryTracing(true).build();
1675+
}
1676+
15531677
/**
15541678
* Writes to Pub/Sub with each record's ordering key. A subscription with message ordering
15551679
* enabled will receive messages published in the same region with the same ordering key in the
@@ -1657,6 +1781,11 @@ public PDone expand(PCollection<T> input) {
16571781
} else {
16581782
pubsubMessages.setCoder(PubsubMessageWithTopicCoder.of());
16591783
}
1784+
if (isEnableOpenTelemetryTracing()) {
1785+
pubsubMessages =
1786+
pubsubMessages.apply(
1787+
"Propagate OpenTelemetry Tracing", ParDo.of(new OpenTelemetryHeaderPropagator()));
1788+
}
16601789
switch (input.isBounded()) {
16611790
case BOUNDED:
16621791
pubsubMessages.apply(

0 commit comments

Comments
 (0)