2828import com .google .protobuf .DynamicMessage ;
2929import com .google .protobuf .InvalidProtocolBufferException ;
3030import 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 ;
3138import java .io .IOException ;
3239import java .io .Serializable ;
3340import java .nio .charset .StandardCharsets ;
41+ import java .util .HashMap ;
3442import java .util .List ;
3543import java .util .Map ;
3644import java .util .Objects ;
5260import org .apache .beam .sdk .io .gcp .pubsub .PubsubClient .TopicPath ;
5361import org .apache .beam .sdk .metrics .Lineage ;
5462import org .apache .beam .sdk .options .PipelineOptions ;
63+ import org .apache .beam .sdk .options .SdkHarnessOptions ;
5564import org .apache .beam .sdk .options .ValueProvider ;
5665import org .apache .beam .sdk .options .ValueProvider .NestedValueProvider ;
5766import 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