Skip to content

Commit ee7ae45

Browse files
Merge pull request #50 from VoxelGroup/fix-apigateway-tracing
Fix apigateway tracing
2 parents 6a57111 + e8b6db6 commit ee7ae45

File tree

10 files changed

+187
-77
lines changed

10 files changed

+187
-77
lines changed

Voxel.MiddyNet.sln.DotSettings

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
2+
<s:Boolean x:Key="/Default/UserDictionary/Words/=Traceparent/@EntryIndexedValue">True</s:Boolean>
3+
<s:Boolean x:Key="/Default/UserDictionary/Words/=Tracestate/@EntryIndexedValue">True</s:Boolean>
24
<s:Boolean x:Key="/Default/UserDictionary/Words/=Voxel/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

docs/source/nuget/packages.rst

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ This is another package that is quite independent. It contains extension methods
2626
You can use this package inside a Lambda function or from any place, to send the ``TraceContext`` information in a way that MiddyNet will be able to read using a middleware.
2727

2828
Voxel.MiddyNet.Tracing.Http
29-
--------------------------
29+
---------------------------
3030
This is another package that is quite independent. It contains extension methods to add the information of the ``TraceContext`` object from ``Voxel.MiddyNet.Tracing.Core`` to an HttpRequestMessage via ``Headers``.
3131

3232
You can use this package inside a Lambda function or from any place, to send the ``TraceContext`` information in a way that MiddyNet will be able to read using a middleware.
@@ -95,6 +95,10 @@ This package contains a middleware that reads the ``TraceContext`` information f
9595

9696
The logs will have a property for ``traceparent``, another one for ``tracestate``, and another one for ``trace-id``.
9797

98+
In addition, the MiddyNetContext is enriched with a new entry in the AdditionalContext collection that contains a TraceContext object.
99+
100+
This TraceContext object provides a MutateParentId method that can be used to obtain a traceparent with the same Version, TraceId, and TraceFlags but with a new ParentId that can be used to call other systems, as `recommended by W3C. <https://www.w3.org/TR/trace-context/#mutating-the-traceparent-field>`_
101+
98102
Sample code
99103
^^^^^^^^^^^
100104
A typical use of the middleware for APIGateway will look like this::
@@ -108,17 +112,23 @@ A typical use of the middleware for APIGateway will look like this::
108112

109113
protected override async Task<APIGatewayProxyResponse> Handle(APIGatewayProxyRequest apiEvent, MiddyNetContext context)
110114
{
111-
context.Logger.Log(LogLevel.Info, "hello world");
115+
//This log is enriched with the tracing information received in the headers of the request
116+
context.Logger.Log(LogLevel.Info, "Function called.");
112117

113-
// Do stuff
118+
//If you need to call another system, you need to obtain a traceparent based on the original traceparent
119+
//received but with the ParentId changed
120+
var currentTraceContext = (TraceContext)context.AdditionalContext[ApiGatewayTracingMiddleware.TraceContextKey];
121+
var newTraceContext = TraceContext.MutateParentId(currentTraceContext);
114122

115-
var result = new APIGatewayProxyResponse
123+
//Now you can use this newTraceContext in your calls
124+
var traceparentForCallingAnotherSystem = newTraceContext.TraceParent;
125+
var tracestateForCallingAnotherSystem = newTraceContext.TraceState;
126+
127+
return Task.FromResult(new APIGatewayProxyResponse
116128
{
117129
StatusCode = 200,
118-
Body = "hello from test"
119-
};
120-
121-
return Task.FromResult(result);
130+
Body = "Ok"
131+
});
122132
}
123133
}
124134

@@ -133,9 +143,17 @@ and for APIGatewayHttpV2Api will look like this::
133143

