@@ -241,6 +241,12 @@ struct OTelTracingClientInterceptorTests {
241241 bodyParts: RPCAsyncSequence (
242242 wrapping: AsyncThrowingStream < StreamingClientResponse . Contents . BodyPart , any Error > {
243243 $0. yield ( . message( [ " response " ] ) )
244+ $0. yield (
245+ . trailingMetadata( [
246+ " some-repeated-response-metadata " : " some-repeated-response-value1 " ,
247+ " some-repeated-response-metadata " : " some-repeated-response-value2 " ,
248+ ] )
249+ )
244250 $0. finish ( )
245251 }
246252 )
@@ -349,10 +355,12 @@ struct OTelTracingClientInterceptorTests {
349355 bodyParts: RPCAsyncSequence (
350356 wrapping: AsyncThrowingStream < StreamingClientResponse . Contents . BodyPart , any Error > {
351357 $0. yield ( . message( [ " response " ] ) )
352- $0. yield ( . trailingMetadata( [
353- " some-repeated-response-metadata " : " some-repeated-response-value1 " ,
354- " some-repeated-response-metadata " : " some-repeated-response-value2 "
355- ] ) )
358+ $0. yield (
359+ . trailingMetadata( [
360+ " some-repeated-response-metadata " : " some-repeated-response-value1 " ,
361+ " some-repeated-response-metadata " : " some-repeated-response-value2 " ,
362+ ] )
363+ )
356364 $0. finish ( )
357365 }
358366 )
@@ -845,6 +853,222 @@ struct OTelTracingServerInterceptorTests {
845853 }
846854 }
847855
856+ @Test ( " All string-valued request metadata is included if opted-in " )
857+ func testRequestMetadataOptIn( ) async throws {
858+ let methodDescriptor = MethodDescriptor (
859+ fullyQualifiedService: " OTelTracingServerInterceptorTests " ,
860+ method: " testRequestMetadataOptIn "
861+ )
862+ let interceptor = ServerOTelTracingInterceptor (
863+ serverHostname: " someserver.com " ,
864+ networkTransportMethod: " tcp " ,
865+ includeRequestMetadata: true
866+ )
867+ let request = ServerRequest (
868+ metadata: [
869+ " some-request-metadata " : " some-request-value " ,
870+ " some-repeated-request-metadata " : " some-repeated-request-value1 " ,
871+ " some-repeated-request-metadata " : " some-repeated-request-value2 " ,
872+ " some-request-metadata-bin " : . binary( [ 1 ] ) ,
873+ ] ,
874+ message: [ UInt8] ( )
875+ )
876+ let response = try await interceptor. intercept (
877+ tracer: self . tracer,
878+ request: . init( single: request) ,
879+ context: ServerContext (
880+ descriptor: methodDescriptor,
881+ remotePeer: " ipv4:10.1.2.80:567 " ,
882+ localPeer: " ipv4:10.1.2.90:123 " ,
883+ cancellation: . init( )
884+ )
885+ ) { request, _ in
886+ for try await _ in request. messages {
887+ // We need to iterate over the messages for the span to be able to record the events.
888+ }
889+
890+ return StreamingServerResponse < String > (
891+ accepted: . success(
892+ . init(
893+ metadata: [
894+ " some-response-metadata " : " some-response-value " ,
895+ " some-response-metadata-bin " : . binary( [ 2 ] ) ,
896+ ] ,
897+ producer: { writer in
898+ try await writer. write ( " response1 " )
899+ try await writer. write ( " response2 " )
900+ return [
901+ " some-repeated-response-metadata " : " some-repeated-response-value1 " ,
902+ " some-repeated-response-metadata " : " some-repeated-response-value2 " ,
903+ ]
904+ }
905+ )
906+ )
907+ )
908+ }
909+
910+ // Get the response out into a response stream, and assert its contents
911+ let ( responseStream, responseStreamContinuation) = AsyncStream< String> . makeStream( )
912+ let responseContents = try response. accepted. get ( )
913+ let trailingMetadata = try await responseContents. producer (
914+ RPCWriter ( wrapping: TestWriter ( streamContinuation: responseStreamContinuation) )
915+ )
916+ responseStreamContinuation. finish ( )
917+
918+ #expect(
919+ trailingMetadata == [
920+ " some-repeated-response-metadata " : " some-repeated-response-value1 " ,
921+ " some-repeated-response-metadata " : " some-repeated-response-value2 " ,
922+ ]
923+ )
924+ await assertStreamContentsEqual ( [ " response1 " , " response2 " ] , responseStream)
925+
926+ assertTestSpanComponents ( forMethod: methodDescriptor, tracer: self . tracer) { events in
927+ #expect(
928+ events == [
929+ // Recorded when request is received
930+ TestSpanEvent ( " rpc.message " , [ " rpc.message.type " : " RECEIVED " , " rpc.message.id " : 1 ] ) ,
931+ // Recorded when `response1` is sent
932+ TestSpanEvent ( " rpc.message " , [ " rpc.message.type " : " SENT " , " rpc.message.id " : 1 ] ) ,
933+ // Recorded when `response2` is sent
934+ TestSpanEvent ( " rpc.message " , [ " rpc.message.type " : " SENT " , " rpc.message.id " : 2 ] ) ,
935+ ]
936+ )
937+ } assertAttributes: { attributes in
938+ #expect(
939+ attributes == [
940+ " rpc.system " : " grpc " ,
941+ " rpc.method " : . string( methodDescriptor. method) ,
942+ " rpc.service " : . string( methodDescriptor. service. fullyQualifiedService) ,
943+ " server.address " : " someserver.com " ,
944+ " server.port " : 123 ,
945+ " network.peer.address " : " 10.1.2.90 " ,
946+ " network.peer.port " : 123 ,
947+ " network.transport " : " tcp " ,
948+ " network.type " : " ipv4 " ,
949+ " client.address " : " 10.1.2.80 " ,
950+ " client.port " : 567 ,
951+ " rpc.grpc.request.metadata.some-request-metadata " : " some-request-value " ,
952+ " rpc.grpc.request.metadata.some-repeated-request-metadata " : . stringArray( [
953+ " some-repeated-request-value1 " , " some-repeated-request-value2 " ,
954+ ] ) ,
955+ ]
956+ )
957+ } assertStatus: { status in
958+ #expect( status == nil )
959+ } assertErrors: { errors in
960+ #expect( errors. isEmpty)
961+ }
962+ }
963+
964+ @Test ( " All string-valued response metadata is included if opted-in " )
965+ func testResponseMetadataOptIn( ) async throws {
966+ let methodDescriptor = MethodDescriptor (
967+ fullyQualifiedService: " OTelTracingServerInterceptorTests " ,
968+ method: " testResponseMetadataOptIn "
969+ )
970+ let interceptor = ServerOTelTracingInterceptor (
971+ serverHostname: " someserver.com " ,
972+ networkTransportMethod: " tcp " ,
973+ includeResponseMetadata: true
974+ )
975+ let request = ServerRequest (
976+ metadata: [
977+ " some-request-metadata " : " some-request-value " ,
978+ " some-repeated-request-metadata " : " some-repeated-request-value1 " ,
979+ " some-repeated-request-metadata " : " some-repeated-request-value2 " ,
980+ " some-request-metadata-bin " : . binary( [ 1 ] ) ,
981+ ] ,
982+ message: [ UInt8] ( )
983+ )
984+ let response = try await interceptor. intercept (
985+ tracer: self . tracer,
986+ request: . init( single: request) ,
987+ context: ServerContext (
988+ descriptor: methodDescriptor,
989+ remotePeer: " ipv4:10.1.2.80:567 " ,
990+ localPeer: " ipv4:10.1.2.90:123 " ,
991+ cancellation: . init( )
992+ )
993+ ) { request, _ in
994+ for try await _ in request. messages {
995+ // We need to iterate over the messages for the span to be able to record the events.
996+ }
997+
998+ return StreamingServerResponse < String > (
999+ accepted: . success(
1000+ . init(
1001+ metadata: [
1002+ " some-response-metadata " : " some-response-value " ,
1003+ " some-response-metadata-bin " : . binary( [ 2 ] ) ,
1004+ ] ,
1005+ producer: { writer in
1006+ try await writer. write ( " response1 " )
1007+ try await writer. write ( " response2 " )
1008+ return [
1009+ " some-repeated-response-metadata " : " some-repeated-response-value1 " ,
1010+ " some-repeated-response-metadata " : " some-repeated-response-value2 " ,
1011+ ]
1012+ }
1013+ )
1014+ )
1015+ )
1016+ }
1017+
1018+ // Get the response out into a response stream, and assert its contents
1019+ let ( responseStream, responseStreamContinuation) = AsyncStream< String> . makeStream( )
1020+ let responseContents = try response. accepted. get ( )
1021+ let trailingMetadata = try await responseContents. producer (
1022+ RPCWriter ( wrapping: TestWriter ( streamContinuation: responseStreamContinuation) )
1023+ )
1024+ responseStreamContinuation. finish ( )
1025+
1026+ #expect(
1027+ trailingMetadata == [
1028+ " some-repeated-response-metadata " : " some-repeated-response-value1 " ,
1029+ " some-repeated-response-metadata " : " some-repeated-response-value2 " ,
1030+ ]
1031+ )
1032+ await assertStreamContentsEqual ( [ " response1 " , " response2 " ] , responseStream)
1033+
1034+ assertTestSpanComponents ( forMethod: methodDescriptor, tracer: self . tracer) { events in
1035+ #expect(
1036+ events == [
1037+ // Recorded when request is received
1038+ TestSpanEvent ( " rpc.message " , [ " rpc.message.type " : " RECEIVED " , " rpc.message.id " : 1 ] ) ,
1039+ // Recorded when `response1` is sent
1040+ TestSpanEvent ( " rpc.message " , [ " rpc.message.type " : " SENT " , " rpc.message.id " : 1 ] ) ,
1041+ // Recorded when `response2` is sent
1042+ TestSpanEvent ( " rpc.message " , [ " rpc.message.type " : " SENT " , " rpc.message.id " : 2 ] ) ,
1043+ ]
1044+ )
1045+ } assertAttributes: { attributes in
1046+ #expect(
1047+ attributes == [
1048+ " rpc.system " : " grpc " ,
1049+ " rpc.method " : . string( methodDescriptor. method) ,
1050+ " rpc.service " : . string( methodDescriptor. service. fullyQualifiedService) ,
1051+ " server.address " : " someserver.com " ,
1052+ " server.port " : 123 ,
1053+ " network.peer.address " : " 10.1.2.90 " ,
1054+ " network.peer.port " : 123 ,
1055+ " network.transport " : " tcp " ,
1056+ " network.type " : " ipv4 " ,
1057+ " client.address " : " 10.1.2.80 " ,
1058+ " client.port " : 567 ,
1059+ " rpc.grpc.response.metadata.some-response-metadata " : " some-response-value " ,
1060+ " rpc.grpc.response.metadata.some-repeated-response-metadata " : . stringArray( [
1061+ " some-repeated-response-value1 " , " some-repeated-response-value2 " ,
1062+ ] ) ,
1063+ ]
1064+ )
1065+ } assertStatus: { status in
1066+ #expect( status == nil )
1067+ } assertErrors: { errors in
1068+ #expect( errors. isEmpty)
1069+ }
1070+ }
1071+
8481072 @Test ( " RPC that throws is correctly recorded " )
8491073 func testThrowingRPC( ) async throws {
8501074 let methodDescriptor = MethodDescriptor (
0 commit comments