1
+ use std:: collections:: BTreeMap ;
1
2
use std:: str:: FromStr ;
2
3
3
4
use chrono:: { TimeZone , Utc } ;
4
5
use opentelemetry_proto:: tonic:: common:: v1:: any_value:: Value as OtelValue ;
6
+ use opentelemetry_proto:: tonic:: trace:: v1:: span:: Link as OtelLink ;
5
7
6
8
use crate :: otel_trace:: {
7
9
status:: StatusCode as OtelStatusCode , Span as OtelSpan , SpanFlags as OtelSpanFlags ,
8
10
} ;
9
11
use crate :: status_codes;
10
12
use relay_event_schema:: protocol:: {
11
- EventId , Span as EventSpan , SpanData , SpanId , SpanStatus , Timestamp , TraceId ,
13
+ EventId , Span as EventSpan , SpanData , SpanId , SpanLink , SpanStatus , Timestamp , TraceId ,
12
14
} ;
13
- use relay_protocol:: { Annotated , FromValue , Object } ;
15
+ use relay_protocol:: { Annotated , FromValue , Object , Value } ;
14
16
15
17
/// convert_from_otel_to_sentry_status returns a status as defined by Sentry based on the OTel status.
16
18
fn convert_from_otel_to_sentry_status (
@@ -96,6 +98,7 @@ pub fn otel_to_sentry_span(otel_span: OtelSpan) -> EventSpan {
96
98
kind,
97
99
attributes,
98
100
status,
101
+ links,
99
102
..
100
103
} = otel_span;
101
104
@@ -165,26 +168,8 @@ pub fn otel_to_sentry_span(otel_span: OtelSpan) -> EventSpan {
165
168
}
166
169
_ => {
167
170
let key = attribute. key ;
168
- match value {
169
- OtelValue :: ArrayValue ( _) => { }
170
- OtelValue :: BoolValue ( v) => {
171
- data. insert ( key, Annotated :: new ( v. into ( ) ) ) ;
172
- }
173
- OtelValue :: BytesValue ( v) => {
174
- if let Ok ( v) = String :: from_utf8 ( v) {
175
- data. insert ( key, Annotated :: new ( v. into ( ) ) ) ;
176
- }
177
- }
178
- OtelValue :: DoubleValue ( v) => {
179
- data. insert ( key, Annotated :: new ( v. into ( ) ) ) ;
180
- }
181
- OtelValue :: IntValue ( v) => {
182
- data. insert ( key, Annotated :: new ( v. into ( ) ) ) ;
183
- }
184
- OtelValue :: KvlistValue ( _) => { }
185
- OtelValue :: StringValue ( v) => {
186
- data. insert ( key, Annotated :: new ( v. into ( ) ) ) ;
187
- }
171
+ if let Some ( v) = otel_to_sentry_value ( value) {
172
+ data. insert ( key, Annotated :: new ( v) ) ;
188
173
}
189
174
}
190
175
}
@@ -199,6 +184,11 @@ pub fn otel_to_sentry_span(otel_span: OtelSpan) -> EventSpan {
199
184
description = description. or ( Some ( format ! ( "{http_method} {http_route}" ) ) ) ;
200
185
}
201
186
187
+ let sentry_links: Vec < Annotated < SpanLink > > = links
188
+ . into_iter ( )
189
+ . map ( |link| otel_to_sentry_link ( link) . into ( ) )
190
+ . collect ( ) ;
191
+
202
192
EventSpan {
203
193
op : op. into ( ) ,
204
194
description : description. into ( ) ,
@@ -221,10 +211,49 @@ pub fn otel_to_sentry_span(otel_span: OtelSpan) -> EventSpan {
221
211
timestamp : Timestamp ( end_timestamp) . into ( ) ,
222
212
trace_id : TraceId ( trace_id) . into ( ) ,
223
213
platform : platform. into ( ) ,
214
+ links : sentry_links. into ( ) ,
224
215
..Default :: default ( )
225
216
}
226
217
}
227
218
219
+ fn otel_to_sentry_link ( otel_link : OtelLink ) -> SpanLink {
220
+ // See the W3C trace context specification:
221
+ // <https://www.w3.org/TR/trace-context-2/#sampled-flag>
222
+ const W3C_TRACE_CONTEXT_SAMPLED : u32 = 1 << 0 ;
223
+
224
+ let attributes = BTreeMap :: from_iter ( otel_link. attributes . into_iter ( ) . flat_map ( |kv| {
225
+ kv. value
226
+ . and_then ( |v| v. value )
227
+ . and_then ( otel_to_sentry_value)
228
+ . map ( |v| ( kv. key , v. into ( ) ) )
229
+ } ) )
230
+ . into ( ) ;
231
+
232
+ SpanLink {
233
+ trace_id : TraceId ( hex:: encode ( otel_link. trace_id ) ) . into ( ) ,
234
+ span_id : SpanId ( hex:: encode ( otel_link. span_id ) ) . into ( ) ,
235
+ sampled : ( otel_link. flags & W3C_TRACE_CONTEXT_SAMPLED != 0 ) . into ( ) ,
236
+ attributes,
237
+ // The parent span ID is not available over OTLP.
238
+ parent_span_id : Annotated :: empty ( ) ,
239
+ other : Default :: default ( ) ,
240
+ }
241
+ }
242
+
243
+ fn otel_to_sentry_value ( value : OtelValue ) -> Option < Value > {
244
+ match value {
245
+ OtelValue :: BoolValue ( v) => Some ( Value :: Bool ( v) ) ,
246
+ OtelValue :: DoubleValue ( v) => Some ( Value :: F64 ( v) ) ,
247
+ OtelValue :: IntValue ( v) => Some ( Value :: I64 ( v) ) ,
248
+ OtelValue :: StringValue ( v) => Some ( Value :: String ( v) ) ,
249
+ OtelValue :: BytesValue ( v) => {
250
+ String :: from_utf8 ( v) . map_or ( None , |str| Some ( Value :: String ( str) ) )
251
+ }
252
+ OtelValue :: ArrayValue ( _) => None ,
253
+ OtelValue :: KvlistValue ( _) => None ,
254
+ }
255
+ }
256
+
228
257
#[ cfg( test) ]
229
258
mod tests {
230
259
use super :: * ;
@@ -707,7 +736,7 @@ mod tests {
707
736
),
708
737
},
709
738
},
710
- links: ~ ,
739
+ links: [] ,
711
740
sentry_tags: ~,
712
741
received: ~,
713
742
measurements: ~,
@@ -756,4 +785,76 @@ mod tests {
756
785
Some ( "fa90fdead5f74052" )
757
786
) ;
758
787
}
788
+
789
+ #[ test]
790
+ fn parse_link ( ) {
791
+ let json = r#"{
792
+ "links": [
793
+ {
794
+ "traceId": "4c79f60c11214eb38604f4ae0781bfb2",
795
+ "spanId": "fa90fdead5f74052",
796
+ "attributes": [
797
+ {
798
+ "key": "str_key",
799
+ "value": {
800
+ "stringValue": "str_value"
801
+ }
802
+ },
803
+ {
804
+ "key": "bool_key",
805
+ "value": {
806
+ "boolValue": true
807
+ }
808
+ },
809
+ {
810
+ "key": "int_key",
811
+ "value": {
812
+ "intValue": "123"
813
+ }
814
+ },
815
+ {
816
+ "key": "double_key",
817
+ "value": {
818
+ "doubleValue": 1.23
819
+ }
820
+ }
821
+ ],
822
+ "flags": 1
823
+ }
824
+ ]
825
+ }"# ;
826
+ let otel_span: OtelSpan = serde_json:: from_str ( json) . unwrap ( ) ;
827
+ let event_span: EventSpan = otel_to_sentry_span ( otel_span) ;
828
+ let annotated_span: Annotated < EventSpan > = Annotated :: new ( event_span) ;
829
+ assert_eq ! (
830
+ get_path!( annotated_span. links[ 0 ] . trace_id) ,
831
+ Some ( & Annotated :: new( TraceId (
832
+ "4c79f60c11214eb38604f4ae0781bfb2" . into( )
833
+ ) ) )
834
+ ) ;
835
+ assert_eq ! (
836
+ get_path!( annotated_span. links[ 0 ] . span_id) ,
837
+ Some ( & Annotated :: new( SpanId ( "fa90fdead5f74052" . into( ) ) ) )
838
+ ) ;
839
+ assert_eq ! (
840
+ get_path!( annotated_span. links[ 0 ] . attributes[ "str_key" ] ) ,
841
+ Some ( & Annotated :: new( Value :: String ( "str_value" . into( ) ) ) )
842
+ ) ;
843
+ assert_eq ! (
844
+ get_path!( annotated_span. links[ 0 ] . attributes[ "bool_key" ] ) ,
845
+ Some ( & Annotated :: new( Value :: Bool ( true ) ) )
846
+ ) ;
847
+ assert_eq ! (
848
+ get_path!( annotated_span. links[ 0 ] . attributes[ "int_key" ] ) ,
849
+ Some ( & Annotated :: new( Value :: I64 ( 123 ) ) )
850
+ ) ;
851
+ assert_eq ! (
852
+ get_path!( annotated_span. links[ 0 ] . attributes[ "double_key" ] ) ,
853
+ Some ( & Annotated :: new( Value :: F64 ( 1.23 ) ) )
854
+ ) ;
855
+ assert_eq ! (
856
+ get_path!( annotated_span. links[ 0 ] . sampled) ,
857
+ Some ( & Annotated :: new( true ) )
858
+ ) ;
859
+ }
759
860
}
0 commit comments