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,66 @@ 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 ,
1141+ OutputReceiver <PubsubMessage > output ) {
1142+ Context context = extractSpanContext (message );
1143+ Span psSub =
1144+ Preconditions .checkArgumentNotNull (tracer )
1145+ .spanBuilder ("PubSubIO.Read" )
1146+ .setParent (context )
1147+ .startSpan ();
1148+ try (Scope s = psSub .makeCurrent ()) {
1149+ output .output (message );
1150+ } finally {
1151+ psSub .end ();
1152+ }
1153+ }
1154+ }
1155+
10821156 /**
10831157 * Causes the source to return a PubsubMessage that includes Pubsub attributes, and uses the
10841158 * given parsing function to transform the PubsubMessage into an output type. A Coder for the
@@ -1205,6 +1279,14 @@ public T apply(PubsubMessage input) {
12051279 }
12061280 };
12071281 PCollection <T > read ;
1282+
1283+ if (isEnableOpenTelemetryTracing ()) {
1284+ preParse =
1285+ preParse .apply (
1286+ "Extract OpenTelemetry context from Header" ,
1287+ ParDo .of (new OpenTelemetryHeaderConsumer ()));
1288+ }
1289+
12081290 if (getDeadLetterTopicProvider () == null
12091291 && (getBadRecordRouter () instanceof ThrowingBadRecordRouter )) {
12101292 read = preParse .apply (MapElements .into (typeDescriptor ).via (parseFnWrapped ));
@@ -1377,6 +1459,8 @@ public abstract static class Write<T> extends PTransform<PCollection<T>, PDone>
13771459
13781460 abstract @ Nullable String getPubsubRootUrl ();
13791461
1462+ abstract boolean isEnableOpenTelemetryTracing ();
1463+
13801464 abstract boolean getPublishWithOrderingKey ();
13811465
13821466 abstract BadRecordRouter getBadRecordRouter ();
@@ -1396,6 +1480,7 @@ static <T> Builder<T> newBuilder(
13961480 builder .setBadRecordErrorHandler (new DefaultErrorHandler <>());
13971481 builder .setPublishWithOrderingKey (false );
13981482 builder .setValidate (false );
1483+ builder .setEnableOpenTelemetryTracing (false );
13991484 return builder ;
14001485 }
14011486
@@ -1420,6 +1505,8 @@ abstract Builder<T> setTopicFunction(
14201505
14211506 abstract Builder <T > setTimestampAttribute (String timestampAttribute );
14221507
1508+ abstract Builder <T > setEnableOpenTelemetryTracing (boolean enableOpenTelemetryTracing );
1509+
14231510 abstract Builder <T > setIdAttribute (String idAttribute );
14241511
14251512 abstract Builder <T > setFormatFn (
@@ -1439,6 +1526,42 @@ abstract Builder<T> setBadRecordErrorHandler(
14391526 abstract Write <T > build ();
14401527 }
14411528
1529+ static class OpenTelemetryHeaderPropagator extends DoFn <PubsubMessage , PubsubMessage > {
1530+ void injectSpanContext (Map <String , String > attr ) {
1531+ TextMapSetter <Map <String , String >> inject =
1532+ new TextMapSetter <Map <String , String >>() {
1533+ @ Override
1534+ public void set (@ Nullable Map <String , String > attr , String key , String value ) {
1535+ if (attr != null ) {
1536+ attr .put ("googclient_" + key , value );
1537+ }
1538+ }
1539+ };
1540+ W3CTraceContextPropagator .getInstance ().inject (Context .current (), attr , inject );
1541+ }
1542+
1543+ @ ProcessElement
1544+ public void processElement (
1545+ ProcessContext c ,
1546+ @ Element PubsubMessage message ,
1547+ OutputReceiver <PubsubMessage > output ,
1548+ PipelineOptions po ) {
1549+ Map <String , String > attributeMap = message .getAttributeMap ();
1550+ Map <String , String > attr =
1551+ attributeMap == null ? new HashMap <>() : new HashMap <>(attributeMap );
1552+ injectSpanContext (attr );
1553+
1554+ // copy the message, multiple fields
1555+ PubsubMessage ps =
1556+ new PubsubMessage (
1557+ message .getPayload (), attr , message .getMessageId (), message .getOrderingKey ());
1558+
1559+ // topic is copied seperately, not via constructor
1560+ ps = ps .withTopic (message .getTopic ());
1561+ output .output (ps );
1562+ }
1563+ }
1564+
14421565 /**
14431566 * Publishes to the specified topic.
14441567 *
@@ -1514,6 +1637,10 @@ public Write<T> withMaxBatchBytesSize(int maxBatchBytesSize) {
15141637 return toBuilder ().setMaxBatchBytesSize (maxBatchBytesSize ).build ();
15151638 }
15161639
1640+ public Write <T > withEnableOpenTelemetryTracing () {
1641+ return toBuilder ().setEnableOpenTelemetryTracing (true ).build ();
1642+ }
1643+
15171644 /**
15181645 * Writes to Pub/Sub with each record's ordering key. A subscription with message ordering
15191646 * enabled will receive messages published in the same region with the same ordering key in the
@@ -1620,6 +1747,11 @@ public PDone expand(PCollection<T> input) {
16201747 } else {
16211748 pubsubMessages .setCoder (PubsubMessageWithTopicCoder .of ());
16221749 }
1750+ if (isEnableOpenTelemetryTracing ()) {
1751+ pubsubMessages =
1752+ pubsubMessages .apply (
1753+ "Propagate OpenTelemetry Tracing" , ParDo .of (new OpenTelemetryHeaderPropagator ()));
1754+ }
16231755 switch (input .isBounded ()) {
16241756 case BOUNDED :
16251757 pubsubMessages .apply (
0 commit comments