134144
protected override Task<APIGatewayHttpApiV2ProxyResponse> Handle(APIGatewayHttpApiV2ProxyRequest proxyRequest, MiddyNetContext context)
135145
{
136-
context.Logger.Log(LogLevel.Info, "hello world");
146+
//This log is enriched with the tracing information received in the headers of the request
147+
context.Logger.Log(LogLevel.Info, "Function called.");
137148

138-
// Do stuff
149+
//If you need to call another system, you need to obtain a traceparent based on the original traceparent
150+
//received but with the ParentId changed
151+
var currentTraceContext = (TraceContext)context.AdditionalContext[ApiGatewayHttpApiV2TracingMiddleware.TraceContextKey];
152+
var newTraceContext = TraceContext.MutateParentId(currentTraceContext);
153+
154+
//Now you can use this newTraceContext in your calls
155+
var traceparentForCallingAnotherSystem = newTraceContext.TraceParent;
156+
var tracestateForCallingAnotherSystem = newTraceContext.TraceState;
139157

140158
return Task.FromResult(new APIGatewayHttpApiV2ProxyResponse
141159
{

samples/ApiGatewayHttpApiV2Tracing/Voxel.MiddyNet.HttpApiV2TracingSample/ApiGatewayHttpApiV2Tracing.cs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using Amazon.Lambda.APIGatewayEvents;
33
using Amazon.Lambda.Core;
44
using Voxel.MiddyNet.Tracing.ApiGatewayMiddleware;
5+
using Voxel.MiddyNet.Tracing.Core;
56

67
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
78
namespace Voxel.MiddyNet.HttpApiV2TracingSample
@@ -15,14 +16,18 @@ public ApiGatewayHttpApiV2Tracing()
1516

1617
protected override Task<APIGatewayHttpApiV2ProxyResponse> Handle(APIGatewayHttpApiV2ProxyRequest proxyRequest, MiddyNetContext context)
1718
{
18-
var originalTraceParentHeaderValue = string.Empty;
19-
if (proxyRequest.Headers.ContainsKey("traceparent"))
20-
{
21-
originalTraceParentHeaderValue = proxyRequest.Headers["traceparent"];
22-
}
19+
//This log is enriched with the tracing information received in the headers of the request
20+
context.Logger.Log(LogLevel.Info, "Function called.");
2321

24-
context.Logger.Log(LogLevel.Info, "Function called", new LogProperty("original-traceparent", originalTraceParentHeaderValue));
22+
//If you need to call another system, you need to obtain a traceparent based on the original traceparent
23+
//received but with the ParentId changed
24+
var currentTraceContext = (TraceContext)context.AdditionalContext[ApiGatewayHttpApiV2TracingMiddleware.TraceContextKey];
25+
var newTraceContext = TraceContext.MutateParentId(currentTraceContext);
2526

27+
//Now you can use this newTraceContext in your calls
28+
var traceparentForCallingAnotherSystem = newTraceContext.TraceParent;
29+
var tracestateForCallingAnotherSystem = newTraceContext.TraceState;
30+
2631
return Task.FromResult(new APIGatewayHttpApiV2ProxyResponse
2732
{
2833
StatusCode = 200,
Lines changed: 38 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,38 @@
1-
using System.Threading.Tasks;
2-
using Amazon.Lambda.APIGatewayEvents;
3-
using Amazon.Lambda.Core;
4-
using Voxel.MiddyNet.Tracing.ApiGatewayMiddleware;
5-
6-
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
7-
namespace Voxel.MiddyNet.ApiGatewayTracingSample
8-
{
9-
public class ApiGatewayTracing : MiddyNet<APIGatewayProxyRequest, APIGatewayProxyResponse>
10-
{
11-
public ApiGatewayTracing()
12-
{
13-
Use(new ApiGatewayTracingMiddleware());
14-
}
15-
16-
protected override Task<APIGatewayProxyResponse> Handle(APIGatewayProxyRequest proxyRequest, MiddyNetContext context)
17-
{
18-
var originalTraceParentHeaderValue = string.Empty;
19-
if (proxyRequest.Headers.ContainsKey("traceparent"))
20-
{
21-
originalTraceParentHeaderValue = proxyRequest.Headers["traceparent"];
22-
}
23-
24-
context.Logger.Log(LogLevel.Info, "Function called", new LogProperty("original-traceparent", originalTraceParentHeaderValue));
25-
26-
return Task.FromResult(new APIGatewayProxyResponse
27-
{
28-
StatusCode = 200,
29-
Body = "Ok"
30-
});
31-
}
32-
}
33-
}
1+
using System.Threading.Tasks;
2+
using Amazon.Lambda.APIGatewayEvents;
3+
using Amazon.Lambda.Core;
4+
using Voxel.MiddyNet.Tracing.ApiGatewayMiddleware;
5+
using Voxel.MiddyNet.Tracing.Core;
6+
7+
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
8+
namespace Voxel.MiddyNet.ApiGatewayTracingSample
9+
{
10+
public class ApiGatewayTracing : MiddyNet<APIGatewayProxyRequest, APIGatewayProxyResponse>
11+
{
12+
public ApiGatewayTracing()
13+
{
14+
Use(new ApiGatewayTracingMiddleware());
15+
}
16+
17+
protected override Task<APIGatewayProxyResponse> Handle(APIGatewayProxyRequest proxyRequest, MiddyNetContext context)
18+
{
19+
//This log is enriched with the tracing information received in the headers of the request
20+
context.Logger.Log(LogLevel.Info, "Function called.");
21+
22+
//If you need to call another system, you need to obtain a traceparent based on the original traceparent
23+
//received but with the ParentId changed
24+
var currentTraceContext = (TraceContext)context.AdditionalContext[ApiGatewayTracingMiddleware.TraceContextKey];
25+
var newTraceContext = TraceContext.MutateParentId(currentTraceContext);
26+
27+
//Now you can use this newTraceContext in your calls
28+
var traceparentForCallingAnotherSystem = newTraceContext.TraceParent;
29+
var tracestateForCallingAnotherSystem = newTraceContext.TraceState;
30+
31+
return Task.FromResult(new APIGatewayProxyResponse
32+
{
33+
StatusCode = 200,
34+
Body = "Ok"
35+
});
36+
}
37+
}
38+
}

src/Voxel.MiddyNet.Tracing.ApiGatewayMiddleware/ApiGatewayHttpApiV2TracingMiddleware.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using System;
2-
using System.Threading.Tasks;
1+
using System.Threading.Tasks;
32
using Amazon.Lambda.APIGatewayEvents;
43
using Voxel.MiddyNet.Tracing.Core;
54

@@ -11,6 +10,8 @@ public class ApiGatewayHttpApiV2TracingMiddleware : ILambdaMiddleware<APIGateway
1110
private const string TraceStateHeaderName = "tracestate";
1211
private const string TraceIdHeaderName = "trace-id";
1312

13+
public static string TraceContextKey = "TraceContext";
14+
1415
public Task Before(APIGatewayHttpApiV2ProxyRequest apiGatewayEvent, MiddyNetContext context)
1516
{
1617
var traceParentHeaderValue = string.Empty;
@@ -23,6 +24,8 @@ public Task Before(APIGatewayHttpApiV2ProxyRequest apiGatewayEvent, MiddyNetCont
2324

2425
var traceContext = TraceContext.Handle(traceParentHeaderValue, traceStateHeaderValue);
2526

27+
context.AdditionalContext.Add(TraceContextKey, traceContext);
28+
2629
context.Logger.EnrichWith(new LogProperty(TraceParentHeaderName, traceContext.TraceParent));
2730
context.Logger.EnrichWith(new LogProperty(TraceStateHeaderName, traceContext.TraceState));
2831
context.Logger.EnrichWith(new LogProperty(TraceIdHeaderName, traceContext.TraceId));

src/Voxel.MiddyNet.Tracing.ApiGatewayMiddleware/ApiGatewayTracingMiddleware.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using System;
2-
using System.Threading.Tasks;
1+
using System.Threading.Tasks;
32
using Amazon.Lambda.APIGatewayEvents;
43
using Voxel.MiddyNet.Tracing.Core;
54

@@ -11,6 +10,8 @@ public class ApiGatewayTracingMiddleware : ILambdaMiddleware<APIGatewayProxyRequ
1110
private const string TraceStateHeaderName = "tracestate";
1211
private const string TraceIdHeaderName = "trace-id";
1312

13+
public static string TraceContextKey = "TraceContext";
14+
1415
public Task Before(APIGatewayProxyRequest apiGatewayEvent, MiddyNetContext context)
1516
{
1617
var traceParentHeaderValue = string.Empty;
@@ -22,11 +23,13 @@ public Task Before(APIGatewayProxyRequest apiGatewayEvent, MiddyNetContext conte
2223
traceStateHeaderValue = apiGatewayEvent.Headers[TraceStateHeaderName];
2324

2425
var traceContext = TraceContext.Handle(traceParentHeaderValue, traceStateHeaderValue);
26+
27+
context.AdditionalContext.Add(TraceContextKey, traceContext);
2528

2629
context.Logger.EnrichWith(new LogProperty(TraceParentHeaderName, traceContext.TraceParent));
2730
context.Logger.EnrichWith(new LogProperty(TraceStateHeaderName, traceContext.TraceState));
2831
context.Logger.EnrichWith(new LogProperty(TraceIdHeaderName, traceContext.TraceId));
29-
32+
3033
return Task.CompletedTask;
3134
}
3235

src/Voxel.MiddyNet.Tracing.Core/TraceContext.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public static TraceContext Handle(string traceParent, string traceState)
5454
return Reset();
5555
}
5656

57-
return new TraceContext(version, traceId, RandomString(16), traceFlags, traceState);
57+
return new TraceContext(version, traceId, parentId, traceFlags, traceState);
5858
}
5959

6060
private static bool FlagsAreNotValid(string traceFlags)
@@ -92,5 +92,10 @@ private static string RandomString(int length)
9292
return new string(Enumerable.Repeat(chars, length)
9393
.Select(s => s[random.Next(s.Length)]).ToArray());
9494
}
95+
96+
public static TraceContext MutateParentId(TraceContext traceContext)
97+
{
98+
return new TraceContext(traceContext.version, traceContext.TraceId, RandomString(16), traceContext.traceFlags, traceContext.TraceState);
99+
}
95100
}
96101
}

test/Voxel.MiddyNet.Tracing.ApiGatewayMiddleware.Tests/ApiGatewayHttpApiV2TracingMiddlewareShould.cs

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,66 @@
22
using System.Threading.Tasks;
33
using Amazon.Lambda.APIGatewayEvents;
44
using Amazon.Lambda.Core;
5+
using FluentAssertions;
56
using NSubstitute;
7+
using Voxel.MiddyNet.Tracing.Core;
68
using Xunit;
79

810
namespace Voxel.MiddyNet.Tracing.ApiGatewayMiddleware.Tests
911
{
1012
public class ApiGatewayHttpApiV2TracingMiddlewareShould
11-
{
13+
{
14+
private const string TracestateHeaderName = "tracestate";
15+
private const string TraceparentHeaderName = "traceparent";
16+
private const string TraceIdHeaderName = "trace-id";
17+
private const string TraceparentHeaderValue = "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01";
18+
private const string TracestateHeaderValue = "congo=ucfJifl5GOE";
19+
private const string TraceIdHeaderValue = "0af7651916cd43dd8448eb211c80319c";
20+
private const string TraceContextKey = "TraceContext";
21+
1222
[Fact]
1323
public async Task EnrichLoggerWithTraceContext()
1424
{
1525
var logger = Substitute.For<IMiddyLogger>();
1626
var context = new MiddyNetContext(Substitute.For<ILambdaContext>(), _ => logger);
17-
1827
var middleware = new ApiGatewayHttpApiV2TracingMiddleware();
28+
var apiGatewayEvent = new APIGatewayHttpApiV2ProxyRequest()
29+
{
30+
Headers = new Dictionary<string, string>
31+
{
32+
{ TraceparentHeaderName, TraceparentHeaderValue },
33+
{ TracestateHeaderName, TracestateHeaderValue }
34+
}
35+
};
1936

20-
var apiGatewayHttpApiV2Event = new APIGatewayHttpApiV2ProxyRequest
37+
await middleware.Before(apiGatewayEvent, context);
38+
39+
logger.Received().EnrichWith(Arg.Is<LogProperty>(p => p.Key == TraceparentHeaderName && p.Value.ToString() == TraceparentHeaderValue));
40+
logger.Received().EnrichWith(Arg.Is<LogProperty>(p => p.Key == TracestateHeaderName && p.Value.ToString() == TracestateHeaderValue));
41+
logger.Received().EnrichWith(Arg.Is<LogProperty>(p => p.Key == TraceIdHeaderName && p.Value.ToString() == TraceIdHeaderValue));
42+
}
43+
44+
[Fact]
45+
public async Task EnrichContextWithTraceContext()
46+
{
47+
var context = new MiddyNetContext(Substitute.For<ILambdaContext>());
48+
var middleware = new ApiGatewayHttpApiV2TracingMiddleware();
49+
var apiGatewayEvent = new APIGatewayHttpApiV2ProxyRequest()
2150
{
2251
Headers = new Dictionary<string, string>
2352
{
24-
{ "traceparent", "traceparent header value" },
25-
{"tracestate", "tracestate header value" }
53+
{ TraceparentHeaderName, TraceparentHeaderValue },
54+
{ TracestateHeaderName, TracestateHeaderValue }
2655
}
2756
};
2857

29-
await middleware.Before(apiGatewayHttpApiV2Event, context);
30-
logger.Received().EnrichWith(Arg.Is<LogProperty>(p => p.Key == "traceparent"));
31-
logger.Received().EnrichWith(Arg.Is<LogProperty>(p => p.Key == "tracestate"));
32-
logger.Received().EnrichWith(Arg.Is<LogProperty>(p => p.Key == "trace-id"));
58+
await middleware.Before(apiGatewayEvent, context);
59+
60+
context.AdditionalContext.Should().ContainKey(TraceContextKey);
61+
var traceContext = context.AdditionalContext[TraceContextKey] as TraceContext;
62+
traceContext.TraceParent.Should().Be(TraceparentHeaderValue);
63+
traceContext.TraceState.Should().Be(TracestateHeaderValue);
64+
traceContext.TraceId.Should().Be(TraceIdHeaderValue);
3365
}
3466
}
3567
}

0 commit comments

Comments
 (0)