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,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