Skip to content

Commit 5ee6e43

Browse files
add meaningful http tags in tracing handler (#237)
* add meaningful tag information in tracing handler * add unit test * add eof extra line * clear tracers * log HTTP_STATUS_CODE only if it is not successful * add option to log http host
1 parent 3aab504 commit 5ee6e43

File tree

3 files changed

+193
-7
lines changed

3 files changed

+193
-7
lines changed

Src/zipkin4net/Src/Transport/Http/TracingHandler.cs

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Net.Http.Headers;
44
using System.Threading.Tasks;
55
using zipkin4net.Propagation;
6+
using zipkin4net.Tracers.Zipkin.Thrift;
67

78
namespace zipkin4net.Transport.Http
89
{
@@ -11,32 +12,41 @@ public class TracingHandler : DelegatingHandler
1112
private readonly IInjector<HttpHeaders> _injector;
1213
private readonly string _serviceName;
1314
private readonly Func<HttpRequestMessage, string> _getClientTraceRpc;
15+
private readonly bool _logHttpHost;
1416

1517
/// <summary>
1618
/// Create a Tracing Handler
1719
/// </summary>
1820
/// <param name="serviceName"></param>
1921
/// <param name="httpMessageHandler">if not set or null then it set an <see cref="HttpClientHandler"/> as inner handler</param>
2022
/// <param name="getClientTraceRpc"></param>
21-
public TracingHandler(string serviceName, HttpMessageHandler httpMessageHandler = null, Func<HttpRequestMessage, string> getClientTraceRpc = null)
22-
: this(Propagations.B3String.Injector<HttpHeaders>((carrier, key, value) => carrier.Add(key, value)), serviceName, httpMessageHandler ?? new HttpClientHandler(), getClientTraceRpc)
23+
/// <param name="logHttpHost"></param>
24+
public TracingHandler(string serviceName, HttpMessageHandler httpMessageHandler = null,
25+
Func<HttpRequestMessage, string> getClientTraceRpc = null, bool logHttpHost = false)
26+
: this(Propagations.B3String.Injector<HttpHeaders>((carrier, key, value) => carrier.Add(key, value)),
27+
serviceName, httpMessageHandler ?? new HttpClientHandler(), getClientTraceRpc, logHttpHost)
2328
{ }
2429

2530
/// <summary>
2631
/// Create a TracingHandler for injection purpose like HttpClientFactory in AspNetCore
2732
/// </summary>
2833
/// <param name="serviceName"></param>
2934
/// <param name="getClientTraceRpc"></param>
35+
/// <param name="logHttpHost"></param>
3036
/// <returns></returns>
31-
public static TracingHandler WithoutInnerHandler(string serviceName, Func<HttpRequestMessage, string> getClientTraceRpc = null)
32-
=> new TracingHandler(Propagations.B3String.Injector<HttpHeaders>((carrier, key, value) => carrier.Add(key, value)), serviceName, getClientTraceRpc);
37+
public static TracingHandler WithoutInnerHandler(string serviceName,
38+
Func<HttpRequestMessage, string> getClientTraceRpc = null, bool logHttpHost = false)
39+
=> new TracingHandler(Propagations.B3String.Injector<HttpHeaders>((carrier, key, value) => carrier.Add(key, value)),
40+
serviceName, getClientTraceRpc, logHttpHost);
3341

34-
private TracingHandler(IInjector<HttpHeaders> injector, string serviceName, HttpMessageHandler httpMessageHandler, Func<HttpRequestMessage, string> getClientTraceRpc = null)
42+
private TracingHandler(IInjector<HttpHeaders> injector, string serviceName, HttpMessageHandler httpMessageHandler,
43+
Func<HttpRequestMessage, string> getClientTraceRpc = null, bool logHttpHost = false)
3544
: base(httpMessageHandler)
3645
{
3746
_injector = injector;
3847
_serviceName = serviceName;
3948
_getClientTraceRpc = getClientTraceRpc ?? (request => request.Method.ToString());
49+
_logHttpHost = logHttpHost;
4050
}
4151

4252
/// <summary>
@@ -45,12 +55,16 @@ private TracingHandler(IInjector<HttpHeaders> injector, string serviceName, Http
4555
/// <param name="injector"></param>
4656
/// <param name="serviceName"></param>
4757
/// <param name="getClientTraceRpc"></param>
48-
private TracingHandler(IInjector<HttpHeaders> injector, string serviceName, Func<HttpRequestMessage, string> getClientTraceRpc = null)
58+
/// <param name="logHttpHost"></param>
59+
private TracingHandler(IInjector<HttpHeaders> injector, string serviceName,
60+
Func<HttpRequestMessage, string> getClientTraceRpc = null, bool logHttpHost = false)
4961
{
5062
_injector = injector;
5163
_serviceName = serviceName;
5264
_getClientTraceRpc = getClientTraceRpc ?? (request => request.Method.ToString());
65+
_logHttpHost = logHttpHost;
5366
}
67+
5468
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
5569
{
5670
using (var clientTrace = new ClientTrace(_serviceName, _getClientTraceRpc(request)))
@@ -59,7 +73,24 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
5973
{
6074
_injector.Inject(clientTrace.Trace.CurrentSpan, request.Headers);
6175
}
62-
return await clientTrace.TracedActionAsync(base.SendAsync(request, cancellationToken));
76+
77+
var result = await clientTrace.TracedActionAsync(base.SendAsync(request, cancellationToken));
78+
79+
if (clientTrace.Trace != null)
80+
{
81+
clientTrace.AddAnnotation(Annotations.Tag(zipkinCoreConstants.HTTP_PATH, result.RequestMessage.RequestUri.LocalPath));
82+
clientTrace.AddAnnotation(Annotations.Tag(zipkinCoreConstants.HTTP_METHOD, result.RequestMessage.Method.Method));
83+
if (_logHttpHost)
84+
{
85+
clientTrace.AddAnnotation(Annotations.Tag(zipkinCoreConstants.HTTP_HOST, result.RequestMessage.RequestUri.Host));
86+
}
87+
if (!result.IsSuccessStatusCode)
88+
{
89+
clientTrace.AddAnnotation(Annotations.Tag(zipkinCoreConstants.HTTP_STATUS_CODE, ((int)result.StatusCode).ToString()));
90+
}
91+
}
92+
93+
return result;
6394
}
6495
}
6596
}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
using Moq;
2+
using NUnit.Framework;
3+
using System;
4+
using System.Net;
5+
using System.Net.Http;
6+
using System.Text;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using zipkin4net.Annotation;
10+
using zipkin4net.Dispatcher;
11+
using zipkin4net.Logger;
12+
using zipkin4net.Tracers.Zipkin.Thrift;
13+
using zipkin4net.Transport.Http;
14+
15+
namespace zipkin4net.UTest.Transport.Http
16+
{
17+
[TestFixture]
18+
public class T_TracingHandler
19+
{
20+
private HttpClient httpClient;
21+
private Mock<IRecordDispatcher> dispatcher;
22+
23+
[SetUp]
24+
public void SetUp()
25+
{
26+
dispatcher = new Mock<IRecordDispatcher>();
27+
28+
TraceManager.ClearTracers();
29+
TraceManager.Stop();
30+
TraceManager.SamplingRate = 1.0f;
31+
TraceManager.Start(new VoidLogger(), dispatcher.Object);
32+
}
33+
34+
[Test]
35+
public async Task ShouldLogTagAnnotations()
36+
{
37+
// Arrange
38+
dispatcher
39+
.Setup(h => h.Dispatch(It.IsAny<Record>()))
40+
.Returns(true);
41+
42+
var returnStatusCode = HttpStatusCode.BadRequest;
43+
var tracingHandler = new TracingHandler("abc", null, null, logHttpHost: true)
44+
{
45+
InnerHandler = new TestHandler(returnStatusCode)
46+
};
47+
httpClient = new HttpClient(tracingHandler);
48+
49+
// Act
50+
Trace.Current = Trace.Create();
51+
var uri = new Uri("https://abc.com/");
52+
var method = HttpMethod.Get;
53+
await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, uri));
54+
55+
// Assert
56+
dispatcher
57+
.Verify(h =>
58+
h.Dispatch(It.Is<Record>(m =>
59+
m.Annotation is TagAnnotation
60+
&& ((TagAnnotation)m.Annotation).Key == zipkinCoreConstants.HTTP_HOST
61+
&& ((TagAnnotation)m.Annotation).Value.ToString() == uri.Host)));
62+
63+
dispatcher
64+
.Verify(h =>
65+
h.Dispatch(It.Is<Record>(m =>
66+
m.Annotation is TagAnnotation
67+
&& ((TagAnnotation)m.Annotation).Key == zipkinCoreConstants.HTTP_PATH
68+
&& ((TagAnnotation)m.Annotation).Value.ToString() == uri.LocalPath)));
69+
70+
dispatcher
71+
.Verify(h =>
72+
h.Dispatch(It.Is<Record>(m =>
73+
m.Annotation is TagAnnotation
74+
&& ((TagAnnotation)m.Annotation).Key == zipkinCoreConstants.HTTP_METHOD
75+
&& ((TagAnnotation)m.Annotation).Value.ToString() == method.Method)));
76+
77+
dispatcher
78+
.Verify(h =>
79+
h.Dispatch(It.Is<Record>(m =>
80+
m.Annotation is TagAnnotation
81+
&& ((TagAnnotation)m.Annotation).Key == zipkinCoreConstants.HTTP_STATUS_CODE
82+
&& ((TagAnnotation)m.Annotation).Value.ToString() == ((int)returnStatusCode).ToString())));
83+
}
84+
85+
[Test]
86+
public async Task ShouldLogHttpHost()
87+
{
88+
// Arrange
89+
var returnStatusCode = HttpStatusCode.BadRequest;
90+
var tracingHandler = new TracingHandler("abc", null, null, logHttpHost: false)
91+
{
92+
InnerHandler = new TestHandler(returnStatusCode)
93+
};
94+
httpClient = new HttpClient(tracingHandler);
95+
96+
dispatcher
97+
.Setup(h => h.Dispatch(It.Is<Record>(m =>
98+
m.Annotation is TagAnnotation
99+
&& ((TagAnnotation)m.Annotation).Key == zipkinCoreConstants.HTTP_HOST)))
100+
.Throws(new Exception("HTTP_HOST Shouldn't be logged."));
101+
102+
// Act
103+
Trace.Current = Trace.Create();
104+
var uri = new Uri("https://abc.com/");
105+
var method = HttpMethod.Get;
106+
await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, uri));
107+
}
108+
109+
[Test]
110+
public async Task ShouldNotLogStatusCodeOnHttpCodeSuccess()
111+
{
112+
// Arrange
113+
var tracingHandler = new TracingHandler("abc")
114+
{
115+
InnerHandler = new TestHandler(HttpStatusCode.OK)
116+
};
117+
httpClient = new HttpClient(tracingHandler);
118+
119+
120+
dispatcher
121+
.Setup(h => h.Dispatch(It.Is<Record>(m =>
122+
m.Annotation is TagAnnotation
123+
&& ((TagAnnotation)m.Annotation).Key == zipkinCoreConstants.HTTP_STATUS_CODE)))
124+
.Throws(new Exception("HTTP_STATUS_CODE Shouldn't be logged."));
125+
126+
// Act and assert
127+
Trace.Current = Trace.Create();
128+
var uri = new Uri("https://abc.com/");
129+
var method = HttpMethod.Get;
130+
await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, uri));
131+
}
132+
133+
private class TestHandler : DelegatingHandler
134+
{
135+
private HttpStatusCode _returnStatusCode;
136+
137+
public TestHandler(HttpStatusCode returnStatusCode)
138+
{
139+
_returnStatusCode = returnStatusCode;
140+
}
141+
142+
protected override Task<HttpResponseMessage> SendAsync(
143+
HttpRequestMessage request, CancellationToken cancellationToken)
144+
{
145+
return Task.FromResult(new HttpResponseMessage(_returnStatusCode)
146+
{
147+
Content = new StringContent("OK", Encoding.UTF8, "application/json"),
148+
RequestMessage = request
149+
});
150+
}
151+
}
152+
}
153+
}

Src/zipkin4net/Tests/Transport/T_ZipkinHttpTraceExtractor.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ internal class T_ZipkinHttpTraceExtractor
1818
public void Setup()
1919
{
2020
_mockLogger = new Mock<ILogger>();
21+
TraceManager.ClearTracers();
22+
TraceManager.Stop();
2123
TraceManager.Start(_mockLogger.Object);
2224
}
2325

0 commit comments

Comments
 (0)