├── .gitignore
├── .nuget
├── NuGet.Config
└── NuGet.exe
├── LICENSE.txt
├── Medidata.ZipkinTracerModule.sln
├── README.md
├── src
└── Medidata.ZipkinTracer.Core
│ ├── .nuget
│ ├── NuGet.Config
│ └── NuGet.exe
│ ├── App_Packages
│ └── LibLog.4.2
│ │ └── LibLog.cs
│ ├── Handlers
│ └── ZipkinMessageHandler.cs
│ ├── Helpers
│ ├── ParserHelper.cs
│ └── SyncHelper.cs
│ ├── ITraceProvider.cs
│ ├── ITracerClient.cs
│ ├── IZipkinConfig.cs
│ ├── Medidata.ZipkinTracer.Core.csproj
│ ├── Medidata.ZipkinTracer.Core.nuspec
│ ├── Middlewares
│ └── ZipkinMiddleware.cs
│ ├── Models
│ ├── Annotation.cs
│ ├── AnnotationBase.cs
│ ├── BinaryAnnotation.cs
│ ├── Endpoint.cs
│ ├── References
│ │ ├── AnnotationType.cs
│ │ └── ZipkinConstants.cs
│ ├── Serialization
│ │ └── Json
│ │ │ ├── JsonAnnotation.cs
│ │ │ ├── JsonBinaryAnnotation.cs
│ │ │ ├── JsonEndpoint.cs
│ │ │ └── JsonSpan.cs
│ └── Span.cs
│ ├── Properties
│ └── AssemblyInfo.cs
│ ├── ServiceEndpoint.cs
│ ├── SpanCollector.cs
│ ├── SpanProcessor.cs
│ ├── SpanProcessorTaskFactory.cs
│ ├── SpanTracer.cs
│ ├── TraceProvider.cs
│ ├── ZipkinClient.cs
│ ├── ZipkinConfig.cs
│ ├── ZipkinTracerExtensions.cs
│ ├── package-nuget.bat
│ └── packages.config
└── tests
└── Medidata.ZipkinTracer.Core.Test
├── Helpers
└── ParserHelperTests.cs
├── HttpModule
└── RequestContextModuleTests.cs
├── Medidata.ZipkinTracer.Core.Test.csproj
├── Models
└── Serialization
│ └── Json
│ └── JsonSpanTests.cs
├── Properties
└── AssemblyInfo.cs
├── SpanCollectorTests.cs
├── SpanProcessorTests.cs
├── SpanProcesssorTaskFactoryTests.cs
├── SpanTracerTests.cs
├── TraceProviderTests.cs
├── ZipkinClientTests.cs
├── ZipkinConfigTests.cs
├── ZipkinEndpointTests.cs
├── app.config
└── packages.config
/.gitignore:
--------------------------------------------------------------------------------
1 | src/*/bin/*
2 | src/*/obj/*
3 | tests/*/bin/*
4 | tests/*/obj/*
5 | src/*/lib/*
6 | *.user
7 | *.suo
8 | *.pdb
9 |
10 | TestResults/
11 | packages/
12 | .vs/*/*
13 |
--------------------------------------------------------------------------------
/.nuget/NuGet.Config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.nuget/NuGet.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdsol/Medidata.ZipkinTracerModule/2fcf3e8639c1a373fc59a95097073174ebdb40f3/.nuget/NuGet.exe
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015 Medidata Solutions Worldwide
2 |
3 | MIT License
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/Medidata.ZipkinTracerModule.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 14
4 | VisualStudioVersion = 14.0.25123.0
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Medidata.ZipkinTracer.Core", "src\Medidata.ZipkinTracer.Core\Medidata.ZipkinTracer.Core.csproj", "{EB432891-204B-4B97-BFB1-A820E424284E}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{ACDBD61A-453C-4AA4-97FB-CD1520DD1D16}"
9 | ProjectSection(SolutionItems) = preProject
10 | .nuget\NuGet.Config = .nuget\NuGet.Config
11 | .nuget\NuGet.exe = .nuget\NuGet.exe
12 | .nuget\NuGet.targets = .nuget\NuGet.targets
13 | .nuget\packages.config = .nuget\packages.config
14 | EndProjectSection
15 | EndProject
16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Medidata.ZipkinTracer.Core.Test", "tests\Medidata.ZipkinTracer.Core.Test\Medidata.ZipkinTracer.Core.Test.csproj", "{0158C8BD-D881-4EE8-A8C6-54707A67A00D}"
17 | EndProject
18 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7CF24590-AC06-44E4-AF93-88A9B2CD1264}"
19 | ProjectSection(SolutionItems) = preProject
20 | README.md = README.md
21 | EndProjectSection
22 | EndProject
23 | Global
24 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
25 | Debug|Any CPU = Debug|Any CPU
26 | Release|Any CPU = Release|Any CPU
27 | EndGlobalSection
28 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
29 | {EB432891-204B-4B97-BFB1-A820E424284E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
30 | {EB432891-204B-4B97-BFB1-A820E424284E}.Debug|Any CPU.Build.0 = Debug|Any CPU
31 | {EB432891-204B-4B97-BFB1-A820E424284E}.Release|Any CPU.ActiveCfg = Release|Any CPU
32 | {EB432891-204B-4B97-BFB1-A820E424284E}.Release|Any CPU.Build.0 = Release|Any CPU
33 | {0158C8BD-D881-4EE8-A8C6-54707A67A00D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
34 | {0158C8BD-D881-4EE8-A8C6-54707A67A00D}.Debug|Any CPU.Build.0 = Debug|Any CPU
35 | {0158C8BD-D881-4EE8-A8C6-54707A67A00D}.Release|Any CPU.ActiveCfg = Release|Any CPU
36 | {0158C8BD-D881-4EE8-A8C6-54707A67A00D}.Release|Any CPU.Build.0 = Release|Any CPU
37 | EndGlobalSection
38 | GlobalSection(SolutionProperties) = preSolution
39 | HideSolutionNode = FALSE
40 | EndGlobalSection
41 | EndGlobal
42 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Deprecated
2 | **This project is deprecated and no longer being maintained.
3 | Please use [zipkin4net](https://github.com/openzipkin/zipkin4net) or other alternatives.**
4 |
5 | # Medidata.ZipkinTracerModule
6 | A .NET implementation of the Zipkin Tracer client.
7 |
8 | ## Overview
9 | This nuget package implements the zipkin tracer client for .net applications.
10 |
11 | **Medidata.ZipkinTracer.Core** : core library for generating zipkin spans from ids sent through from
12 | CrossApplicationTracer and sending it to the zipkin collector using HTTP as the transport protocol. For more information
13 | and implementations in other languages, please check [Openzipkin](https://github.com/openzipkin/).
14 |
15 | ### Enable/Disable zipkin tracing
16 |
17 | Zipkin will record traces if IsSampled HTTP header is true.
18 | This will happen if :
19 | - **a)** the caller of the app has set the IsSampled HTTP header value to true.
20 | - **OR**
21 | - **b)** the url request is not in the `ExcludedPathList` of ZipkinConfig , and using the `SampleRate`, it will
22 | determine whether or not to trace this request. `SampleRate` is the approximate percentage of traces being recorded in
23 | zipkin.
24 |
25 | ## Configurations
26 | Please use `ZipkinConfig` class to configure the module and verify these values and modify them according to your
27 | service/environment.
28 |
29 | - `Bypass` - Controls whether the requests should be sent through the Zipkin module
30 | - **false**: Enables the ZipkinMiddleware/ZipkinMessageHandler
31 | - **true**: Disables the ZipkinMiddleware/ZipkinMessageHandler
32 | - `ZipkinBaseUri` - is the zipkin scribe/collector server URI with port to send the Spans
33 | - `Domain` - is a valid public facing base url for your app instance. Zipkin will use to label the trace.
34 | - by default this looks at the incoming requests and uses the hostname from them. It's a `Func` - customise this to your requirements.
35 | - `SpanProcessorBatchSize` - how many Spans should be sent to the zipkin scribe/collector in one go.
36 | - `SampleRate` - 1 decimal point float value between 0 and 1. This value will determine randomly if the current request will be traced or not.
37 | - `NotToBeDisplayedDomainList`(optional) - It will be used when logging host name by excluding these strings in service name attribute
38 | e.g. domain: ".xyz.com", host: "abc.xyz.com" will be logged as "abc" only
39 | - `ExcludedPathList`(optional) - Path list that is not needed for tracing. Each item must start with "/".
40 | - `Create128BitTraceId` - Create new traces using 128 bit (32 hex character) traceId
41 |
42 |
43 | ```csharp
44 | var config = new ZipkinConfig
45 | {
46 | Bypass = request => request.Uri.AbsolutePath.StartsWith("/allowed"),
47 | Domain = request => new Uri("https://yourservice.com"), // or, you might like to derive a value from the request, like r => new Uri($"{r.Scheme}{Uri.SchemeDelimiter}{r.Host}"),
48 | ZipkinBaseUri = new Uri("http://zipkin.xyz.net:9411"),
49 | SpanProcessorBatchSize = 10,
50 | SampleRate = 0.5,
51 | NotToBeDisplayedDomainList = new[] { ".xyz.com", ".myApplication.net" },
52 | ExcludedPathList = new[] { "/check_uri", "/status" }
53 | }
54 | ```
55 |
56 | ## Tracing
57 |
58 | ### Server trace (Inbound request)
59 | Server Trace relies on OWIN Middleware. Please create OWIN Startup class then call `UseZipkin()`.
60 |
61 |
62 | ```csharp
63 | using Medidata.ZipkinTracer.Core;
64 | using Medidata.ZipkinTracer.Core.Middlewares;
65 |
66 | public class Startup
67 | {
68 | public void Configuration(IAppBuilder app)
69 | {
70 | app.UseZipkin(new ZipkinConfig
71 | {
72 | Domain = new Uri("https://yourservice.com"),
73 | ZipkinBaseUri = new Uri("http://zipkin.xyz.net:9411"),
74 | SpanProcessorBatchSize = 10,
75 | SampleRate = 0.5
76 | });
77 | }
78 | }
79 |
80 | ```
81 |
82 | ### Client trace (Outbound request)
83 | Client Trace relies on HttpMessageHandler for HttpClient. Please pass a ZipkinMessageHandler instance into HttpClient.
84 |
85 | Note: You will need the `GetOwinContext` extension method. If you host in IIS with `System.Web`, this can be found in `Microsoft.Owin.Host.SystemWeb`.
86 |
87 | ```csharp
88 | using Medidata.ZipkinTracer.Core.Handlers;
89 |
90 | public class HomeController : AsyncController
91 | {
92 | public async Task Index()
93 | {
94 | var context = System.Web.HttpContext.Current.GetOwinContext();
95 | var config = new ZipkinConfig // you can use Dependency Injection to get the same config across your app.
96 | {
97 | Domain = new Uri("https://yourservice.com"),
98 | ZipkinBaseUri = new Uri("http://zipkin.xyz.net:9411"),
99 | SpanProcessorBatchSize = 10,
100 | SampleRate = 0.5
101 | }
102 | var client = new ZipkinClient(config, context);
103 |
104 | using (var httpClient = new HttpClient(new ZipkinMessageHandler(client))))
105 | {
106 | var response = await httpClient.GetAsync("http://www.google.com");
107 | if (response.IsSuccessStatusCode)
108 | {
109 | var content = await response.Content.ReadAsStringAsync();
110 | }
111 | }
112 |
113 | return View();
114 | }
115 | }
116 | ```
117 |
118 | #### Recording arbitrary events and additional information
119 | **NOTE:This can only be used if you are NOT using ZipkinMessageHandler as described above "Client trace (Outbound request)". ```RecordBinary()` methods:
122 | ```csharp
123 | var context = System.Web.HttpContext.Current.GetOwinContext();
124 | var config = new ZipkinConfig // you can use Dependency Injection to get the same config across your app.
125 | {
126 | Domain = new Uri("https://yourservice.com"),
127 | ZipkinBaseUri = new Uri("http://zipkin.xyz.net:9411"),
128 | SpanProcessorBatchSize = 10,
129 | SampleRate = 0.5
130 | }
131 | var zipkinClient = new ZipkinClient(config, context);
132 | var url = "https://abc.xyz.com:8000";
133 | var requestUri = "/object/1";
134 | HttpResponseMessage result;
135 | using (var client = new HttpClient())
136 | {
137 | client.BaseAddress = new Uri(url);
138 |
139 | // start client trace
140 | var span = zipkinClient.StartClientTrace(new Uri(client.BaseAddress, requestUri), "GET");
141 |
142 | zipkinClient.Record(span, "A description which will gets recorded with a timestamp.");
143 |
144 | result = await client.GetAsync(requestUri);
145 |
146 | // Record the total memory used after the call
147 | zipkinClient.RecordBinary(span, "client.memory", GC.GetTotalMemory(false));
148 |
149 | // end client trace
150 | zipkinClient.EndClientTrace(span);
151 | }
152 | ...
153 | ```
154 |
155 | In case of the `ZipkinClient.Record()` method, the second parameter(`value`) can be omitted during the call, in that
156 | case the caller member name (method, property etc.) will get recorded.
157 |
158 | #### Recording a local component
159 | With the `RecordLocalComponent()` method of the client a local component (or information) can be recorded for the
160 | current trace. This will result an additional binary annotation with the 'lc' key (LOCAL_COMPONENT) and a custom value.
161 |
162 | #### Troubleshooting
163 |
164 | ##### Logs
165 |
166 | Logging internal to the library is provided via the [LibLog abstraction](https://github.com/damianh/LibLog). Caveat: to get logs, you must have initialised your logging framework on application-start ([console app example](https://github.com/damianh/LibLog/blob/master/src/LibLog.Example.Log4Net/Program.cs#L12) - a web-app might do this in OWIN Startup or Global.asax, or the inversion of control container initialisation).
167 |
168 | ## Contributors
169 | ZipkinTracer is (c) Medidata Solutions Worldwide and owned by its major contributors:
170 | * Tomoko Kwan
171 | * [Kenya Matsumoto](https://github.com/kenyamat)
172 | * [Brent Villanueva](https://github.com/bvillanueva-mdsol)
173 | * [Laszlo Schreck](https://github.com/lschreck-mdsol)
174 | * [Jordi Carres](https://github.com/jcarres-mdsol)
175 | * [Herry Kurniawan](https://github.com/hkurniawan-mdsol)
176 |
--------------------------------------------------------------------------------
/src/Medidata.ZipkinTracer.Core/.nuget/NuGet.Config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/Medidata.ZipkinTracer.Core/.nuget/NuGet.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdsol/Medidata.ZipkinTracerModule/2fcf3e8639c1a373fc59a95097073174ebdb40f3/src/Medidata.ZipkinTracer.Core/.nuget/NuGet.exe
--------------------------------------------------------------------------------
/src/Medidata.ZipkinTracer.Core/Handlers/ZipkinMessageHandler.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Http;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 |
5 | namespace Medidata.ZipkinTracer.Core.Handlers
6 | {
7 | public class ZipkinMessageHandler : DelegatingHandler
8 | {
9 | private readonly ITracerClient _client;
10 |
11 | public ZipkinMessageHandler(ITracerClient client)
12 | : this(client, new HttpClientHandler())
13 | {
14 | }
15 |
16 | public ZipkinMessageHandler(ITracerClient client, HttpMessageHandler innerHandler)
17 | : base(innerHandler)
18 | {
19 | _client = client;
20 | }
21 |
22 | protected override async Task SendAsync(
23 | HttpRequestMessage request, CancellationToken cancellationToken)
24 | {
25 | if (!_client.IsTraceOn)
26 | {
27 | return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
28 | }
29 |
30 | var nextTrace = _client.TraceProvider.GetNext();
31 |
32 | request.Headers.Add(TraceProvider.TraceIdHeaderName, nextTrace.TraceId);
33 | request.Headers.Add(TraceProvider.SpanIdHeaderName, nextTrace.SpanId);
34 | request.Headers.Add(TraceProvider.ParentSpanIdHeaderName, nextTrace.ParentSpanId);
35 | request.Headers.Add(TraceProvider.SampledHeaderName, nextTrace.ParentSpanId);
36 |
37 | var span = _client.StartClientTrace(request.RequestUri, request.Method.ToString(), nextTrace);
38 | var response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
39 |
40 | _client.EndClientTrace(span, (int)response.StatusCode);
41 | return response;
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/src/Medidata.ZipkinTracer.Core/Helpers/ParserHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 |
4 | namespace Medidata.ZipkinTracer.Core.Helpers
5 | {
6 | internal static class ParserHelper
7 | {
8 | ///
9 | /// Checks if string value can be converted to 128bit (Guid) or 64bit (long)
10 | ///
11 | ///
12 | ///
13 | internal static bool IsParsableTo128Or64Bit(this string value)
14 | {
15 | if (value.IsParsableToGuid()) return true;
16 | return value.IsParsableToLong();
17 | }
18 |
19 | ///
20 | /// Checks if hex string value is parsable to Guid
21 | ///
22 | ///
23 | ///
24 | internal static bool IsParsableToGuid(this string value)
25 | {
26 | Guid result;
27 | return Guid.TryParseExact(value, "N", out result);
28 | }
29 |
30 | ///
31 | /// Checks if hex string value is parsable to long
32 | ///
33 | ///
34 | ///
35 | internal static bool IsParsableToLong(this string value)
36 | {
37 | long result;
38 | return !string.IsNullOrWhiteSpace(value) && long.TryParse(value, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out result);
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Medidata.ZipkinTracer.Core/Helpers/SyncHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Medidata.ZipkinTracer.Core.Helpers
4 | {
5 | public static class SyncHelper
6 | {
7 | public static void ExecuteSafely(object sync, Func canExecute, Action actiontoExecuteSafely)
8 | {
9 | if (canExecute())
10 | {
11 | lock (sync)
12 | {
13 | if (canExecute())
14 | {
15 | actiontoExecuteSafely();
16 | }
17 | }
18 | }
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Medidata.ZipkinTracer.Core/ITraceProvider.cs:
--------------------------------------------------------------------------------
1 | namespace Medidata.ZipkinTracer.Core
2 | {
3 | ///
4 | /// TraceProvider interface
5 | ///
6 | public interface ITraceProvider
7 | {
8 | ///
9 | /// Gets a TraceId
10 | ///
11 | string TraceId { get; }
12 |
13 | ///
14 | /// Gets a SpanId
15 | ///
16 | string SpanId { get; }
17 |
18 | ///
19 | /// Gets a ParentSpanId
20 | ///
21 | string ParentSpanId { get; }
22 |
23 | ///
24 | /// Gets IsSampled
25 | ///
26 | bool IsSampled { get; }
27 |
28 | ///
29 | /// Gets a Trace for outgoing HTTP request.
30 | ///
31 | /// The trace
32 | ITraceProvider GetNext();
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Medidata.ZipkinTracer.Core/ITracerClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.CompilerServices;
3 | using Medidata.ZipkinTracer.Models;
4 |
5 | namespace Medidata.ZipkinTracer.Core
6 | {
7 | public interface ITracerClient
8 | {
9 | bool IsTraceOn { get; }
10 |
11 | ITraceProvider TraceProvider { get; }
12 |
13 | Span StartServerTrace(Uri requestUri, string methodName);
14 |
15 | Span StartClientTrace(Uri remoteUri, string methodName, ITraceProvider trace);
16 |
17 | void EndServerTrace(Span serverSpan);
18 |
19 | void EndClientTrace(Span clientSpan, int statusCode);
20 |
21 | void Record(Span span, [CallerMemberName] string value = null);
22 |
23 | void RecordBinary(Span span, string key, T value);
24 |
25 | void RecordLocalComponent(Span span, string value);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Medidata.ZipkinTracer.Core/IZipkinConfig.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Microsoft.Owin;
4 |
5 | namespace Medidata.ZipkinTracer.Core
6 | {
7 | public interface IZipkinConfig
8 | {
9 | Predicate Bypass { get; set; }
10 |
11 | Uri ZipkinBaseUri { get; set; }
12 |
13 | Func Domain { get; set; }
14 |
15 | uint SpanProcessorBatchSize { get; set; }
16 |
17 | IList ExcludedPathList { get; set; }
18 |
19 | double SampleRate { get; set; }
20 |
21 | IList NotToBeDisplayedDomainList { get; set; }
22 |
23 | bool Create128BitTraceId { get; set; }
24 |
25 | bool ShouldBeSampled(string sampled, string requestPath);
26 |
27 | void Validate();
28 | }
29 | }
--------------------------------------------------------------------------------
/src/Medidata.ZipkinTracer.Core/Medidata.ZipkinTracer.Core.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Debug
7 | AnyCPU
8 | {EB432891-204B-4B97-BFB1-A820E424284E}
9 | Library
10 | Properties
11 | Medidata.ZipkinTracer.Core
12 | Medidata.ZipkinTracer.Core
13 | v4.5
14 | 512
15 | .\
16 | true
17 |
18 |
19 |
20 |
21 | true
22 | full
23 | false
24 | bin\Debug\
25 | TRACE;DEBUG;LIBLOG_PUBLIC
26 | prompt
27 | 4
28 |
29 |
30 | pdbonly
31 | true
32 | bin\Release\
33 | TRACE;LIBLOG_PUBLIC
34 | prompt
35 | 4
36 |
37 |
38 |
39 | ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll
40 | True
41 |
42 |
43 | ..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll
44 | True
45 |
46 |
47 | ..\..\packages\Owin.1.0\lib\net40\Owin.dll
48 | True
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 | Designer
95 |
96 |
97 | Designer
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
109 |
110 |
111 |
112 |
119 |
--------------------------------------------------------------------------------
/src/Medidata.ZipkinTracer.Core/Medidata.ZipkinTracer.Core.nuspec:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $id$
5 | $version$
6 | $title$
7 | $author$
8 | $author$
9 | https://github.com/mdsol/Medidata.ZipkinTracerModule
10 | Medidata Zipkin Tracer Core Service
11 | Initial Beta Release of the ZipkinTracer Core Service for .Net Projects
12 | Copyright 2014
13 | ZipkinTracer Core
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/Medidata.ZipkinTracer.Core/Middlewares/ZipkinMiddleware.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Microsoft.Owin;
3 | using Owin;
4 |
5 | namespace Medidata.ZipkinTracer.Core.Middlewares
6 | {
7 | public class ZipkinMiddleware : OwinMiddleware
8 | {
9 | private readonly IZipkinConfig _config;
10 |
11 | public ZipkinMiddleware(OwinMiddleware next, IZipkinConfig options) : base(next)
12 | {
13 | _config = options;
14 | }
15 |
16 | public override async Task Invoke(IOwinContext context)
17 | {
18 | if (_config.Bypass != null && _config.Bypass(context.Request))
19 | {
20 | await Next.Invoke(context);
21 | return;
22 | }
23 |
24 | var zipkin = new ZipkinClient(_config, context);
25 | var span = zipkin.StartServerTrace(context.Request.Uri, context.Request.Method);
26 | await Next.Invoke(context);
27 | zipkin.EndServerTrace(span);
28 | }
29 | }
30 |
31 | public static class AppBuilderExtensions
32 | {
33 | public static void UseZipkin(this IAppBuilder app, IZipkinConfig config)
34 | {
35 | config.Validate();
36 | app.Use(config);
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/src/Medidata.ZipkinTracer.Core/Models/Annotation.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Medidata.ZipkinTracer.Models
4 | {
5 | public class Annotation: AnnotationBase
6 | {
7 | public DateTimeOffset Timestamp { get; set; } = DateTimeOffset.UtcNow;
8 |
9 | public string Value { get; set; }
10 |
11 | public int DurationMilliseconds { get; set; }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Medidata.ZipkinTracer.Core/Models/AnnotationBase.cs:
--------------------------------------------------------------------------------
1 | namespace Medidata.ZipkinTracer.Models
2 | {
3 | public abstract class AnnotationBase
4 | {
5 | public Endpoint Host { get; set; }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/Medidata.ZipkinTracer.Core/Models/BinaryAnnotation.cs:
--------------------------------------------------------------------------------
1 | namespace Medidata.ZipkinTracer.Models
2 | {
3 | public class BinaryAnnotation: AnnotationBase
4 | {
5 | public string Key { get; set; }
6 |
7 | public object Value { get; set; }
8 |
9 | public AnnotationType AnnotationType => Value.GetType().AsAnnotationType();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/Medidata.ZipkinTracer.Core/Models/Endpoint.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 |
3 | namespace Medidata.ZipkinTracer.Models
4 | {
5 | public class Endpoint
6 | {
7 | public IPAddress IPAddress { get; set; }
8 |
9 | public ushort Port { get; set; }
10 |
11 | public string ServiceName { get; set; }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Medidata.ZipkinTracer.Core/Models/References/AnnotationType.cs:
--------------------------------------------------------------------------------
1 | namespace Medidata.ZipkinTracer.Models
2 | {
3 | public enum AnnotationType
4 | {
5 | Boolean = 0,
6 | ByteArray = 1,
7 | Int16 = 2,
8 | Int32 = 3,
9 | Int64 = 4,
10 | Double = 5,
11 | String = 6,
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Medidata.ZipkinTracer.Core/Models/References/ZipkinConstants.cs:
--------------------------------------------------------------------------------
1 | namespace Medidata.ZipkinTracer.Models
2 | {
3 | public static class ZipkinConstants
4 | {
5 | public static readonly string ClientSend = "cs";
6 | public static readonly string ClientReceive = "cr";
7 | public static readonly string ServerSend = "ss";
8 | public static readonly string ServerReceive = "sr";
9 | public static readonly string LocalComponent = "lc";
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/Medidata.ZipkinTracer.Core/Models/Serialization/Json/JsonAnnotation.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Newtonsoft.Json;
3 |
4 | namespace Medidata.ZipkinTracer.Models
5 | {
6 | internal class JsonAnnotation
7 | {
8 | private readonly Annotation annotation;
9 |
10 | [JsonProperty("endpoint")]
11 | public JsonEndpoint Endpoint => new JsonEndpoint(annotation.Host);
12 |
13 | [JsonProperty("value")]
14 | public string Value => annotation.Value;
15 |
16 | [JsonProperty("timestamp")]
17 | public long Timestamp => annotation.Timestamp.ToUnixTimeMicroseconds();
18 |
19 | public JsonAnnotation(Annotation annotation)
20 | {
21 | if (annotation == null)
22 | throw new ArgumentNullException(nameof(annotation));
23 |
24 | this.annotation = annotation;
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/src/Medidata.ZipkinTracer.Core/Models/Serialization/Json/JsonBinaryAnnotation.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Newtonsoft.Json;
3 |
4 | namespace Medidata.ZipkinTracer.Models
5 | {
6 | internal class JsonBinaryAnnotation
7 | {
8 | private readonly BinaryAnnotation binaryAnnotation;
9 |
10 | [JsonProperty("endpoint")]
11 | public JsonEndpoint Endpoint => new JsonEndpoint(binaryAnnotation.Host);
12 |
13 | [JsonProperty("key")]
14 | public string Key => binaryAnnotation.Key;
15 |
16 | [JsonProperty("value")]
17 | public string Value => binaryAnnotation.Value.ToString();
18 |
19 | public JsonBinaryAnnotation(BinaryAnnotation binaryAnnotation)
20 | {
21 | if (binaryAnnotation == null)
22 | throw new ArgumentNullException(nameof(binaryAnnotation));
23 |
24 | this.binaryAnnotation = binaryAnnotation;
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/src/Medidata.ZipkinTracer.Core/Models/Serialization/Json/JsonEndpoint.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Newtonsoft.Json;
3 |
4 | namespace Medidata.ZipkinTracer.Models
5 | {
6 | internal class JsonEndpoint
7 | {
8 | private readonly Endpoint endpoint;
9 |
10 | [JsonProperty("ipv4")]
11 | public string IPv4 => endpoint.IPAddress.ToString();
12 |
13 | [JsonProperty("port")]
14 | public ushort Port => endpoint.Port;
15 |
16 | [JsonProperty("serviceName")]
17 | public string ServiceName => endpoint.ServiceName;
18 |
19 | public JsonEndpoint(Endpoint endpoint)
20 | {
21 | if (endpoint == null)
22 | throw new ArgumentNullException(nameof(endpoint));
23 |
24 | this.endpoint = endpoint;
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/src/Medidata.ZipkinTracer.Core/Models/Serialization/Json/JsonSpan.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using Newtonsoft.Json;
5 |
6 | namespace Medidata.ZipkinTracer.Models
7 | {
8 | internal class JsonSpan
9 | {
10 | private readonly Span span;
11 |
12 | [JsonProperty("traceId")]
13 | public string TraceId => span.TraceId;
14 |
15 | [JsonProperty("name")]
16 | public string Name => span.Name;
17 |
18 | [JsonProperty("id")]
19 | public string Id => span.Id;
20 |
21 | [JsonProperty("parentId", NullValueHandling = NullValueHandling.Ignore)]
22 | public string ParentId => string.IsNullOrWhiteSpace(span.ParentId) ? null : span.ParentId;
23 |
24 | [JsonProperty("annotations")]
25 | public IEnumerable Annotations =>
26 | span.GetAnnotationsByType().Select(annotation => new JsonAnnotation(annotation));
27 |
28 | [JsonProperty("binaryAnnotations")]
29 | public IEnumerable BinaryAnnotations =>
30 | span.GetAnnotationsByType().Select(annotation => new JsonBinaryAnnotation(annotation));
31 |
32 | public JsonSpan(Span span)
33 | {
34 | if (span == null)
35 | throw new ArgumentNullException(nameof(span));
36 |
37 | this.span = span;
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Medidata.ZipkinTracer.Core/Models/Span.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace Medidata.ZipkinTracer.Models
5 | {
6 | public class Span
7 | {
8 | public string TraceId { get; set; }
9 |
10 | public string Name { get; set; }
11 |
12 | public string Id { get; set; }
13 |
14 | public string ParentId { get; set; }
15 |
16 | public IList Annotations { get; } = new List();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Medidata.ZipkinTracer.Core/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("Medidata.ZipkinTracer.Core")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyCompany("Medidata Solutions, Inc.")]
11 | [assembly: AssemblyProduct("Medidata.ZipkinTracer.Core")]
12 | [assembly: AssemblyCopyright("Copyright © Medidata Solutions, Inc. 2016")]
13 | [assembly: AssemblyTrademark("")]
14 | [assembly: AssemblyCulture("")]
15 |
16 | #if DEBUG
17 | [assembly: AssemblyConfiguration("Debug")]
18 | #else
19 | [assembly: AssemblyConfiguration("Release")]
20 | #endif
21 |
22 | // Setting ComVisible to false makes the types in this assembly not visible
23 | // to COM components. If you need to access a type in this assembly from
24 | // COM, set the ComVisible attribute to true on that type.
25 | [assembly: ComVisible(false)]
26 |
27 | // The following GUID is for the ID of the typelib if this project is exposed to COM
28 | [assembly: Guid("dfbe97d6-8b81-4bee-b2ce-23ef0f4d9953")]
29 |
30 | // Version information for an assembly consists of the following four values:
31 | //
32 | // Major Version
33 | // Minor Version
34 | // Build Number
35 | // Revision
36 | //
37 | // You can specify all the values or you can default the Build and Revision Numbers
38 | // by using the '*' as shown below:
39 | // [assembly: AssemblyVersion("1.0.*")]
40 | [assembly: AssemblyVersion("3.2.0")]
41 | [assembly: AssemblyFileVersion("3.2.0")]
42 | [assembly: AssemblyInformationalVersion("3.2.0")]
43 | [assembly: InternalsVisibleTo("Medidata.ZipkinTracer.Core.Test")]
--------------------------------------------------------------------------------
/src/Medidata.ZipkinTracer.Core/ServiceEndpoint.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Net;
4 | using System.Net.Sockets;
5 | using Medidata.ZipkinTracer.Models;
6 |
7 | namespace Medidata.ZipkinTracer.Core
8 | {
9 | public class ServiceEndpoint
10 | {
11 | public virtual Endpoint GetLocalEndpoint(string serviceName, ushort port)
12 | {
13 | return new Endpoint()
14 | {
15 | ServiceName = serviceName,
16 | IPAddress = GetLocalIPAddress(),
17 | Port = port
18 | };
19 | }
20 |
21 | public virtual Endpoint GetRemoteEndpoint(Uri remoteServer, string remoteServiceName)
22 | {
23 | var addressBytes = GetRemoteIPAddress(remoteServer).GetAddressBytes();
24 | if (BitConverter.IsLittleEndian)
25 | {
26 | Array.Reverse(addressBytes);
27 | }
28 |
29 | var ipAddressStr = BitConverter.ToInt32(addressBytes, 0);
30 | var hostIpAddressStr = (int)IPAddress.HostToNetworkOrder(ipAddressStr);
31 |
32 | return new Endpoint()
33 | {
34 | ServiceName = remoteServiceName,
35 | IPAddress = GetRemoteIPAddress(remoteServer),
36 | Port = (ushort)remoteServer.Port
37 | };
38 | }
39 |
40 | private static IPAddress GetLocalIPAddress()
41 | {
42 | if (!System.Net.NetworkInformation.NetworkInterface.GetIsNetworkAvailable())
43 | {
44 | return null;
45 | }
46 |
47 | IPHostEntry host = Dns.GetHostEntry(Dns.GetHostName());
48 |
49 | return host
50 | .AddressList
51 | .FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetwork);
52 | }
53 |
54 | private static IPAddress GetRemoteIPAddress(Uri remoteServer)
55 | {
56 | var adressList = Dns.GetHostAddresses(remoteServer.Host);
57 | return adressList.FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetwork);
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Medidata.ZipkinTracer.Core/SpanCollector.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 | using Medidata.ZipkinTracer.Models;
4 | using Medidata.ZipkinTracer.Core.Helpers;
5 |
6 | namespace Medidata.ZipkinTracer.Core
7 | {
8 | public class SpanCollector
9 | {
10 | private const int MAX_QUEUE_SIZE = 100;
11 | internal static BlockingCollection spanQueue;
12 |
13 | internal SpanProcessor spanProcessor;
14 |
15 | public bool IsStarted { get; private set; }
16 | private readonly object syncObj = new object();
17 |
18 | public SpanCollector(Uri uri, uint maxProcessorBatchSize)
19 | {
20 | if (spanQueue == null)
21 | {
22 | spanQueue = new BlockingCollection(MAX_QUEUE_SIZE);
23 | }
24 |
25 | spanProcessor = new SpanProcessor(uri, spanQueue, maxProcessorBatchSize);
26 | }
27 |
28 | public virtual void Collect(Span span)
29 | {
30 | spanQueue.Add(span);
31 | }
32 |
33 | public virtual void Start()
34 | {
35 | SyncHelper.ExecuteSafely(syncObj, () => !IsStarted, () => { spanProcessor.Start(); IsStarted = true; });
36 | }
37 |
38 | public virtual void Stop()
39 | {
40 | SyncHelper.ExecuteSafely(syncObj, () => IsStarted, () => { spanProcessor.Stop() ; IsStarted = false; });
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Medidata.ZipkinTracer.Core/SpanProcessor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Net;
7 | using Medidata.ZipkinTracer.Core.Logging;
8 | using Medidata.ZipkinTracer.Models;
9 | using Newtonsoft.Json;
10 |
11 | namespace Medidata.ZipkinTracer.Core
12 | {
13 | public class SpanProcessor
14 | {
15 | //send contents of queue if it has pending items but less than max batch size after doing max number of polls
16 | internal const int MAX_NUMBER_OF_POLLS = 5;
17 | internal const string ZIPKIN_SPAN_POST_PATH = "/api/v1/spans";
18 |
19 | private readonly Uri uri;
20 | internal BlockingCollection spanQueue;
21 |
22 | //using a queue because even as we pop items to send to zipkin, another
23 | //thread can be adding spans if someone shares the span processor accross threads
24 | internal ConcurrentQueue serializableSpans;
25 | internal SpanProcessorTaskFactory spanProcessorTaskFactory;
26 |
27 | internal int subsequentPollCount;
28 | internal uint maxBatchSize;
29 | private readonly ILog logger;
30 |
31 | public SpanProcessor(Uri uri, BlockingCollection spanQueue, uint maxBatchSize)
32 | {
33 | if ( spanQueue == null)
34 | {
35 | throw new ArgumentNullException("spanQueue");
36 | }
37 |
38 | if (uri == null)
39 | {
40 | throw new ArgumentNullException("uri");
41 | }
42 |
43 | this.uri = uri;
44 | this.spanQueue = spanQueue;
45 | this.serializableSpans = new ConcurrentQueue();
46 | this.maxBatchSize = maxBatchSize;
47 | this.logger = LogProvider.GetCurrentClassLogger();
48 | spanProcessorTaskFactory = new SpanProcessorTaskFactory();
49 | }
50 |
51 | public virtual void Stop()
52 | {
53 | spanProcessorTaskFactory.StopTask();
54 | LogSubmittedSpans();
55 | }
56 |
57 | public virtual void Start()
58 | {
59 | spanProcessorTaskFactory.CreateAndStart(LogSubmittedSpans);
60 | }
61 |
62 | internal virtual void LogSubmittedSpans()
63 | {
64 | var anyNewSpans = ProcessQueuedSpans();
65 |
66 | if (anyNewSpans) subsequentPollCount = 0;
67 | else if (serializableSpans.Count > 0) subsequentPollCount++;
68 |
69 | if (ShouldSendQueuedSpansOverWire())
70 | {
71 | SendSpansOverHttp();
72 | }
73 | }
74 |
75 | public virtual void SendSpansToZipkin(string spans)
76 | {
77 | if(spans == null) throw new ArgumentNullException("spans");
78 | using (var client = new WebClient())
79 | {
80 | try
81 | {
82 | client.BaseAddress = uri.ToString();
83 | client.UploadString(ZIPKIN_SPAN_POST_PATH, "POST", spans);
84 | }
85 | catch (WebException ex)
86 | {
87 | //Very friendly HttpWebRequest Error message with good information.
88 | LogHttpErrorMessage(ex);
89 | throw;
90 | }
91 | }
92 | }
93 |
94 | private bool ShouldSendQueuedSpansOverWire()
95 | {
96 | return serializableSpans.Any() &&
97 | (serializableSpans.Count() >= maxBatchSize
98 | || spanProcessorTaskFactory.IsTaskCancelled()
99 | || subsequentPollCount > MAX_NUMBER_OF_POLLS);
100 | }
101 |
102 | private bool ProcessQueuedSpans()
103 | {
104 | Span span;
105 | var anyNewSpansQueued = false;
106 | while (spanQueue.TryTake(out span))
107 | {
108 | serializableSpans.Enqueue(new JsonSpan(span));
109 | anyNewSpansQueued = true;
110 | }
111 | return anyNewSpansQueued;
112 | }
113 |
114 | private void SendSpansOverHttp()
115 | {
116 | var spansJsonRepresentation = GetSpansJSONRepresentation();
117 | SendSpansToZipkin(spansJsonRepresentation);
118 | subsequentPollCount = 0;
119 | }
120 |
121 | private string GetSpansJSONRepresentation()
122 | {
123 | JsonSpan span;
124 | var spanList = new List();
125 | //using Dequeue into a list so that the span is removed from the queue as we add it to list
126 | while (serializableSpans.TryDequeue(out span))
127 | {
128 | spanList.Add(span);
129 | }
130 | var spansJsonRepresentation = JsonConvert.SerializeObject(spanList);
131 | return spansJsonRepresentation;
132 | }
133 |
134 | private void LogHttpErrorMessage(WebException ex)
135 | {
136 | var response = ex.Response as HttpWebResponse;
137 | if ((response == null)) return;
138 | var responseStream = response.GetResponseStream();
139 | var responseString = responseStream != null ? new StreamReader(responseStream).ReadToEnd() : string.Empty;
140 | logger.ErrorFormat(
141 | "Failed to send spans to Zipkin server (HTTP status code returned: {0}). Exception message: {1}, response from server: {2}",
142 | response.StatusCode, ex.Message, responseString);
143 | }
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/src/Medidata.ZipkinTracer.Core/SpanProcessorTaskFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics.CodeAnalysis;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using Medidata.ZipkinTracer.Core.Logging;
6 | using Medidata.ZipkinTracer.Core.Helpers;
7 |
8 | namespace Medidata.ZipkinTracer.Core
9 | {
10 | public class SpanProcessorTaskFactory
11 | {
12 | private Task spanProcessorTaskInstance;
13 | private CancellationTokenSource cancellationTokenSource;
14 | private ILog logger;
15 | private const int defaultDelayTime = 500;
16 | private const int encounteredAnErrorDelayTime = 30000;
17 |
18 | readonly object sync = new object();
19 |
20 | public SpanProcessorTaskFactory(ILog logger, CancellationTokenSource cancellationTokenSource)
21 | {
22 | this.logger = logger ?? LogProvider.GetCurrentClassLogger();
23 | this.cancellationTokenSource = cancellationTokenSource ?? new CancellationTokenSource();
24 | }
25 |
26 | public SpanProcessorTaskFactory()
27 | :this(LogProvider.GetCurrentClassLogger(), new CancellationTokenSource())
28 | {
29 | }
30 |
31 | [ExcludeFromCodeCoverage] //excluded from code coverage since this class is a 1 liner that starts up a background thread
32 | public virtual void CreateAndStart(Action action)
33 | {
34 | SyncHelper.ExecuteSafely(sync, () => spanProcessorTaskInstance == null || spanProcessorTaskInstance.Status == TaskStatus.Faulted,
35 | () =>
36 | {
37 | spanProcessorTaskInstance = Task.Factory.StartNew(() => ActionWrapper(action), cancellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
38 | });
39 | }
40 |
41 | public virtual void StopTask()
42 | {
43 | SyncHelper.ExecuteSafely(sync, () => cancellationTokenSource.Token.CanBeCanceled, () => cancellationTokenSource.Cancel());
44 | }
45 |
46 | internal async void ActionWrapper(Action action)
47 | {
48 | while (!IsTaskCancelled())
49 | {
50 | int delayTime = defaultDelayTime;
51 | try
52 | {
53 | action();
54 | }
55 | catch (Exception ex)
56 | {
57 | logger.ErrorException("Error in SpanProcessorTask", ex);
58 | delayTime = encounteredAnErrorDelayTime;
59 | }
60 |
61 | // stop loop if task is cancelled while delay is in process
62 | try
63 | {
64 | await Task.Delay(delayTime, cancellationTokenSource.Token);
65 | }
66 | catch (TaskCanceledException)
67 | {
68 | break;
69 | }
70 |
71 | }
72 | }
73 |
74 | public virtual bool IsTaskCancelled()
75 | {
76 | return cancellationTokenSource.IsCancellationRequested;
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/Medidata.ZipkinTracer.Core/SpanTracer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using Medidata.ZipkinTracer.Models;
5 |
6 | namespace Medidata.ZipkinTracer.Core
7 | {
8 | public class SpanTracer
9 | {
10 | private SpanCollector spanCollector;
11 | private string serviceName;
12 | private ushort servicePort;
13 | private ServiceEndpoint zipkinEndpoint;
14 | private IEnumerable zipkinNotToBeDisplayedDomainList;
15 |
16 | public SpanTracer(SpanCollector spanCollector, ServiceEndpoint zipkinEndpoint, IEnumerable zipkinNotToBeDisplayedDomainList, Uri domain)
17 | {
18 | if (spanCollector == null) throw new ArgumentNullException(nameof(spanCollector));
19 | if (zipkinEndpoint == null) throw new ArgumentNullException(nameof(zipkinEndpoint));
20 | if (zipkinNotToBeDisplayedDomainList == null) throw new ArgumentNullException(nameof(zipkinNotToBeDisplayedDomainList));
21 | if (domain == null) throw new ArgumentNullException(nameof(domain));
22 |
23 | this.spanCollector = spanCollector;
24 | this.zipkinEndpoint = zipkinEndpoint;
25 | this.zipkinNotToBeDisplayedDomainList = zipkinNotToBeDisplayedDomainList;
26 | var domainHost = domain.Host;
27 | this.serviceName = CleanServiceName(domainHost);
28 | this.servicePort = (ushort)domain.Port;
29 | }
30 |
31 | public virtual Span ReceiveServerSpan(string spanName, string traceId, string parentSpanId, string spanId, Uri requestUri)
32 | {
33 | var newSpan = CreateNewSpan(spanName, traceId, parentSpanId, spanId);
34 | var serviceEndpoint = zipkinEndpoint.GetLocalEndpoint(serviceName, (ushort)requestUri.Port);
35 |
36 | var annotation = new Annotation()
37 | {
38 | Host = serviceEndpoint,
39 | Value = ZipkinConstants.ServerReceive
40 | };
41 |
42 | newSpan.Annotations.Add(annotation);
43 |
44 | AddBinaryAnnotation("http.path", requestUri.AbsolutePath, newSpan, serviceEndpoint);
45 |
46 | return newSpan;
47 | }
48 |
49 | public virtual void SendServerSpan(Span span)
50 | {
51 | if (span == null)
52 | {
53 | throw new ArgumentNullException(nameof(span));
54 | }
55 |
56 | if (span.Annotations == null || !span.Annotations.Any())
57 | {
58 | throw new ArgumentException("Invalid server span: Annotations list is invalid.");
59 | }
60 |
61 | var annotation = new Annotation()
62 | {
63 | Host = span.Annotations.First().Host,
64 | Value = ZipkinConstants.ServerSend
65 | };
66 |
67 | span.Annotations.Add(annotation);
68 |
69 | spanCollector.Collect(span);
70 | }
71 |
72 | public virtual Span SendClientSpan(string spanName, string traceId, string parentSpanId, string spanId, Uri remoteUri)
73 | {
74 | var newSpan = CreateNewSpan(spanName, traceId, parentSpanId, spanId);
75 | var serviceEndpoint = zipkinEndpoint.GetLocalEndpoint(serviceName, (ushort)remoteUri.Port);
76 | var clientServiceName = CleanServiceName(remoteUri.Host);
77 |
78 | var annotation = new Annotation
79 | {
80 | Host = serviceEndpoint,
81 | Value = ZipkinConstants.ClientSend
82 | };
83 |
84 | newSpan.Annotations.Add(annotation);
85 | AddBinaryAnnotation("http.path", remoteUri.AbsolutePath, newSpan, serviceEndpoint);
86 | AddBinaryAnnotation("sa", "1", newSpan, zipkinEndpoint.GetRemoteEndpoint(remoteUri, clientServiceName));
87 |
88 | return newSpan;
89 | }
90 |
91 | private string CleanServiceName(string host)
92 | {
93 | foreach (var domain in zipkinNotToBeDisplayedDomainList)
94 | {
95 | if (host.Contains(domain))
96 | {
97 | return host.Replace(domain, string.Empty);
98 | }
99 | }
100 |
101 | return host;
102 | }
103 |
104 | public virtual void ReceiveClientSpan(Span span, int statusCode)
105 | {
106 | if (span == null)
107 | {
108 | throw new ArgumentNullException(nameof(span));
109 | }
110 |
111 | if (span.Annotations == null || !span.Annotations.Any())
112 | {
113 | throw new ArgumentException("Invalid client span: Annotations list is invalid.");
114 | }
115 |
116 | var annotation = new Annotation()
117 | {
118 | Host = span.Annotations.First().Host,
119 | Value = ZipkinConstants.ClientReceive
120 | };
121 |
122 | span.Annotations.Add(annotation);
123 | AddBinaryAnnotation("http.status", statusCode.ToString(), span, span.Annotations.First().Host);
124 |
125 | spanCollector.Collect(span);
126 | }
127 |
128 | public virtual void Record(Span span, string value)
129 | {
130 | if (span == null)
131 | throw new ArgumentNullException(nameof(span), "In order to record an annotation, the span must be not null.");
132 |
133 | span.Annotations.Add(new Annotation()
134 | {
135 | Host = zipkinEndpoint.GetLocalEndpoint(serviceName, servicePort),
136 | Value = value
137 | });
138 | }
139 |
140 | public void RecordBinary(Span span, string key, T value)
141 | {
142 | if (span == null)
143 | throw new ArgumentNullException(nameof(span), "In order to record a binary annotation, the span must be not null.");
144 |
145 | span.Annotations.Add(new BinaryAnnotation()
146 | {
147 | Host = zipkinEndpoint.GetLocalEndpoint(serviceName, servicePort),
148 | Key = key,
149 | Value = value
150 | });
151 | }
152 |
153 | internal static Span CreateNewSpan(string spanName, string traceId, string parentSpanId, string spanId)
154 | {
155 | return new Span
156 | {
157 | Name = spanName,
158 | TraceId = traceId,
159 | ParentId = parentSpanId,
160 | Id = spanId
161 | };
162 | }
163 |
164 | private void AddBinaryAnnotation(string key, T value, Span span, Endpoint endpoint)
165 | {
166 | var binaryAnnotation = new BinaryAnnotation()
167 | {
168 | Host = endpoint,
169 | Key = key,
170 | Value = value
171 | };
172 |
173 | span.Annotations.Add(binaryAnnotation);
174 | }
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/src/Medidata.ZipkinTracer.Core/TraceProvider.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.Owin;
3 | using Medidata.ZipkinTracer.Core.Helpers;
4 |
5 | namespace Medidata.ZipkinTracer.Core
6 | {
7 | ///
8 | /// TraceProvider class
9 | ///
10 | internal class TraceProvider : ITraceProvider
11 | {
12 | public const string TraceIdHeaderName = "X-B3-TraceId";
13 | public const string SpanIdHeaderName = "X-B3-SpanId";
14 | public const string ParentSpanIdHeaderName = "X-B3-ParentSpanId";
15 | public const string SampledHeaderName = "X-B3-Sampled";
16 |
17 | ///
18 | /// Key name for context.Environment
19 | ///
20 | public const string Key = "Medidata.ZipkinTracer.Core.TraceProvider";
21 |
22 | ///
23 | /// Gets a TraceId
24 | ///
25 | public string TraceId { get; }
26 |
27 | ///
28 | /// Gets a SpanId
29 | ///
30 | public string SpanId { get; }
31 |
32 | ///
33 | /// Gets a ParentSpanId
34 | ///
35 | public string ParentSpanId { get; }
36 |
37 | ///
38 | /// Gets IsSampled
39 | ///
40 | public bool IsSampled { get; }
41 |
42 | // TODO: Make another constructor to accept System.Web.HttpContext for non Owin applications
43 | ///
44 | /// Initializes a new instance of the TraceProvider class.
45 | ///
46 | /// ZipkinConfig instance
47 | /// the IOwinContext
48 | internal TraceProvider(IZipkinConfig config, IOwinContext context = null)
49 | {
50 | string headerTraceId = null;
51 | string headerSpanId = null;
52 | string headerParentSpanId = null;
53 | string headerSampled = null;
54 | string requestPath = null;
55 |
56 | if (context != null)
57 | {
58 | object value;
59 | if (context.Environment.TryGetValue(Key, out value))
60 | {
61 | // set properties from context's item.
62 | var provider = (ITraceProvider)value;
63 | TraceId = provider.TraceId;
64 | SpanId = provider.SpanId;
65 | ParentSpanId = provider.ParentSpanId;
66 | IsSampled = provider.IsSampled;
67 | return;
68 | }
69 |
70 | // zipkin use the following X-Headers to propagate the trace information
71 | headerTraceId = context.Request.Headers[TraceIdHeaderName];
72 | headerSpanId = context.Request.Headers[SpanIdHeaderName];
73 | headerParentSpanId = context.Request.Headers[ParentSpanIdHeaderName];
74 | headerSampled = context.Request.Headers[SampledHeaderName];
75 |
76 | requestPath = context.Request.Path.ToString();
77 | }
78 |
79 | TraceId = headerTraceId.IsParsableTo128Or64Bit() ? headerTraceId : GenerateNewTraceId(config.Create128BitTraceId);
80 | SpanId = headerSpanId.IsParsableToLong() ? headerSpanId : GenerateHexEncodedInt64Id();
81 | ParentSpanId = headerParentSpanId.IsParsableToLong() ? headerParentSpanId : string.Empty;
82 | IsSampled = config.ShouldBeSampled(headerSampled, requestPath);
83 |
84 | if (SpanId == ParentSpanId)
85 | {
86 | throw new ArgumentException("x-b3-SpanId and x-b3-ParentSpanId must not be the same value.");
87 | }
88 |
89 | context?.Environment.Add(Key, this);
90 | }
91 |
92 | ///
93 | /// private constructor to accept property values
94 | ///
95 | ///
96 | ///
97 | ///
98 | ///
99 | internal TraceProvider(string traceId, string spanId, string parentSpanId, bool isSampled)
100 | {
101 | TraceId = traceId;
102 | SpanId = spanId;
103 | ParentSpanId = parentSpanId;
104 | IsSampled = isSampled;
105 | }
106 |
107 | ///
108 | /// Gets a Trace for outgoing HTTP request.
109 | ///
110 | /// The trace
111 | public ITraceProvider GetNext()
112 | {
113 | return new TraceProvider(
114 | TraceId,
115 | GenerateHexEncodedInt64Id(),
116 | SpanId,
117 | IsSampled);
118 | }
119 |
120 | ///
121 | /// Generate a traceId.
122 | ///
123 | /// true for 128bit, false for 64 bit
124 | ///
125 | private string GenerateNewTraceId(bool create128Bit)
126 | {
127 | if (create128Bit)
128 | return Guid.NewGuid().ToString("N");
129 | else
130 | return GenerateHexEncodedInt64Id();
131 | }
132 |
133 | ///
134 | /// Generate a hex encoded Int64 from new Guid.
135 | ///
136 | /// The hex encoded int64
137 | private string GenerateHexEncodedInt64Id()
138 | {
139 | return Convert.ToString(BitConverter.ToInt64(Guid.NewGuid().ToByteArray(), 0), 16);
140 | }
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/src/Medidata.ZipkinTracer.Core/ZipkinClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.CompilerServices;
3 | using Medidata.ZipkinTracer.Core.Logging;
4 | using Microsoft.Owin;
5 | using Medidata.ZipkinTracer.Models;
6 | using Medidata.ZipkinTracer.Core.Helpers;
7 |
8 | namespace Medidata.ZipkinTracer.Core
9 | {
10 | public class ZipkinClient: ITracerClient
11 | {
12 | internal SpanCollector spanCollector;
13 | internal SpanTracer spanTracer;
14 |
15 | private ILog logger;
16 |
17 | public bool IsTraceOn { get; set; }
18 |
19 | public ITraceProvider TraceProvider { get; }
20 |
21 | public IZipkinConfig ZipkinConfig { get; }
22 |
23 | private static SpanCollector instance;
24 | private static readonly object syncObj = new object();
25 |
26 | static SpanCollector GetInstance(Uri uri, uint maxProcessorBatchSize)
27 | {
28 | SyncHelper.ExecuteSafely(syncObj, () => instance == null,
29 | () =>
30 | {
31 | instance = new SpanCollector(uri, maxProcessorBatchSize);
32 | });
33 |
34 | return instance;
35 | }
36 |
37 | public ZipkinClient(IZipkinConfig zipkinConfig, IOwinContext context, SpanCollector collector = null)
38 | {
39 | if (zipkinConfig == null) throw new ArgumentNullException(nameof(zipkinConfig));
40 | if (context == null) throw new ArgumentNullException(nameof(context));
41 | var traceProvider = new TraceProvider(zipkinConfig, context);
42 | IsTraceOn = !zipkinConfig.Bypass(context.Request) && IsTraceProviderSamplingOn(traceProvider);
43 |
44 | if (!IsTraceOn)
45 | return;
46 |
47 | zipkinConfig.Validate();
48 | ZipkinConfig = zipkinConfig;
49 | this.logger = LogProvider.GetCurrentClassLogger();
50 |
51 | try
52 | {
53 | spanCollector = collector ?? GetInstance(
54 | zipkinConfig.ZipkinBaseUri,
55 | zipkinConfig.SpanProcessorBatchSize);
56 |
57 | spanCollector.Start();
58 |
59 | spanTracer = new SpanTracer(
60 | spanCollector,
61 | new ServiceEndpoint(),
62 | zipkinConfig.NotToBeDisplayedDomainList,
63 | zipkinConfig.Domain(context.Request));
64 |
65 | TraceProvider = traceProvider;
66 | }
67 | catch (Exception ex)
68 | {
69 | logger.Log(LogLevel.Error, () => "Error Building Zipkin Client Provider", ex);
70 | IsTraceOn = false;
71 | }
72 | }
73 |
74 | public Span StartClientTrace(Uri remoteUri, string methodName, ITraceProvider trace)
75 | {
76 | if (!IsTraceOn)
77 | return null;
78 |
79 | try
80 | {
81 | return spanTracer.SendClientSpan(
82 | methodName.ToLower(),
83 | trace.TraceId,
84 | trace.ParentSpanId,
85 | trace.SpanId,
86 | remoteUri);
87 | }
88 | catch (Exception ex)
89 | {
90 | logger.Log(LogLevel.Error, () => "Error Starting Client Trace", ex);
91 | return null;
92 | }
93 | }
94 |
95 | public void EndClientTrace(Span clientSpan, int statusCode)
96 | {
97 | if (!IsTraceOn)
98 | return;
99 |
100 | try
101 | {
102 | spanTracer.ReceiveClientSpan(clientSpan, statusCode);
103 | }
104 | catch (Exception ex)
105 | {
106 | logger.Log(LogLevel.Error, () => "Error Ending Client Trace", ex);
107 | }
108 | }
109 |
110 | public Span StartServerTrace(Uri requestUri, string methodName)
111 | {
112 | if (!IsTraceOn)
113 | return null;
114 |
115 | try
116 | {
117 | return spanTracer.ReceiveServerSpan(
118 | methodName.ToLower(),
119 | TraceProvider.TraceId,
120 | TraceProvider.ParentSpanId,
121 | TraceProvider.SpanId,
122 | requestUri);
123 | }
124 | catch (Exception ex)
125 | {
126 | logger.Log(LogLevel.Error, () => "Error Starting Server Trace", ex);
127 | return null;
128 | }
129 | }
130 |
131 | public void EndServerTrace(Span serverSpan)
132 | {
133 | if (!IsTraceOn)
134 | return;
135 |
136 | try
137 | {
138 | spanTracer.SendServerSpan(serverSpan);
139 | }
140 | catch (Exception ex)
141 | {
142 | logger.Log(LogLevel.Error, () => "Error Ending Server Trace", ex);
143 | }
144 | }
145 |
146 | ///
147 | /// Records an annotation with the current timestamp and the provided value in the span.
148 | ///
149 | /// The span where the annotation will be recorded.
150 | /// The value of the annotation to be recorded. If this parameter is omitted
151 | /// (or its value set to null), the method caller member name will be automatically passed.
152 | public void Record(Span span, [CallerMemberName] string value = null)
153 | {
154 | if (!IsTraceOn)
155 | return;
156 |
157 | try
158 | {
159 | spanTracer.Record(span, value);
160 | }
161 | catch (Exception ex)
162 | {
163 | logger.Log(LogLevel.Error, () => "Error recording the annotation", ex);
164 | }
165 | }
166 |
167 | ///
168 | /// Records a key-value pair as a binary annotiation in the span.
169 | ///
170 | /// The type of the value to be recorded. See remarks for the currently supported types.
171 | /// The span where the annotation will be recorded.
172 | /// The key which is a reference to the recorded value.
173 | /// The value of the annotation to be recorded.
174 | /// The RecordBinary will record a key-value pair which can be used to tag some additional information
175 | /// in the trace without any timestamps. The currently supported value types are ,
176 | /// , , , , and
177 | /// . Any other types will be passed as string annotation types.
178 | ///
179 | /// Please note, that although the values have types, they will be recorded and sent by calling their
180 | /// respective ToString() method.
181 | public void RecordBinary(Span span, string key, T value)
182 | {
183 | if (!IsTraceOn)
184 | return;
185 |
186 | try
187 | {
188 | spanTracer.RecordBinary(span, key, value);
189 | }
190 | catch (Exception ex)
191 | {
192 | logger.Log(LogLevel.Error, () => $"Error recording a binary annotation (key: {key})", ex);
193 | }
194 | }
195 |
196 | ///
197 | /// Records a local component annotation in the span.
198 | ///
199 | /// The span where the annotation will be recorded.
200 | /// The value of the local trace to be recorder.
201 | public void RecordLocalComponent(Span span, string value)
202 | {
203 | if (!IsTraceOn)
204 | return;
205 |
206 | try
207 | {
208 | spanTracer.RecordBinary(span, ZipkinConstants.LocalComponent, value);
209 | }
210 | catch (Exception ex)
211 | {
212 | logger.Log(LogLevel.Error, () => $"Error recording local trace (value: {value})", ex);
213 | }
214 | }
215 |
216 | public void ShutDown()
217 | {
218 | if (spanCollector != null)
219 | {
220 | spanCollector.Stop();
221 | }
222 | }
223 |
224 | private bool IsTraceProviderSamplingOn(ITraceProvider traceProvider)
225 | {
226 | return !string.IsNullOrEmpty(traceProvider.TraceId) && traceProvider.IsSampled;
227 | }
228 | }
229 | }
230 |
--------------------------------------------------------------------------------
/src/Medidata.ZipkinTracer.Core/ZipkinConfig.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using Microsoft.Owin;
5 |
6 | namespace Medidata.ZipkinTracer.Core
7 | {
8 | public class ZipkinConfig : IZipkinConfig
9 | {
10 | private Random random = new Random();
11 |
12 | public Predicate Bypass { get; set; } = r => false;
13 | public Uri ZipkinBaseUri { get; set; }
14 | public Func Domain { get; set; }
15 | public uint SpanProcessorBatchSize { get; set; }
16 | public IList ExcludedPathList { get; set; } = new List();
17 | public double SampleRate { get; set; }
18 | public IList NotToBeDisplayedDomainList { get; set; } = new List();
19 | public bool Create128BitTraceId { get; set; }
20 |
21 | public void Validate()
22 | {
23 | if (ZipkinBaseUri == null)
24 | {
25 | throw new ArgumentNullException("ZipkinBaseUri");
26 | }
27 |
28 | if (Domain == null)
29 | {
30 | Domain = request => new Uri(request.Uri.Host);
31 | }
32 |
33 | if (ExcludedPathList == null)
34 | {
35 | throw new ArgumentNullException("ExcludedPathList");
36 | }
37 |
38 | if (ExcludedPathList.Any(item => !item.StartsWith("/")))
39 | {
40 | throw new ArgumentException("Item of ExcludedPathList must start with '/'. e.g.) '/check_uri'");
41 | }
42 |
43 | if (SampleRate < 0 || SampleRate > 1)
44 | {
45 | throw new ArgumentException("SampleRate must range from 0 to 1.");
46 | }
47 |
48 | if (NotToBeDisplayedDomainList == null)
49 | {
50 | throw new ArgumentNullException("NotToBeDisplayedDomainList");
51 | }
52 | }
53 |
54 | ///
55 | /// Checks if sampled flag from headers has value if not decide if need to sample or not using sample rate
56 | ///
57 | ///
58 | ///
59 | ///
60 | public bool ShouldBeSampled(string sampledFlag, string requestPath)
61 | {
62 | bool result;
63 | if (TryParseSampledFlagToBool(sampledFlag, out result)) return result;
64 |
65 | if (IsInDontSampleList(requestPath)) return false;
66 |
67 | return random.NextDouble() <= SampleRate;
68 | }
69 |
70 | internal bool IsInDontSampleList(string path)
71 | {
72 | if (path != null)
73 | {
74 | if (ExcludedPathList.Any(uri => path.StartsWith(uri, StringComparison.InvariantCultureIgnoreCase)))
75 | {
76 | return true;
77 | }
78 | }
79 | return false;
80 | }
81 |
82 | ///
83 | /// Try parse sampledFlag to bool
84 | ///
85 | ///
86 | ///
87 | /// returns true if sampledFlag can be parsed to bool
88 | private bool TryParseSampledFlagToBool(string sampledFlag, out bool booleanValue)
89 | {
90 | booleanValue = false;
91 |
92 | if (string.IsNullOrWhiteSpace(sampledFlag)) return false;
93 |
94 | switch (sampledFlag)
95 | {
96 | case "0":
97 | booleanValue = false;
98 | return true;
99 | case "1":
100 | booleanValue = true;
101 | return true;
102 | default:
103 | return bool.TryParse(sampledFlag, out booleanValue);
104 | }
105 | }
106 | }
107 | }
--------------------------------------------------------------------------------
/src/Medidata.ZipkinTracer.Core/ZipkinTracerExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using Medidata.ZipkinTracer.Models;
7 |
8 | namespace Medidata.ZipkinTracer
9 | {
10 | public static class ZipkinTracerExtensions
11 | {
12 | private static readonly Dictionary annotationTypeMappings =
13 | new Dictionary()
14 | {
15 | { typeof(bool), AnnotationType.Boolean },
16 | { typeof(byte[]), AnnotationType.ByteArray },
17 | { typeof(short), AnnotationType.Int16 },
18 | { typeof(int), AnnotationType.Int32 },
19 | { typeof(long), AnnotationType.Int64 },
20 | { typeof(double), AnnotationType.Double },
21 | { typeof(string), AnnotationType.String }
22 | };
23 |
24 | public static AnnotationType AsAnnotationType(this Type type)
25 | {
26 | return annotationTypeMappings.ContainsKey(type) ? annotationTypeMappings[type] : AnnotationType.String;
27 | }
28 |
29 | public static IEnumerable GetAnnotationsByType(this Span span)
30 | where TAnnotation: AnnotationBase
31 | {
32 | return span.Annotations.OfType();
33 | }
34 |
35 | public static long ToUnixTimeMicroseconds(this DateTimeOffset value)
36 | {
37 | return Convert.ToInt64(
38 | (value - new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero)).TotalMilliseconds * 1000
39 | );
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Medidata.ZipkinTracer.Core/package-nuget.bat:
--------------------------------------------------------------------------------
1 | "C:\Program Files (x86)\MSBuild\14.0\Bin\MSBuild.exe" "Medidata.ZipkinTracer.Core.csproj" /p:Configuration=Release /t:Clean
2 | "..\..\.nuget\nuget.exe" pack Medidata.ZipkinTracer.Core.csproj -Build -MSBuildVersion 14 -Properties Configuration=Release
--------------------------------------------------------------------------------
/src/Medidata.ZipkinTracer.Core/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/tests/Medidata.ZipkinTracer.Core.Test/Helpers/ParserHelperTests.cs:
--------------------------------------------------------------------------------
1 | using Medidata.ZipkinTracer.Core.Helpers;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 | using System;
4 |
5 | namespace Medidata.ZipkinTracer.Core.Test.Helpers
6 | {
7 | [TestClass]
8 | public class ParserHelperTests
9 | {
10 | #region IsParsableTo128Or64Bit
11 |
12 | [TestMethod]
13 | public void IsParsableTo128Or64Bit_ValidLongHexStringRepresentation()
14 | {
15 | // Arrange
16 | string value = long.MaxValue.ToString("x");
17 |
18 | // Act
19 | var result = ParserHelper.IsParsableTo128Or64Bit(value);
20 |
21 | // Assert
22 | Assert.IsTrue(result);
23 | }
24 |
25 | [TestMethod]
26 | public void IsParsableTo128Or64Bit_ValidGuidStringRepresentation()
27 | {
28 | // Arrange
29 | string value = Guid.NewGuid().ToString("N");
30 |
31 | // Act
32 | var result = ParserHelper.IsParsableTo128Or64Bit(value);
33 |
34 | // Assert
35 | Assert.IsTrue(result);
36 | }
37 |
38 | [TestMethod]
39 | public void IsParsableTo128Or64Bit_InvalidLongHexStringRepresentation()
40 | {
41 | // Arrange
42 | var longStringRepresentation = long.MaxValue.ToString("x");
43 | string value = longStringRepresentation.Substring(1) + "k" ;
44 |
45 | // Act
46 | var result = ParserHelper.IsParsableTo128Or64Bit(value);
47 |
48 | // Assert
49 | Assert.IsFalse(result);
50 | }
51 |
52 | #endregion
53 |
54 | #region IsParsableToGuid
55 |
56 | [TestMethod]
57 | public void IsParsableToGuid_Null()
58 | {
59 | // Arrange
60 | string value = null;
61 |
62 | // Act
63 | var result = ParserHelper.IsParsableToGuid(value);
64 |
65 | // Assert
66 | Assert.IsFalse(result);
67 | }
68 |
69 | [TestMethod]
70 | public void IsParsableToGuid_WhiteSpace()
71 | {
72 | // Arrange
73 | string value = string.Empty;
74 |
75 | // Act
76 | var result = ParserHelper.IsParsableToGuid(value);
77 |
78 | // Assert
79 | Assert.IsFalse(result);
80 | }
81 |
82 | [TestMethod]
83 | public void IsParsableToGuid_MoreThan32Characters()
84 | {
85 | // Arrange
86 | string value = Guid.NewGuid().ToString("N") + "a";
87 |
88 | // Act
89 | var result = ParserHelper.IsParsableToGuid(value);
90 |
91 | // Assert
92 | Assert.IsFalse(result);
93 | }
94 |
95 | [TestMethod]
96 | public void IsParsableToGuid_LessThan32Characters()
97 | {
98 | // Arrange
99 | string value = Guid.NewGuid().ToString("N").Substring(1);
100 |
101 | // Act
102 | var result = ParserHelper.IsParsableToGuid(value);
103 |
104 | // Assert
105 | Assert.IsFalse(result);
106 | }
107 |
108 | [TestMethod]
109 | public void IsParsableToGuid_32CharactersWithInvalidCharacter()
110 | {
111 | // Arrange
112 | string value = Guid.NewGuid().ToString("N").Substring(1) + "x";
113 |
114 | // Act
115 | var result = ParserHelper.IsParsableToGuid(value);
116 |
117 | // Assert
118 | Assert.IsFalse(result);
119 | }
120 |
121 | [TestMethod]
122 | public void IsParsableToGuid_32Characters()
123 | {
124 | // Arrange
125 | string value = Guid.NewGuid().ToString("N");
126 |
127 | // Act
128 | var result = ParserHelper.IsParsableToGuid(value);
129 |
130 | // Assert
131 | Assert.IsTrue(result);
132 | }
133 |
134 | [TestMethod]
135 | public void IsParsableToGuid_GuidWithHyphens()
136 | {
137 | // Arrange
138 | string value = Guid.NewGuid().ToString();
139 |
140 | // Act
141 | var result = ParserHelper.IsParsableToGuid(value);
142 |
143 | // Assert
144 | Assert.IsFalse(result);
145 | }
146 |
147 | #endregion
148 |
149 | #region IsParsableToLong
150 |
151 | [TestMethod]
152 | public void IsParsableToLong_Null()
153 | {
154 | // Arrange
155 | string value = null;
156 |
157 | // Act
158 | var result = ParserHelper.IsParsableToLong(value);
159 |
160 | // Assert
161 | Assert.IsFalse(result);
162 | }
163 |
164 | [TestMethod]
165 | public void IsParsableToLong_WhiteSpace()
166 | {
167 | // Arrange
168 | string value = string.Empty;
169 |
170 | // Act
171 | var result = ParserHelper.IsParsableToLong(value);
172 |
173 | // Assert
174 | Assert.IsFalse(result);
175 | }
176 |
177 | [TestMethod]
178 | public void IsParsableToLong_MinLong()
179 | {
180 | // Arrange
181 | var longValue = long.MinValue;
182 | string value = longValue.ToString("x");
183 |
184 | // Act
185 | var result = ParserHelper.IsParsableToLong(value);
186 |
187 | // Assert
188 | Assert.IsTrue(result);
189 | }
190 |
191 | [TestMethod]
192 | public void IsParsableToLong_MaxLong()
193 | {
194 | // Arrange
195 | var longValue = long.MaxValue;
196 | string value = longValue.ToString("x4");
197 |
198 | // Act
199 | var result = ParserHelper.IsParsableToLong(value);
200 |
201 | // Assert
202 | Assert.IsTrue(result);
203 | }
204 |
205 | [TestMethod]
206 | public void IsParsableToLong_MoreThan16Characters()
207 | {
208 | // Arrange
209 | var longValue = long.MaxValue;
210 | string value = longValue.ToString("x") + "a";
211 |
212 | // Act
213 | var result = ParserHelper.IsParsableToLong(value);
214 |
215 | // Assert
216 | Assert.IsFalse(result);
217 | }
218 |
219 | [TestMethod]
220 | public void IsParsableToLong_HasInvalidCharacter()
221 | {
222 | // Arrange
223 | var longValue = long.MaxValue;
224 | string value = longValue.ToString("x4").Remove(15) + "x";
225 |
226 | // Act
227 | var result = ParserHelper.IsParsableToLong(value);
228 |
229 | // Assert
230 | Assert.IsFalse(result);
231 | }
232 |
233 | #endregion
234 | }
235 | }
--------------------------------------------------------------------------------
/tests/Medidata.ZipkinTracer.Core.Test/HttpModule/RequestContextModuleTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 | using Ploeh.AutoFixture;
4 | using Medidata.ZipkinTracer.Core.HttpModule;
5 | using Rhino.Mocks;
6 | using System.Diagnostics;
7 | using Medidata.ZipkinTracer.Core.Logging;
8 | using System.Collections.Generic;
9 |
10 | namespace Medidata.ZipkinTracer.Core.Test.HttpModule
11 | {
12 | [TestClass]
13 | public class RequestContextModuleTests
14 | {
15 | private Fixture fixture;
16 | private IZipkinClient zipkinClient;
17 | private ZipkinRequestContextModule requestContextModule;
18 | private IMDLogger logger;
19 |
20 | [TestInitialize]
21 | public void Init()
22 | {
23 | fixture = new Fixture();
24 |
25 | zipkinClient = MockRepository.GenerateStub();
26 | logger = MockRepository.GenerateStub();
27 |
28 | requestContextModule = new ZipkinRequestContextModule();
29 | requestContextModule.logger = logger;
30 | }
31 |
32 | [TestMethod]
33 | public void StartZipkinSpan()
34 | {
35 | var url = fixture.Create();
36 | var traceId = fixture.Create();
37 | var parentSpanId = fixture.Create();
38 | var spanId = fixture.Create();
39 |
40 | var expectedSpan = new Span();
41 |
42 | zipkinClient.Expect(x => x.StartServerSpan(url, traceId, parentSpanId, spanId)).Return(expectedSpan);
43 |
44 | var resultSpan = requestContextModule.StartZipkinSpan(zipkinClient, url, traceId, parentSpanId, spanId);
45 | Assert.AreEqual(expectedSpan, resultSpan);
46 | }
47 |
48 | [TestMethod]
49 | public void StartZipkinSpan_EmptyTraceId()
50 | {
51 | var url = fixture.Create();
52 | string traceId = string.Empty;
53 | var parentSpanId = fixture.Create();
54 | var spanId = fixture.Create();
55 |
56 | var resultSpan = requestContextModule.StartZipkinSpan(zipkinClient,url, traceId, parentSpanId, spanId);
57 |
58 | zipkinClient.AssertWasNotCalled(x => x.StartServerSpan(Arg.Is.Anything, Arg.Is.Anything, Arg.Is.Anything, Arg.Is.Anything));
59 | logger.AssertWasCalled(x => x.Event(Arg.Is.Anything, Arg>.Is.Null, Arg