44using System . Diagnostics ;
55using System . Reflection ;
66using AWS . Distro . OpenTelemetry . AutoInstrumentation . Logging ;
7- using Google . Protobuf ;
87using Microsoft . Extensions . Logging ;
9- using Newtonsoft . Json ;
108using OpenTelemetry ;
11- using OpenTelemetry . Proto . Collector . Trace . V1 ;
12- using OpenTelemetry . Proto . Trace . V1 ;
139using OpenTelemetry . Resources ;
14- using OtlpResource = OpenTelemetry . Proto . Resource . V1 ;
1510
1611namespace AWS . Distro . OpenTelemetry . Exporter . Xray . Udp ;
1712
@@ -20,152 +15,69 @@ public class OtlpExporterUtils
2015 private static readonly ILoggerFactory Factory = LoggerFactory . Create ( builder => builder . AddProvider ( new ConsoleLoggerProvider ( ) ) ) ;
2116 private static readonly ILogger Logger = Factory . CreateLogger < OtlpExporterUtils > ( ) ;
2217
23- // The SerializeSpans function builds a ExportTraceServiceRequest object by calling private "ToOtlpSpan" function
24- // using reflection. "ToOtlpSpan" converts an Activity object into an OpenTelemetry.Proto.Trace.V1.Span object.
25- // With the conversion above, the Activity object is converted to an Otel span object to be exported using the
26- // UDP exporter. The "ToOtlpSpan" function can be found here:
27- // https://github.com/open-telemetry/opentelemetry-dotnet/blob/main/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ActivityExtensions.cs#L136
28- public static byte [ ] ? SerializeSpans ( Batch < Activity > batch , Resource processResource )
29- {
30- Type ? activityExtensionsType = Type . GetType ( "OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ActivityExtensions, OpenTelemetry.Exporter.OpenTelemetryProtocol" ) ;
18+ private static readonly MethodInfo ? WriteTraceDataMethod ;
19+ private static readonly object ? SdkLimitOptions ;
3120
21+ static OtlpExporterUtils ( ) {
22+ Type ? otlpSerializerType = Type . GetType ( "OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Serializer.ProtobufOtlpTraceSerializer, OpenTelemetry.Exporter.OpenTelemetryProtocol" ) ;
3223 Type ? sdkLimitOptionsType = Type . GetType ( "OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.SdkLimitOptions, OpenTelemetry.Exporter.OpenTelemetryProtocol" ) ;
3324
3425 if ( sdkLimitOptionsType == null )
3526 {
3627 Logger . LogTrace ( "SdkLimitOptions Type was not found" ) ;
37- return null ;
28+ return ;
3829 }
3930
40- MethodInfo ? toOtlpSpanMethod = activityExtensionsType ? . GetMethod (
41- "ToOtlpSpan" ,
42- BindingFlags . Static | BindingFlags . NonPublic ,
43- null ,
44- new [ ] { typeof ( Activity ) , sdkLimitOptionsType } ,
45- null ) ;
46-
47- var request = new ExportTraceServiceRequest ( ) ;
48- var sdkLimitOptions = OtlpExporterUtils . GetSdkLimitOptions ( ) ;
49-
50- if ( sdkLimitOptions == null )
31+ if ( otlpSerializerType == null )
5132 {
52- Logger . LogTrace ( "SdkLimitOptions Object was not found/created properly using the default parameterless constructor " ) ;
53- return null ;
33+ Logger . LogTrace ( "OtlpSerializer Type was not found" ) ;
34+ return ;
5435 }
5536
56- var otlpResource = OtlpExporterUtils . ToOtlpResource ( processResource ) ;
57-
58- // Create a ResourceSpans instance to hold the span and the otlpResource
59- ResourceSpans resourceSpans = new ResourceSpans
60- {
61- Resource = otlpResource ,
62- } ;
63- var scopeSpans = new ScopeSpans ( ) ;
64-
65- if ( toOtlpSpanMethod != null )
66- {
67- foreach ( var activity in batch )
37+ WriteTraceDataMethod = otlpSerializerType . GetMethod (
38+ "WriteTraceData" ,
39+ BindingFlags . NonPublic | BindingFlags . Static ,
40+ null ,
41+ new [ ]
6842 {
69- var otlpSpan = toOtlpSpanMethod . Invoke ( null , new object [ ] { activity , sdkLimitOptions } ) ;
70-
71- // The converters below are required since the the JsonConvert.DeserializeObject doesn't
72- // know how to deserialize a BytesString or SpanKinds from otlp proto json object.
73- var settings = new Newtonsoft . Json . JsonSerializerSettings ( ) ;
74- settings . Converters . Add ( new ByteStringConverter ( ) ) ;
75- settings . Converters . Add ( new SpanKindConverter ( ) ) ;
76- settings . Converters . Add ( new StatusCodeConverter ( ) ) ;
77-
78- // Below is a workaround to casting and works by converting an object into JSON then converting the
79- // JSON string back into the required object type. The reason casting isn't working is because of different
80- // assemblies being used. To use the protobuf library, we need to have a local copy of the protobuf assembly.
81- // Since upstream also has their own copy of the protobuf library, casting is not possible since the complier
82- // is recognizing them as two different types.
83- try
84- {
85- var otlpSpanJson = otlpSpan ? . ToString ( ) ;
86- if ( otlpSpanJson == null )
87- {
88- continue ;
89- }
90-
91- var otlpSpanConverted = JsonConvert . DeserializeObject < Span > ( otlpSpanJson , settings ) ;
92- scopeSpans . Spans . Add ( otlpSpanConverted ) ;
93- }
94- catch ( Exception ex )
95- {
96- Logger . LogError ( $ "Error converting OtlpSpan to/from JSON: { ex . Message } ") ;
97- }
98- }
99-
100- resourceSpans . ScopeSpans . Add ( scopeSpans ) ;
101- request . ResourceSpans . Add ( resourceSpans ) ;
102- }
103- else
104- {
105- Logger . LogTrace ( "ActivityExtensions.ToOtlpSpan method is not found" ) ;
106- }
107-
108- return request . ToByteArray ( ) ;
43+ typeof ( byte [ ] ) . MakeByRefType ( ) , // ref byte[] buffer
44+ typeof ( int ) , // int writePosition
45+ sdkLimitOptionsType , // SdkLimitOptions
46+ typeof ( Resource ) , // Resource?
47+ typeof ( Batch < Activity > ) . MakeByRefType ( ) // in Batch<Activity>
48+ } ,
49+ null )
50+ ?? throw new MissingMethodException ( "WriteTraceData not found" ) ; // :contentReference[oaicite:1]{index=1}
51+
52+ SdkLimitOptions = GetSdkLimitOptions ( ) ;
10953 }
11054
111- // Function that uses reflection to call ResourceExtensions.ToOtlpResource function.
112- // This functions converts from an OpenTelemetry.Resources.Resource to
113- // OpenTelemetry.Proto.Resource.V1.Resource (protobuf resource to be exported)
114- private static OtlpResource . Resource ? ToOtlpResource ( Resource processResource )
55+ // The WriteTraceData function builds writes data to the buffer byte[] object by calling private "WriteTraceData" function
56+ // using reflection. "WriteTraceData" is based on the latest v1.11.2 version of the OpenTelemetry.Exporter.OpenTelemetryProtocol
57+ // depedency specifically found at https://github.com/open-telemetry/opentelemetry-dotnet/blob/main/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpTraceSerializer.cs#L23
58+ // and used the by the OTLP Exporters.
59+ public static int WriteTraceData (
60+ ref byte [ ] buffer ,
61+ int writePosition ,
62+ Resource ? resource ,
63+ in Batch < Activity > batch )
11564 {
116- Type ? resourceExtensionsType = Type . GetType ( "OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ResourceExtensions, OpenTelemetry.Exporter.OpenTelemetryProtocol" ) ;
117-
118- if ( resourceExtensionsType == null )
119- {
120- Logger . LogTrace ( "ResourceExtensions Type was not found" ) ;
121- return null ;
122- }
123-
124- MethodInfo ? toOtlpResourceMethod = resourceExtensionsType . GetMethod (
125- "ToOtlpResource" ,
126- BindingFlags . Static | BindingFlags . Public ,
127- null ,
128- new [ ] { typeof ( Resource ) } ,
129- null ) ;
130-
131- if ( toOtlpResourceMethod == null )
65+ if ( SdkLimitOptions == null )
13266 {
133- Logger . LogTrace ( "ResourceExtensions.ToOtlpResource Method was not found" ) ;
134- return null ;
67+ Logger . LogTrace ( "SdkLimitOptions Object was not found/created properly using the default parameterless constructor " ) ;
68+ return - 1 ;
13569 }
70+
71+ // Pack arguments (ref/in remain by-ref in the args array)
72+ object [ ] args = { buffer , writePosition , SdkLimitOptions , resource ! , batch ! } ;
13673
137- var otlpResource = toOtlpResourceMethod . Invoke ( null , new object [ ] { processResource } ) ;
138-
139- if ( otlpResource == null )
140- {
141- Logger . LogTrace ( "OtlpResource object cannot be converted from OpenTelemetry.Resources" ) ;
142- return null ;
143- }
74+ // Invoke static method (null target) :contentReference[oaicite:2]{index=2}
75+ var result = ( int ) WriteTraceDataMethod ? . Invoke ( obj : null , parameters : args ) ! ;
14476
145- // Below is a workaround to casting and works by converting an object into JSON then converting the
146- // JSON string back into the required object type. The reason casting isn't working is because of different
147- // assemblies being used. To use the protobuf library, we need to have a local copy of the protobuf assembly.
148- // Since upstream also has their own copy of the protobuf library, casting is not possible since the complier
149- // is recognizing them as two different types.
150- try
151- {
152- // ToString method from OpenTelemetry.Proto.Resource.V1.Resource already converts the object into
153- // Json using the proper converters.
154- string ? otlpResourceJson = otlpResource . ToString ( ) ;
155- if ( otlpResourceJson == null )
156- {
157- Logger . LogTrace ( "OtlpResource object cannot be converted to JSON" ) ;
158- return null ;
159- }
77+ // Unpack ref-buffer
78+ buffer = ( byte [ ] ) args [ 0 ] ;
16079
161- var otlpResourceConverted = JsonConvert . DeserializeObject < OtlpResource . Resource > ( otlpResourceJson ) ;
162- return otlpResourceConverted ;
163- }
164- catch ( Exception ex )
165- {
166- Logger . LogError ( $ "Error converting OtlpResource to/from JSON: { ex . Message } ") ;
167- return null ;
168- }
80+ return result ;
16981 }
17082
17183 // Uses reflection to the get the SdkLimitOptions required to invoke the ToOtlpSpan function used in the
@@ -185,84 +97,4 @@ public class OtlpExporterUtils
18597 object ? sdkLimitOptionsInstance = Activator . CreateInstance ( sdkLimitOptionsType ) ;
18698 return sdkLimitOptionsInstance ;
18799 }
188- }
189-
190- internal class ByteStringConverter : JsonConverter < ByteString >
191- {
192- /// <inheritdoc/>
193- public override ByteString ? ReadJson ( JsonReader reader , Type objectType , ByteString ? existingValue , bool hasExistingValue , JsonSerializer serializer )
194- {
195- var base64String = ( string ? ) reader . Value ;
196- return ByteString . FromBase64 ( base64String ) ;
197- }
198-
199- /// <inheritdoc/>
200- public override void WriteJson ( JsonWriter writer , ByteString ? value , JsonSerializer serializer )
201- {
202- writer . WriteValue ( value ? . ToBase64 ( ) ) ;
203- }
204- }
205-
206- internal class SpanKindConverter : JsonConverter < Span . Types . SpanKind >
207- {
208- /// <inheritdoc/>
209- public override Span . Types . SpanKind ReadJson ( JsonReader reader , Type objectType , Span . Types . SpanKind existingValue , bool hasExistingValue , JsonSerializer serializer )
210- {
211- // Handle the string to enum conversion
212- string ? enumString = reader . Value ? . ToString ( ) ;
213-
214- // Convert the string representation to the corresponding enum value
215- switch ( enumString )
216- {
217- case "SPAN_KIND_CLIENT" :
218- return Span . Types . SpanKind . Client ;
219- case "SPAN_KIND_SERVER" :
220- return Span . Types . SpanKind . Server ;
221- case "SPAN_KIND_INTERNAL" :
222- return Span . Types . SpanKind . Internal ;
223- case "SPAN_KIND_PRODUCER" :
224- return Span . Types . SpanKind . Producer ;
225- case "SPAN_KIND_CONSUMER" :
226- return Span . Types . SpanKind . Consumer ;
227- default :
228- throw new JsonSerializationException ( $ "Unknown SpanKind: { enumString } ") ;
229- }
230- }
231-
232- /// <inheritdoc/>
233- public override void WriteJson ( JsonWriter writer , Span . Types . SpanKind value , JsonSerializer serializer )
234- {
235- // Write the string representation of the enum
236- writer . WriteValue ( value . ToString ( ) ) ;
237- }
238- }
239-
240- internal class StatusCodeConverter : JsonConverter < Status . Types . StatusCode >
241- {
242- /// <inheritdoc/>
243- public override Status . Types . StatusCode ReadJson ( JsonReader reader , Type objectType , Status . Types . StatusCode existingValue , bool hasExistingValue , JsonSerializer serializer )
244- {
245- // Handle the string to enum conversion
246- string ? enumString = reader . Value ? . ToString ( ) ;
247-
248- // Convert the string representation to the corresponding enum value
249- switch ( enumString )
250- {
251- case "STATUS_CODE_UNSET" :
252- return Status . Types . StatusCode . Unset ;
253- case "STATUS_CODE_OK" :
254- return Status . Types . StatusCode . Ok ;
255- case "STATUS_CODE_ERROR" :
256- return Status . Types . StatusCode . Error ;
257- default :
258- throw new JsonSerializationException ( $ "Unknown StatusCode: { enumString } ") ;
259- }
260- }
261-
262- /// <inheritdoc/>
263- public override void WriteJson ( JsonWriter writer , Status . Types . StatusCode value , JsonSerializer serializer )
264- {
265- // Write the string representation of the enum
266- writer . WriteValue ( value . ToString ( ) ) ;
267- }
268- }
100+ }
0 commit comments