Skip to content
This repository was archived by the owner on Dec 18, 2023. It is now read-only.

Commit da7cc06

Browse files
Zipkin exporter (#32)
* Zipkin exporter * fix timestamp * remove Dispose of http client
1 parent 4241320 commit da7cc06

22 files changed

Lines changed: 847 additions & 42 deletions

OpenCensus.sln

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{7CB2F02E
1919
build\stylecop.json = build\stylecop.json
2020
EndProjectSection
2121
EndProject
22+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenCensus.Exporter.Zipkin", "src\OpenCensus.Exporter.Zipkin\OpenCensus.Exporter.Zipkin.csproj", "{7EDAE7FA-B44E-42CA-80FA-7DF2FAA2C5DD}"
23+
EndProject
2224
Global
2325
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2426
Debug|Any CPU = Debug|Any CPU
@@ -33,6 +35,10 @@ Global
3335
{CC62B3C1-5875-4986-A7F6-C4B26E42B0A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
3436
{CC62B3C1-5875-4986-A7F6-C4B26E42B0A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
3537
{CC62B3C1-5875-4986-A7F6-C4B26E42B0A1}.Release|Any CPU.Build.0 = Release|Any CPU
38+
{7EDAE7FA-B44E-42CA-80FA-7DF2FAA2C5DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
39+
{7EDAE7FA-B44E-42CA-80FA-7DF2FAA2C5DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
40+
{7EDAE7FA-B44E-42CA-80FA-7DF2FAA2C5DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
41+
{7EDAE7FA-B44E-42CA-80FA-7DF2FAA2C5DD}.Release|Any CPU.Build.0 = Release|Any CPU
3642
EndGlobalSection
3743
GlobalSection(SolutionProperties) = preSolution
3844
HideSolutionNode = FALSE
Lines changed: 347 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
1+
// <copyright file="TraceExporterHandler.cs" company="OpenCensus Authors">
2+
// Copyright 2018, OpenCensus Authors
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of theLicense at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
// </copyright>
16+
17+
namespace OpenCensus.Exporter.Zipkin.Implementation
18+
{
19+
using System;
20+
using System.Collections.Generic;
21+
using System.Net;
22+
using System.Net.Http;
23+
using System.Net.Security;
24+
using System.Net.Sockets;
25+
using System.Text;
26+
using System.Threading.Tasks;
27+
using Newtonsoft.Json;
28+
using OpenCensus.Common;
29+
using OpenCensus.Trace;
30+
using OpenCensus.Trace.Export;
31+
32+
internal class TraceExporterHandler : IHandler
33+
{
34+
private const string StatusCode = "census.status_code";
35+
private const string StatusDescription = "census.status_description";
36+
private const long MillisPerSecond = 1000L;
37+
private const long NanosPerMillisecond = 1000 * 1000;
38+
private const long NanosPerSecond = NanosPerMillisecond * MillisPerSecond;
39+
private readonly ZipkinTraceExporterOptions options;
40+
private readonly ZipkinEndpoint localEndpoint;
41+
private readonly HttpClient httpClient;
42+
43+
public TraceExporterHandler(ZipkinTraceExporterOptions options, HttpClient client)
44+
{
45+
this.options = options;
46+
this.localEndpoint = this.GetLocalZipkinEndpoint();
47+
this.httpClient = client ?? new HttpClient();
48+
}
49+
50+
public void Export(IList<ISpanData> spanDataList)
51+
{
52+
List<ZipkinSpan> zipkinSpans = new List<ZipkinSpan>();
53+
54+
foreach (var data in spanDataList)
55+
{
56+
var zipkinSpan = this.GenerateSpan(data, this.localEndpoint);
57+
zipkinSpans.Add(zipkinSpan);
58+
}
59+
60+
this.SendSpansAsync(zipkinSpans);
61+
}
62+
63+
internal ZipkinSpan GenerateSpan(ISpanData spanData, ZipkinEndpoint localEndpoint)
64+
{
65+
ISpanContext context = spanData.Context;
66+
long startTimestamp = this.ToEpochMicroseconds(spanData.StartTimestamp);
67+
long endTimestamp = this.ToEpochMicroseconds(spanData.EndTimestamp);
68+
69+
ZipkinSpan.Builder spanBuilder =
70+
ZipkinSpan.NewBuilder()
71+
.TraceId(this.EncodeTraceId(context.TraceId))
72+
.Id(this.EncodeSpanId(context.SpanId))
73+
.Kind(this.ToSpanKind(spanData))
74+
.Name(spanData.Name)
75+
.Timestamp(this.ToEpochMicroseconds(spanData.StartTimestamp))
76+
.Duration(endTimestamp - startTimestamp)
77+
.LocalEndpoint(localEndpoint);
78+
79+
if (spanData.ParentSpanId != null && spanData.ParentSpanId.IsValid)
80+
{
81+
spanBuilder.ParentId(this.EncodeSpanId(spanData.ParentSpanId));
82+
}
83+
84+
foreach (var label in spanData.Attributes.AttributeMap)
85+
{
86+
spanBuilder.PutTag(label.Key, this.AttributeValueToString(label.Value));
87+
}
88+
89+
Status status = spanData.Status;
90+
91+
if (status != null)
92+
{
93+
spanBuilder.PutTag(StatusCode, status.CanonicalCode.ToString());
94+
95+
if (status.Description != null)
96+
{
97+
spanBuilder.PutTag(StatusDescription, status.Description);
98+
}
99+
}
100+
101+
foreach (var annotation in spanData.Annotations.Events)
102+
{
103+
spanBuilder.AddAnnotation(this.ToEpochMicroseconds(annotation.Timestamp), annotation.Event.Description);
104+
}
105+
106+
foreach (var networkEvent in spanData.MessageEvents.Events)
107+
{
108+
spanBuilder.AddAnnotation(this.ToEpochMicroseconds(networkEvent.Timestamp), networkEvent.Event.Type.ToString());
109+
}
110+
111+
return spanBuilder.Build();
112+
}
113+
114+
private long ToEpochMicroseconds(ITimestamp timestamp)
115+
{
116+
long nanos = (timestamp.Seconds * NanosPerSecond) + timestamp.Nanos;
117+
long micros = nanos / 1000L;
118+
return micros;
119+
}
120+
121+
private string AttributeValueToString(IAttributeValue attributeValue)
122+
{
123+
return attributeValue.Match(
124+
(arg) => { return arg; },
125+
(arg) => { return arg.ToString(); },
126+
(arg) => { return arg.ToString(); },
127+
(arg) => { return null; });
128+
}
129+
130+
private string EncodeTraceId(ITraceId traceId)
131+
{
132+
var id = traceId.ToLowerBase16();
133+
134+
if (id.Length > 16 && this.options.UseShortTraceIds)
135+
{
136+
id = id.Substring(id.Length - 16, 16);
137+
}
138+
139+
return id;
140+
}
141+
142+
private string EncodeSpanId(ISpanId spanId)
143+
{
144+
return spanId.ToLowerBase16();
145+
}
146+
147+
private ZipkinSpanKind ToSpanKind(ISpanData spanData)
148+
{
149+
foreach (var label in spanData.Attributes.AttributeMap)
150+
{
151+
if (label.Key.Equals(SpanAttributeConstants.SpanKindKey))
152+
{
153+
if (this.IsClientSpanKind(label.Value))
154+
{
155+
return ZipkinSpanKind.CLIENT;
156+
}
157+
158+
if (this.IsServerSpanKind(label.Value))
159+
{
160+
return ZipkinSpanKind.SERVER;
161+
}
162+
}
163+
}
164+
165+
if (spanData.HasRemoteParent.HasValue && spanData.HasRemoteParent.Value)
166+
{
167+
return ZipkinSpanKind.SERVER;
168+
}
169+
170+
return ZipkinSpanKind.CLIENT;
171+
}
172+
173+
private bool IsClientSpanKind(IAttributeValue value)
174+
{
175+
return value.Match(
176+
(arg) =>
177+
{
178+
return arg.Equals(SpanAttributeConstants.ClientSpanKind);
179+
},
180+
(arg) =>
181+
{
182+
return false;
183+
},
184+
(arg) =>
185+
{
186+
return false;
187+
},
188+
(arg) =>
189+
{
190+
return false;
191+
});
192+
}
193+
194+
private bool IsServerSpanKind(IAttributeValue value)
195+
{
196+
return value.Match(
197+
(arg) =>
198+
{
199+
return arg.Equals(SpanAttributeConstants.ServerSpanKind);
200+
},
201+
(arg) =>
202+
{
203+
return false;
204+
},
205+
(arg) =>
206+
{
207+
return false;
208+
},
209+
(arg) =>
210+
{
211+
return false;
212+
});
213+
}
214+
215+
private async void SendSpansAsync(List<ZipkinSpan> spans)
216+
{
217+
try
218+
{
219+
var requestUri = new Uri(this.options.Endpoint);
220+
var request = this.GetHttpRequestMessage(HttpMethod.Post, requestUri);
221+
request.Content = this.GetRequestContent(spans);
222+
await this.DoPost(this.httpClient, request);
223+
}
224+
catch (Exception)
225+
{
226+
}
227+
}
228+
229+
private async Task DoPost(HttpClient client, HttpRequestMessage request)
230+
{
231+
try
232+
{
233+
using (HttpResponseMessage response = await client.SendAsync(request))
234+
{
235+
if (response.StatusCode != HttpStatusCode.OK &&
236+
response.StatusCode != HttpStatusCode.Accepted)
237+
{
238+
var statusCode = (int)response.StatusCode;
239+
}
240+
241+
return;
242+
}
243+
}
244+
catch (Exception)
245+
{
246+
}
247+
}
248+
249+
private HttpRequestMessage GetHttpRequestMessage(HttpMethod method, Uri requestUri)
250+
{
251+
var request = new HttpRequestMessage(method, requestUri);
252+
253+
request.Headers.Add("Accept", "application/json");
254+
255+
return request;
256+
}
257+
258+
private HttpContent GetRequestContent(IList<ZipkinSpan> toSerialize)
259+
{
260+
try
261+
{
262+
string json = JsonConvert.SerializeObject(toSerialize);
263+
264+
return new StringContent(json, Encoding.UTF8, "application/json");
265+
}
266+
catch (Exception)
267+
{
268+
}
269+
270+
return new StringContent(string.Empty, Encoding.UTF8, "application/json");
271+
}
272+
273+
private ZipkinEndpoint GetLocalZipkinEndpoint()
274+
{
275+
var result = new ZipkinEndpoint()
276+
{
277+
ServiceName = this.options.ServiceName
278+
};
279+
280+
string hostName = this.ResolveHostName();
281+
282+
if (!string.IsNullOrEmpty(hostName))
283+
{
284+
result.Ipv4 = this.ResolveHostAddress(hostName, AddressFamily.InterNetwork);
285+
286+
result.Ipv6 = this.ResolveHostAddress(hostName, AddressFamily.InterNetworkV6);
287+
}
288+
289+
return result;
290+
}
291+
292+
private string ResolveHostAddress(string hostName, AddressFamily family)
293+
{
294+
string result = null;
295+
296+
try
297+
{
298+
var results = Dns.GetHostAddresses(hostName);
299+
300+
if (results != null && results.Length > 0)
301+
{
302+
foreach (var addr in results)
303+
{
304+
if (addr.AddressFamily.Equals(family))
305+
{
306+
result = addr.ToString();
307+
308+
break;
309+
}
310+
}
311+
}
312+
}
313+
catch (Exception)
314+
{
315+
// Ignore
316+
}
317+
318+
return result;
319+
}
320+
321+
private string ResolveHostName()
322+
{
323+
string result = null;
324+
325+
try
326+
{
327+
result = Dns.GetHostName();
328+
329+
if (!string.IsNullOrEmpty(result))
330+
{
331+
var response = Dns.GetHostEntry(result);
332+
333+
if (response != null)
334+
{
335+
return response.HostName;
336+
}
337+
}
338+
}
339+
catch (Exception)
340+
{
341+
// Ignore
342+
}
343+
344+
return result;
345+
}
346+
}
347+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// <copyright file="ZipkinAnnotation.cs" company="OpenCensus Authors">
2+
// Copyright 2018, OpenCensus Authors
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of theLicense at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
// </copyright>
16+
17+
namespace OpenCensus.Exporter.Zipkin.Implementation
18+
{
19+
using Newtonsoft.Json;
20+
21+
internal class ZipkinAnnotation
22+
{
23+
[JsonProperty("timestamp")]
24+
public long Timestamp { get; set; }
25+
26+
[JsonProperty("value")]
27+
public string Value { get; set; }
28+
}
29+
}

0 commit comments

Comments
 (0)