2626import com .google .protobuf .DynamicMessage ;
2727import com .google .protobuf .InvalidProtocolBufferException ;
2828import 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 ;
2936import java .io .IOException ;
3037import java .io .Serializable ;
3138import java .nio .charset .StandardCharsets ;
39+ import java .util .HashMap ;
3240import java .util .List ;
3341import java .util .Map ;
3442import java .util .Objects ;
5058import org .apache .beam .sdk .io .gcp .pubsub .PubsubClient .TopicPath ;
5159import org .apache .beam .sdk .metrics .Lineage ;
5260import org .apache .beam .sdk .options .PipelineOptions ;
61+ import org .apache .beam .sdk .options .SdkHarnessOptions ;
5362import org .apache .beam .sdk .options .ValueProvider ;
5463import org .apache .beam .sdk .options .ValueProvider .NestedValueProvider ;
5564import 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