├── .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.Is.Null), options => options.Repeat.Twice()); 60 | } 61 | 62 | [TestMethod] 63 | public void StartZipkinSpan_EmptySpanId() 64 | { 65 | var url = fixture.Create(); 66 | string traceId = fixture.Create(); 67 | var parentSpanId = fixture.Create(); 68 | var spanId = String.Empty; 69 | 70 | var resultSpan = requestContextModule.StartZipkinSpan(zipkinClient,url, traceId, parentSpanId, spanId); 71 | 72 | zipkinClient.AssertWasNotCalled(x => x.StartServerSpan(Arg.Is.Anything, Arg.Is.Anything, Arg.Is.Anything, Arg.Is.Anything)); 73 | logger.AssertWasCalled(x => x.Event(Arg.Is.Anything, Arg>.Is.Null, Arg.Is.Null), options => options.Repeat.Twice()); 74 | } 75 | 76 | [TestMethod] 77 | public void StartZipkinSpan_exception() 78 | { 79 | var url = fixture.Create(); 80 | var traceId = fixture.Create(); 81 | var parentSpanId = fixture.Create(); 82 | var spanId = fixture.Create(); 83 | 84 | var exception = new Exception(); 85 | 86 | zipkinClient.Expect(x => x.StartServerSpan(Arg.Is.Anything, Arg.Is.Anything, Arg.Is.Anything, Arg.Is.Anything)).Throw(exception); 87 | 88 | var resultSpan = requestContextModule.StartZipkinSpan(zipkinClient,url, traceId, parentSpanId, spanId); 89 | Assert.IsNull(resultSpan); 90 | 91 | logger.AssertWasCalled(x => x.Event(Arg.Is.Anything, Arg>.Is.Null, Arg.Is.Null, Arg.Is.Equal(exception))); 92 | } 93 | 94 | [TestMethod] 95 | public void EndZipkinSpan() 96 | { 97 | var span = new Span(); 98 | var stopWatch = new Stopwatch(); 99 | 100 | requestContextModule.EndZipkinSpan(zipkinClient,stopWatch, span); 101 | 102 | zipkinClient.AssertWasCalled(x => x.EndServerSpan(Arg.Is.Equal(span), Arg.Is.Anything)); 103 | } 104 | 105 | [TestMethod] 106 | public void EndZipkinSpan_Exception() 107 | { 108 | var span = new Span(); 109 | var stopWatch = new Stopwatch(); 110 | 111 | var exception = new Exception(); 112 | 113 | zipkinClient.Expect(x => x.EndServerSpan(Arg.Is.Anything, Arg.Is.Anything)).Throw(exception); 114 | 115 | requestContextModule.EndZipkinSpan(zipkinClient,stopWatch, span); 116 | 117 | logger.AssertWasCalled(x => x.Event(Arg.Is.Anything, Arg>.Is.Null, Arg.Is.Null, Arg.Is.Equal(exception))); 118 | } 119 | 120 | [TestMethod] 121 | public void EndZipkinSpan_NullSpan() 122 | { 123 | Span span = null; 124 | var stopWatch = new Stopwatch(); 125 | 126 | requestContextModule.EndZipkinSpan(zipkinClient,stopWatch, span); 127 | 128 | zipkinClient.AssertWasNotCalled(x => x.EndServerSpan(Arg.Is.Anything, Arg.Is.Anything)); 129 | logger.AssertWasNotCalled(x => x.Event(Arg.Is.Anything, Arg>.Is.Null, Arg.Is.Null, Arg.Is.Anything)); 130 | } 131 | 132 | [TestMethod] 133 | public void EndZipkinSpan_NullZipkinClient() 134 | { 135 | Span span = new Span(); 136 | var stopWatch = new Stopwatch(); 137 | 138 | requestContextModule.EndZipkinSpan(null,stopWatch, span); 139 | 140 | zipkinClient.AssertWasNotCalled(x => x.EndServerSpan(Arg.Is.Anything, Arg.Is.Anything)); 141 | logger.AssertWasNotCalled(x => x.Event(Arg.Is.Anything, Arg>.Is.Null, Arg.Is.Null, Arg.Is.Anything)); 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /tests/Medidata.ZipkinTracer.Core.Test/Medidata.ZipkinTracer.Core.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {0158C8BD-D881-4EE8-A8C6-54707A67A00D} 8 | Library 9 | Properties 10 | Medidata.ZipkinTracer.Core.Test 11 | Medidata.ZipkinTracer.Core.Test 12 | v4.5 13 | 512 14 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 15 | 10.0 16 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 17 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 18 | False 19 | UnitTest 20 | .\ 21 | true 22 | 23 | 24 | 25 | 26 | true 27 | full 28 | false 29 | bin\Debug\ 30 | DEBUG;TRACE 31 | prompt 32 | 4 33 | 34 | 35 | pdbonly 36 | true 37 | bin\Release\ 38 | TRACE 39 | prompt 40 | 4 41 | 42 | 43 | 44 | 45 | ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll 46 | True 47 | 48 | 49 | ..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll 50 | True 51 | 52 | 53 | ..\..\packages\Owin.1.0\lib\net40\Owin.dll 54 | True 55 | 56 | 57 | ..\..\packages\AutoFixture.3.50.2\lib\net40\Ploeh.AutoFixture.dll 58 | True 59 | 60 | 61 | ..\..\packages\AutoFixture.AutoRhinoMocks.3.50.2\lib\net40\Ploeh.AutoFixture.AutoRhinoMock.dll 62 | True 63 | 64 | 65 | False 66 | ..\..\packages\RhinoMocks.3.6.1\lib\net\Rhino.Mocks.dll 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 | 95 | 96 | 97 | 98 | 99 | {eb432891-204b-4b97-bfb1-a820e424284e} 100 | Medidata.ZipkinTracer.Core 101 | 102 | 103 | 104 | 105 | 106 | Designer 107 | 108 | 109 | 110 | 111 | 112 | 113 | False 114 | 115 | 116 | False 117 | 118 | 119 | False 120 | 121 | 122 | False 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 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}. 132 | 133 | 134 | 135 | 142 | -------------------------------------------------------------------------------- /tests/Medidata.ZipkinTracer.Core.Test/Models/Serialization/Json/JsonSpanTests.cs: -------------------------------------------------------------------------------- 1 | using Medidata.ZipkinTracer.Models; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using Ploeh.AutoFixture; 4 | using System.Linq; 5 | 6 | namespace Medidata.ZipkinTracer.Core.Test.Models.Serialization.Json 7 | { 8 | [TestClass] 9 | public class JsonSpanTests 10 | { 11 | private IFixture fixture; 12 | 13 | [TestInitialize] 14 | public void Init() 15 | { 16 | fixture = new Fixture(); 17 | } 18 | 19 | [TestMethod] 20 | public void JsonSpan() 21 | { 22 | // Arrange 23 | var span = new Span 24 | { 25 | Id = fixture.Create(), 26 | Name = fixture.Create(), 27 | ParentId = fixture.Create(), 28 | TraceId = fixture.Create(), 29 | }; 30 | span.Annotations.Add(fixture.Create()); 31 | span.Annotations.Add(fixture.Create()); 32 | span.Annotations.Add(fixture.Create()); 33 | span.Annotations.Add(fixture.Create()); 34 | 35 | // Act 36 | var result = new JsonSpan(span); 37 | 38 | // Assert 39 | Assert.AreEqual(span.TraceId, result.TraceId); 40 | Assert.AreEqual(span.Name, result.Name); 41 | Assert.AreEqual(span.Id, result.Id); 42 | Assert.AreEqual(span.ParentId, result.ParentId); 43 | Assert.AreEqual(2, result.Annotations.Count()); 44 | Assert.AreEqual(2, result.BinaryAnnotations.Count()); 45 | } 46 | 47 | [TestMethod] 48 | public void JsonSpan_ParentIdIsWhiteSpace() 49 | { 50 | // Arrange 51 | var span = new Span 52 | { 53 | ParentId = string.Empty 54 | }; 55 | 56 | // Act 57 | var result = new JsonSpan(span); 58 | 59 | // Assert 60 | Assert.IsNull(result.ParentId); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /tests/Medidata.ZipkinTracer.Core.Test/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.Test")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Medidata Solutions Inc")] 12 | [assembly: AssemblyProduct("Medidata.ZipkinTracer.Core.Test")] 13 | [assembly: AssemblyCopyright("Copyright © Medidata Solutions Inc 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("78961bfd-538b-4a56-8d2d-7cd6b7e6e65d")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /tests/Medidata.ZipkinTracer.Core.Test/SpanCollectorTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using Medidata.ZipkinTracer.Core.Logging; 4 | using Medidata.ZipkinTracer.Models; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | using Ploeh.AutoFixture; 7 | using Rhino.Mocks; 8 | 9 | namespace Medidata.ZipkinTracer.Core.Test 10 | { 11 | [TestClass] 12 | public class SpanCollectorTests 13 | { 14 | IFixture fixture; 15 | private SpanCollector spanCollector; 16 | private SpanProcessor spanProcessorStub; 17 | private ILog logger; 18 | 19 | [TestInitialize] 20 | public void Init() 21 | { 22 | fixture = new Fixture(); 23 | logger = MockRepository.GenerateStub(); 24 | } 25 | 26 | [TestMethod] 27 | public void CTOR_initializesSpanCollector() 28 | { 29 | SpanCollector.spanQueue = null; 30 | 31 | spanCollector = new SpanCollector(new Uri("http://localhost"), 0); 32 | 33 | Assert.IsNotNull(SpanCollector.spanQueue); 34 | } 35 | 36 | [TestMethod] 37 | public void CTOR_doesntReinitializeSpanCollector() 38 | { 39 | var spanQueue = new BlockingCollection(); 40 | SpanCollector.spanQueue = spanQueue; 41 | 42 | spanCollector = new SpanCollector(new Uri("http://localhost"), 0); 43 | 44 | Assert.IsTrue(System.Object.ReferenceEquals(SpanCollector.spanQueue, spanQueue)); 45 | } 46 | 47 | [TestMethod] 48 | public void CollectSpans() 49 | { 50 | SetupSpanCollector(); 51 | 52 | var testSpanId = fixture.Create(); 53 | var testTraceId = fixture.Create(); 54 | var testParentSpanId = fixture.Create(); 55 | var testName = fixture.Create(); 56 | 57 | Span span = new Span(); 58 | span.Id = testSpanId; 59 | span.TraceId = testTraceId; 60 | span.ParentId = testParentSpanId; 61 | span.Name = testName; 62 | 63 | spanCollector.Collect(span); 64 | 65 | Assert.AreEqual(1, SpanCollector.spanQueue.Count); 66 | 67 | Span queuedSpan; 68 | var spanInQueue = SpanCollector.spanQueue.TryTake(out queuedSpan); 69 | 70 | Assert.AreEqual(span, queuedSpan); 71 | } 72 | 73 | [TestMethod] 74 | public void StartProcessingSpans() 75 | { 76 | SetupSpanCollector(); 77 | 78 | spanCollector.Start(); 79 | 80 | spanProcessorStub.AssertWasCalled(x => x.Start()); 81 | Assert.IsTrue(spanCollector.IsStarted); 82 | } 83 | 84 | [TestMethod] 85 | public void StopProcessingSpansWithoutStartFirst() 86 | { 87 | SetupSpanCollector(); 88 | 89 | spanCollector.Stop(); 90 | 91 | spanProcessorStub.AssertWasNotCalled(x => x.Stop()); 92 | Assert.IsFalse(spanCollector.IsStarted); 93 | } 94 | 95 | [TestMethod] 96 | public void StopProcessingSpans() 97 | { 98 | SetupSpanCollector(); 99 | 100 | spanCollector.Start(); 101 | spanCollector.Stop(); 102 | 103 | spanProcessorStub.AssertWasCalled(x => x.Stop()); 104 | Assert.IsFalse(spanCollector.IsStarted); 105 | } 106 | 107 | private void SetupSpanCollector() 108 | { 109 | spanCollector = new SpanCollector(new Uri("http://localhost"), 0); 110 | 111 | SpanCollector.spanQueue = fixture.Create>(); 112 | spanProcessorStub = MockRepository.GenerateStub(new Uri("http://localhost"), 113 | SpanCollector.spanQueue, (uint)0); 114 | spanCollector.spanProcessor = spanProcessorStub; 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /tests/Medidata.ZipkinTracer.Core.Test/SpanProcessorTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using Medidata.ZipkinTracer.Core.Logging; 4 | using Medidata.ZipkinTracer.Models; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | using Ploeh.AutoFixture; 7 | using Rhino.Mocks; 8 | using Rhino.Mocks.Interfaces; 9 | 10 | namespace Medidata.ZipkinTracer.Core.Collector.Test 11 | { 12 | [TestClass] 13 | public class SpanProcessorTests 14 | { 15 | private IFixture fixture; 16 | private SpanProcessor spanProcessor; 17 | private SpanProcessorTaskFactory taskFactory; 18 | private BlockingCollection queue; 19 | private uint testMaxBatchSize; 20 | private ILog logger; 21 | 22 | [TestInitialize] 23 | public void Init() 24 | { 25 | fixture = new Fixture(); 26 | logger = MockRepository.GenerateStub(); 27 | queue = new BlockingCollection(); 28 | testMaxBatchSize = 10; 29 | spanProcessor = MockRepository.GenerateStub(new Uri("http://localhost"), queue, testMaxBatchSize); 30 | spanProcessor.Stub(x => x.SendSpansToZipkin(Arg.Is.Anything)).WhenCalled(s => { }); 31 | taskFactory = MockRepository.GenerateStub(logger, null); 32 | spanProcessor.spanProcessorTaskFactory = taskFactory; 33 | } 34 | 35 | [TestMethod] 36 | [ExpectedException(typeof(ArgumentNullException))] 37 | public void CTOR_WithNullSpanQueue() 38 | { 39 | new SpanProcessor(new Uri("http://localhost"), null, fixture.Create()); 40 | } 41 | 42 | [TestMethod] 43 | [ExpectedException(typeof(ArgumentNullException))] 44 | public void CTOR_WithNullZipkinServer() 45 | { 46 | new SpanProcessor(null, queue, fixture.Create()); 47 | } 48 | 49 | [TestMethod] 50 | public void Start() 51 | { 52 | spanProcessor.Start(); 53 | taskFactory.Expect(x => x.CreateAndStart(Arg.Matches(y => ValidateStartAction(y, spanProcessor)))); 54 | } 55 | 56 | [TestMethod] 57 | public void Stop() 58 | { 59 | spanProcessor.Stub(x => x.Stop()).CallOriginalMethod(OriginalCallOptions.NoExpectation); 60 | spanProcessor.Stop(); 61 | taskFactory.AssertWasCalled(x => x.StopTask()); 62 | } 63 | 64 | [TestMethod] 65 | public void Stop_RemainingGetLoggedIfCancelled() 66 | { 67 | spanProcessor.Stub(x => x.Stop()).CallOriginalMethod(OriginalCallOptions.NoExpectation); 68 | taskFactory.Expect(x => x.IsTaskCancelled()).Return(true); 69 | 70 | spanProcessor.spanQueue.Add(GenerateNewSpan()); 71 | spanProcessor.Stop(); 72 | 73 | spanProcessor.AssertWasCalled(s => s.SendSpansToZipkin(Arg.Is.Anything)); 74 | } 75 | 76 | [TestMethod] 77 | public void LogSubmittedSpans_DoNotIncrementSubsequentPollCountIfSpanQueueIsEmpty() 78 | { 79 | spanProcessor.LogSubmittedSpans(); 80 | Assert.AreEqual(0, spanProcessor.subsequentPollCount); 81 | } 82 | 83 | [TestMethod] 84 | public void LogSubmittedSpans_IncrementSubsequentPollCountIfSpanQueueHasAnItemLessThanMax() 85 | { 86 | //put item in queue 87 | spanProcessor.spanQueue.Add(GenerateNewSpan()); 88 | spanProcessor.LogSubmittedSpans(); 89 | 90 | //Proces Log with no new items 91 | spanProcessor.LogSubmittedSpans(); 92 | 93 | //Subsquent count has incremented 94 | Assert.AreEqual(1, spanProcessor.subsequentPollCount); 95 | } 96 | 97 | [TestMethod] 98 | public void LogSubmittedSpans_WhenQueueIsSubsequentlyLessThanTheMaxBatchCountMaxTimes() 99 | { 100 | spanProcessor.spanQueue.Add(GenerateNewSpan()); 101 | spanProcessor.LogSubmittedSpans(); 102 | spanProcessor.subsequentPollCount = SpanProcessor.MAX_NUMBER_OF_POLLS + 1; 103 | spanProcessor.LogSubmittedSpans(); 104 | 105 | spanProcessor.AssertWasCalled(s => s.SendSpansToZipkin(Arg.Is.Anything)); 106 | } 107 | 108 | [TestMethod] 109 | public void LogSubmittedSpans_WhenLogEntriesReachMaxBatchSize() 110 | { 111 | AddLogEntriesToMaxBatchSize(); 112 | spanProcessor.LogSubmittedSpans(); 113 | spanProcessor.AssertWasCalled( s=> s.SendSpansToZipkin(Arg.Is.Anything)); 114 | } 115 | 116 | private bool ValidateStartAction(Action y, SpanProcessor spanProcessor) 117 | { 118 | Assert.AreEqual(() => spanProcessor.LogSubmittedSpans(), y); 119 | return true; 120 | } 121 | 122 | private void AddLogEntriesToMaxBatchSize() 123 | { 124 | for (int i = 0; i < testMaxBatchSize + 1; i++) 125 | { 126 | spanProcessor.spanQueue.Add(GenerateNewSpan()); 127 | } 128 | } 129 | 130 | private Span GenerateNewSpan() 131 | { 132 | return new Span 133 | { 134 | Id = fixture.Create(), 135 | Name = fixture.Create(), 136 | ParentId = fixture.Create(), 137 | TraceId = fixture.Create(), 138 | }; 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /tests/Medidata.ZipkinTracer.Core.Test/SpanProcesssorTaskFactoryTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using Medidata.ZipkinTracer.Core.Logging; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using Rhino.Mocks; 6 | 7 | namespace Medidata.ZipkinTracer.Core.Collector.Test 8 | { 9 | [TestClass] 10 | public class SpanProcesssorTaskFactoryTests 11 | { 12 | private SpanProcessorTaskFactory spanProcessorTaskFactory; 13 | private CancellationTokenSource cancellationTokenSource; 14 | private bool actionCalled; 15 | private ILog logger; 16 | 17 | [TestInitialize] 18 | public void Init() 19 | { 20 | logger = MockRepository.GenerateStub(); 21 | cancellationTokenSource = new CancellationTokenSource(); 22 | spanProcessorTaskFactory = new SpanProcessorTaskFactory(logger, cancellationTokenSource); 23 | actionCalled = false; 24 | } 25 | 26 | [TestMethod] 27 | public void StopTask() 28 | { 29 | Assert.IsFalse(cancellationTokenSource.IsCancellationRequested); 30 | 31 | spanProcessorTaskFactory.StopTask(); 32 | 33 | Assert.IsTrue(cancellationTokenSource.IsCancellationRequested); 34 | } 35 | 36 | [TestMethod] 37 | public void IsTaskCancelled() 38 | { 39 | Assert.IsFalse(cancellationTokenSource.IsCancellationRequested); 40 | Assert.IsFalse(spanProcessorTaskFactory.IsTaskCancelled()); 41 | 42 | cancellationTokenSource.Cancel(); 43 | Assert.IsTrue(cancellationTokenSource.IsCancellationRequested); 44 | Assert.IsTrue(spanProcessorTaskFactory.IsTaskCancelled()); 45 | } 46 | 47 | [TestMethod] 48 | public void ActionWrapper() 49 | { 50 | var myAction = new Action(() => { actionCalled = true; }); 51 | Assert.IsFalse(actionCalled); 52 | 53 | spanProcessorTaskFactory.ActionWrapper(myAction); 54 | Assert.IsTrue(actionCalled); 55 | 56 | cancellationTokenSource.Cancel(); 57 | } 58 | 59 | [TestMethod] 60 | public void ActionWrapper_Exception() 61 | { 62 | Exception ex = new Exception("Exception!"); 63 | bool logErrorCalled = false; 64 | logger.Stub(x => x.Log(Arg.Is.Equal(LogLevel.Error), Arg>.Is.Null, Arg.Is.Null, Arg.Is.Null)).Return(true); 65 | logger.Stub(x => x.ErrorException("Error in SpanProcessorTask", ex)) 66 | .WhenCalled(x => { logErrorCalled = true; }).Return(true); 67 | var myAction = new Action(() => { actionCalled = true; throw ex; }); 68 | Assert.IsFalse(actionCalled); 69 | 70 | spanProcessorTaskFactory.ActionWrapper(myAction); 71 | Assert.IsTrue(actionCalled); 72 | Assert.IsTrue(logErrorCalled); 73 | 74 | cancellationTokenSource.Cancel(); 75 | } 76 | 77 | [TestMethod] 78 | public void ActionWrapper_NotCalledIfCancelled() 79 | { 80 | var myAction = new Action(() => { actionCalled = true; }); 81 | Assert.IsFalse(actionCalled); 82 | 83 | cancellationTokenSource.Cancel(); 84 | spanProcessorTaskFactory.ActionWrapper(myAction); 85 | Assert.IsFalse(actionCalled); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /tests/Medidata.ZipkinTracer.Core.Test/SpanTracerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Medidata.ZipkinTracer.Core.Logging; 5 | using Medidata.ZipkinTracer.Models; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | using Ploeh.AutoFixture; 8 | using Rhino.Mocks; 9 | 10 | namespace Medidata.ZipkinTracer.Core.Test 11 | { 12 | [TestClass] 13 | public class SpanTracerTests 14 | { 15 | private IFixture fixture; 16 | private SpanCollector spanCollectorStub; 17 | private ServiceEndpoint zipkinEndpointStub; 18 | private ILog logger; 19 | private IEnumerable zipkinNotToBeDisplayedDomainList; 20 | private string serverServiceName; 21 | private string clientServiceName; 22 | private ushort port; 23 | private string api; 24 | 25 | [TestInitialize] 26 | public void Init() 27 | { 28 | fixture = new Fixture(); 29 | logger = MockRepository.GenerateStub(); 30 | spanCollectorStub = MockRepository.GenerateStub(new Uri("http://localhost"), (uint)0); 31 | zipkinEndpointStub = MockRepository.GenerateStub(); 32 | zipkinNotToBeDisplayedDomainList = new List {".xyz.net"}; 33 | serverServiceName = "xyz-sandbox"; 34 | clientServiceName = "abc-sandbox"; 35 | port = 42; 36 | api = "/api/method1"; 37 | } 38 | 39 | [TestMethod] 40 | public void CreateNewSpan() 41 | { 42 | var spanName = fixture.Create(); 43 | var traceId = Guid.NewGuid().ToString("N"); 44 | var parentSpanId = fixture.Create().ToString("x"); 45 | var spanId = fixture.Create().ToString("x"); 46 | 47 | var resultSpan = SpanTracer.CreateNewSpan(spanName, traceId, parentSpanId, spanId); 48 | 49 | Assert.AreEqual(spanName, resultSpan.Name); 50 | Assert.AreEqual(traceId, resultSpan.TraceId); 51 | Assert.AreEqual(parentSpanId, resultSpan.ParentId); 52 | Assert.AreEqual(spanId, resultSpan.Id); 53 | } 54 | 55 | [TestMethod] 56 | public void CreateNewSpan_WithNullParentSpanId() 57 | { 58 | var resultSpan = SpanTracer.CreateNewSpan(fixture.Create(), fixture.Create().ToString(), null, fixture.Create().ToString()); 59 | 60 | Assert.IsNull(resultSpan.ParentId); 61 | } 62 | 63 | [TestMethod] 64 | [ExpectedException(typeof(ArgumentNullException))] 65 | public void CTOR_WithNullSpanCollector() 66 | { 67 | new SpanTracer(null, zipkinEndpointStub, new List(), fixture.Create()); 68 | } 69 | 70 | [TestMethod] 71 | [ExpectedException(typeof(ArgumentNullException))] 72 | public void CTOR_WithNullZipkinEndpoint() 73 | { 74 | new SpanTracer(spanCollectorStub, null, new List(), fixture.Create()); 75 | } 76 | 77 | [TestMethod] 78 | [ExpectedException(typeof(ArgumentNullException))] 79 | public void CTOR_WithNullZipkinNotToBeDomainList() 80 | { 81 | new SpanTracer(spanCollectorStub, zipkinEndpointStub, null, fixture.Create()); 82 | } 83 | 84 | [TestMethod] 85 | [ExpectedException(typeof(ArgumentNullException))] 86 | public void CTOR_WithNullDomain() 87 | { 88 | new SpanTracer(spanCollectorStub, zipkinEndpointStub, new List(), null); 89 | } 90 | 91 | [TestMethod] 92 | public void ReceiveServerSpan() 93 | { 94 | var domain = new Uri("http://server.com"); 95 | var requestName = fixture.Create(); 96 | var traceId = Guid.NewGuid().ToString("N"); 97 | var parentSpanId = fixture.Create().ToString("x"); 98 | var spanId = fixture.Create().ToString("x"); 99 | var serverUri = new Uri("https://" + clientServiceName + ":" + port + api); 100 | 101 | var spanTracer = new SpanTracer(spanCollectorStub, zipkinEndpointStub, zipkinNotToBeDisplayedDomainList, domain); 102 | 103 | var localEndpoint = new Endpoint { ServiceName = serverServiceName, Port = port }; 104 | zipkinEndpointStub.Expect(x => x.GetLocalEndpoint(Arg.Is(domain.Host), Arg.Is(port))).Return(localEndpoint); 105 | 106 | var resultSpan = spanTracer.ReceiveServerSpan(requestName, traceId, parentSpanId, spanId, serverUri); 107 | 108 | Assert.AreEqual(requestName, resultSpan.Name); 109 | Assert.AreEqual(traceId, resultSpan.TraceId); 110 | Assert.AreEqual(parentSpanId, resultSpan.ParentId); 111 | Assert.AreEqual(spanId, resultSpan.Id); 112 | 113 | Assert.AreEqual(1, resultSpan.GetAnnotationsByType().Count()); 114 | 115 | var annotation = resultSpan.Annotations[0] as Annotation; 116 | Assert.IsNotNull(annotation); 117 | Assert.AreEqual(ZipkinConstants.ServerReceive, annotation.Value); 118 | Assert.IsNotNull(annotation.Timestamp); 119 | Assert.IsNotNull(annotation.Host); 120 | 121 | Assert.AreEqual(localEndpoint, annotation.Host); 122 | 123 | var binaryAnnotations = resultSpan.GetAnnotationsByType(); 124 | 125 | Assert.AreEqual(1, binaryAnnotations.Count()); 126 | 127 | AssertBinaryAnnotations(binaryAnnotations, "http.path", serverUri.AbsolutePath); 128 | } 129 | 130 | [TestMethod] 131 | public void ReceiveServerSpan_UsingToBeCleanedDomainName() 132 | { 133 | var requestName = fixture.Create(); 134 | var traceId = fixture.Create().ToString("N"); 135 | var parentSpanId = fixture.Create().ToString(); 136 | var spanId = fixture.Create().ToString(); 137 | var serverUri = new Uri("https://" + clientServiceName + ":" + port + api); 138 | 139 | var domain = new Uri("https://" + serverServiceName + zipkinNotToBeDisplayedDomainList.First()); 140 | 141 | var spanTracer = new SpanTracer(spanCollectorStub, zipkinEndpointStub, zipkinNotToBeDisplayedDomainList, domain); 142 | 143 | var localEndpoint = new Endpoint { ServiceName = serverServiceName, Port = port }; 144 | zipkinEndpointStub.Expect(x => x.GetLocalEndpoint(Arg.Is(serverServiceName), Arg.Is(port))).Return(localEndpoint); 145 | 146 | var resultSpan = spanTracer.ReceiveServerSpan(requestName, traceId, parentSpanId, spanId, serverUri); 147 | 148 | var annotation = resultSpan.Annotations[0] as Annotation; 149 | Assert.AreEqual(localEndpoint, annotation.Host); 150 | } 151 | 152 | [TestMethod] 153 | public void ReceiveServerSpan_UsingAlreadyCleanedDomainName() 154 | { 155 | var domain = new Uri("https://server.com"); 156 | var requestName = fixture.Create(); 157 | var traceId = fixture.Create().ToString("N"); 158 | var parentSpanId = fixture.Create().ToString(); 159 | var spanId = fixture.Create().ToString(); 160 | var serverUri = new Uri("https://" + clientServiceName + ":" + port + api); 161 | 162 | var spanTracer = new SpanTracer(spanCollectorStub, zipkinEndpointStub, zipkinNotToBeDisplayedDomainList, domain); 163 | 164 | var localEndpoint = new Endpoint { ServiceName = domain.Host, Port = port }; 165 | zipkinEndpointStub.Expect(x => x.GetLocalEndpoint(Arg.Is(domain.Host), Arg.Is(port))).Return(localEndpoint); 166 | 167 | var resultSpan = spanTracer.ReceiveServerSpan(requestName, traceId, parentSpanId, spanId, serverUri); 168 | 169 | var annotation = resultSpan.Annotations[0] as Annotation; 170 | Assert.AreEqual(localEndpoint, annotation.Host); 171 | } 172 | 173 | [TestMethod] 174 | public void SendServerSpan() 175 | { 176 | var domain = new Uri("https://server.com"); 177 | var spanTracer = new SpanTracer(spanCollectorStub, zipkinEndpointStub, zipkinNotToBeDisplayedDomainList, domain); 178 | 179 | var endpoint = new Endpoint() { ServiceName = domain.Host, Port = (ushort)domain.Port }; 180 | var expectedSpan = new Span(); 181 | expectedSpan.Annotations.Add(new Annotation() { Host = endpoint, Value = ZipkinConstants.ServerReceive, Timestamp = DateTimeOffset.UtcNow }); 182 | 183 | zipkinEndpointStub.Expect(x => x.GetLocalEndpoint(domain.Host, (ushort)domain.Port)).Return(new Endpoint() { ServiceName = domain.Host }); 184 | 185 | spanTracer.SendServerSpan(expectedSpan); 186 | 187 | spanCollectorStub.AssertWasCalled(x => x.Collect(Arg.Matches(y => 188 | ValidateSendServerSpan(y, domain.Host) 189 | )) 190 | ); 191 | } 192 | 193 | [TestMethod] 194 | [ExpectedException(typeof(ArgumentNullException))] 195 | public void SendServerSpan_NullSpan() 196 | { 197 | var spanTracer = new SpanTracer(spanCollectorStub, zipkinEndpointStub, zipkinNotToBeDisplayedDomainList, new Uri("http://server.com")); 198 | 199 | spanTracer.SendServerSpan(null); 200 | } 201 | 202 | [TestMethod] 203 | [ExpectedException(typeof(ArgumentException))] 204 | public void SendServerSpan_NullAnnotation() 205 | { 206 | var spanTracer = new SpanTracer(spanCollectorStub, zipkinEndpointStub, zipkinNotToBeDisplayedDomainList, new Uri("http://server.com")); 207 | 208 | var expectedSpan = new Span(); 209 | 210 | spanTracer.SendServerSpan(expectedSpan); 211 | } 212 | 213 | [TestMethod] 214 | [ExpectedException(typeof(ArgumentException))] 215 | public void SendServerSpan_InvalidAnnotation() 216 | { 217 | var spanTracer = new SpanTracer(spanCollectorStub, zipkinEndpointStub, zipkinNotToBeDisplayedDomainList, new Uri("http://server.com")); 218 | 219 | var expectedSpan = new Span(); 220 | 221 | spanTracer.SendServerSpan(expectedSpan); 222 | } 223 | 224 | [TestMethod] 225 | public void SendClientSpan() 226 | { 227 | var domain = new Uri("https://server.com"); 228 | var requestName = fixture.Create(); 229 | var traceId = Guid.NewGuid().ToString("N"); 230 | var parentSpanId = fixture.Create().ToString("x"); 231 | var spanId = fixture.Create().ToString("x"); 232 | var serverUri = new Uri("https://" + clientServiceName + ":" + port + api); 233 | 234 | var spanTracer = new SpanTracer(spanCollectorStub, zipkinEndpointStub, zipkinNotToBeDisplayedDomainList, domain); 235 | 236 | var localEndpoint = new Endpoint {ServiceName = serverServiceName}; 237 | zipkinEndpointStub.Expect(x => x.GetLocalEndpoint(Arg.Is(domain.Host), Arg.Is.Anything)).Return(localEndpoint); 238 | var remoteEndpoint = new Endpoint { ServiceName = clientServiceName, Port = port }; 239 | zipkinEndpointStub.Expect(x => x.GetRemoteEndpoint(Arg.Is(serverUri), Arg.Is(clientServiceName))).Return(remoteEndpoint); 240 | 241 | var resultSpan = spanTracer.SendClientSpan(requestName, traceId, parentSpanId, spanId, serverUri); 242 | 243 | Assert.AreEqual(requestName, resultSpan.Name); 244 | Assert.AreEqual(traceId, resultSpan.TraceId); 245 | Assert.AreEqual(parentSpanId, resultSpan.ParentId); 246 | Assert.AreEqual(spanId, resultSpan.Id); 247 | 248 | 249 | 250 | Assert.AreEqual(1, resultSpan.GetAnnotationsByType().Count()); 251 | 252 | var annotation = resultSpan.Annotations[0] as Annotation; 253 | Assert.IsNotNull(annotation); 254 | Assert.AreEqual(ZipkinConstants.ClientSend, annotation.Value); 255 | Assert.IsNotNull(annotation.Timestamp); 256 | Assert.AreEqual(localEndpoint, annotation.Host); 257 | 258 | var binaryAnnotations = resultSpan.GetAnnotationsByType(); 259 | 260 | Assert.AreEqual(2, binaryAnnotations.Count()); 261 | AssertBinaryAnnotations(binaryAnnotations, "http.path", serverUri.AbsolutePath); 262 | AssertBinaryAnnotations(binaryAnnotations, "sa", "1"); 263 | 264 | var endpoint = binaryAnnotations.ToArray()[1].Host as Endpoint; 265 | 266 | Assert.IsNotNull(endpoint); 267 | Assert.AreEqual(clientServiceName, endpoint.ServiceName); 268 | Assert.AreEqual(port, endpoint.Port); 269 | } 270 | 271 | [TestMethod] 272 | public void SendClientSpanWithDomainUnderFilterList() 273 | { 274 | var domain = new Uri("https://server.com"); 275 | var requestName = fixture.Create(); 276 | var traceId = fixture.Create().ToString("N"); 277 | var parentSpanId = fixture.Create().ToString(); 278 | var spanId = fixture.Create().ToString(); 279 | var serverUri = new Uri("https://" + clientServiceName + zipkinNotToBeDisplayedDomainList.First() + ":" + port + api); 280 | 281 | var spanTracer = new SpanTracer(spanCollectorStub, zipkinEndpointStub, zipkinNotToBeDisplayedDomainList, domain); 282 | 283 | var localEndpoint = new Endpoint { ServiceName = serverServiceName }; 284 | zipkinEndpointStub.Expect(x => x.GetLocalEndpoint(Arg.Is(domain.Host), Arg.Is.Anything)).Return(localEndpoint); 285 | var remoteEndpoint = new Endpoint { ServiceName = clientServiceName, Port = port }; 286 | zipkinEndpointStub.Expect(x => x.GetRemoteEndpoint(Arg.Is(serverUri), Arg.Is(clientServiceName))).Return(remoteEndpoint); 287 | 288 | var resultSpan = spanTracer.SendClientSpan(requestName, traceId, parentSpanId, spanId, serverUri); 289 | 290 | var endpoint = resultSpan.GetAnnotationsByType().ToArray()[1].Host as Endpoint; 291 | 292 | Assert.IsNotNull(endpoint); 293 | Assert.AreEqual(clientServiceName, endpoint.ServiceName); 294 | Assert.AreEqual(port, endpoint.Port); 295 | } 296 | 297 | [TestMethod] 298 | public void ReceiveClientSpan() 299 | { 300 | var domain = new Uri("http://server.com"); 301 | var spanTracer = new SpanTracer(spanCollectorStub, zipkinEndpointStub, zipkinNotToBeDisplayedDomainList, domain); 302 | var endpoint = new Endpoint() { ServiceName = clientServiceName, Port = port }; 303 | var serverUri = new Uri("https://" + clientServiceName + ":" + port + api); 304 | var returnCode = fixture.Create(); 305 | var expectedSpan = new Span(); 306 | 307 | expectedSpan.Annotations.Add(new Annotation() { Host = endpoint, Value = ZipkinConstants.ClientSend, Timestamp = DateTimeOffset.UtcNow }); 308 | 309 | zipkinEndpointStub.Expect(x => x.GetRemoteEndpoint(serverUri, domain.Host)).Return(endpoint); 310 | 311 | spanTracer.ReceiveClientSpan(expectedSpan, returnCode); 312 | 313 | spanCollectorStub.AssertWasCalled(x => x.Collect(Arg.Matches(y => 314 | ValidateReceiveClientSpan(y, clientServiceName, port) 315 | )) 316 | ); 317 | } 318 | 319 | [TestMethod] 320 | [ExpectedException(typeof(ArgumentException))] 321 | public void ReceiveClientSpan_EmptyAnnotationsList() 322 | { 323 | var domain = new Uri("http://server.com"); 324 | var spanTracer = new SpanTracer(spanCollectorStub, zipkinEndpointStub, zipkinNotToBeDisplayedDomainList, domain); 325 | var endpoint = new Endpoint() { ServiceName = clientServiceName }; 326 | var serverUri = new Uri("https://" + clientServiceName + ":" + port + api); 327 | var returnCode = fixture.Create(); 328 | var expectedSpan = new Span(); 329 | 330 | zipkinEndpointStub.Expect(x => x.GetRemoteEndpoint(serverUri, domain.Host)).Return(endpoint); 331 | 332 | spanTracer.ReceiveClientSpan(expectedSpan, returnCode); 333 | } 334 | 335 | [TestMethod] 336 | [TestCategory("TraceRecordTests")] 337 | public void Record_WithSpanAndValue_AddsNewAnnotation() 338 | { 339 | // Arrange 340 | var expectedDescription = "Description"; 341 | var expectedSpan = new Span(); 342 | var domain = new Uri("http://server.com"); 343 | var spanTracer = new SpanTracer(spanCollectorStub, zipkinEndpointStub, zipkinNotToBeDisplayedDomainList, domain); 344 | 345 | // Act 346 | spanTracer.Record(expectedSpan, expectedDescription); 347 | 348 | // Assert 349 | Assert.IsNotNull( 350 | expectedSpan.GetAnnotationsByType().SingleOrDefault(a => (string)a.Value == expectedDescription), 351 | "The record is not found in the Annotations." 352 | ); 353 | } 354 | 355 | [TestMethod] 356 | [TestCategory("TraceRecordTests")] 357 | public void RecordBinary_WithSpanAndValue_AddsNewTypeCorrectBinaryAnnotation() 358 | { 359 | // Arrange 360 | var keyName = "TestKey"; 361 | var testValues = new dynamic[] 362 | { 363 | new { Value = true, ExpectedResult = true, Type = AnnotationType.Boolean }, 364 | new { Value = short.MaxValue, ExpectedResult = short.MaxValue, Type = AnnotationType.Int16 }, 365 | new { Value = int.MaxValue, ExpectedResult = int.MaxValue, Type = AnnotationType.Int32 }, 366 | new { Value = long.MaxValue, ExpectedResult = long.MaxValue, Type = AnnotationType.Int64 }, 367 | new { Value = double.MaxValue, ExpectedResult = double.MaxValue, Type = AnnotationType.Double }, 368 | new { Value = "String", ExpectedResult = "String", Type = AnnotationType.String }, 369 | new { Value = DateTime.MaxValue, ExpectedResult = DateTime.MaxValue, Type = AnnotationType.String } 370 | }; 371 | 372 | var domain = new Uri("http://server.com"); 373 | var spanTracer = new SpanTracer(spanCollectorStub, zipkinEndpointStub, zipkinNotToBeDisplayedDomainList, domain); 374 | 375 | foreach (var testValue in testValues) 376 | { 377 | var expectedSpan = new Span(); 378 | 379 | // Act 380 | spanTracer.RecordBinary(expectedSpan, keyName, testValue.Value); 381 | 382 | // Assert 383 | var actualAnnotation = expectedSpan 384 | .GetAnnotationsByType()? 385 | .SingleOrDefault(a => a.Key == keyName); 386 | 387 | var result = actualAnnotation?.Value; 388 | var annotationType = actualAnnotation?.AnnotationType; 389 | Assert.AreEqual(testValue.ExpectedResult, result, "The recorded value in the annotation is wrong."); 390 | Assert.AreEqual(testValue.Type, annotationType, "The Annotation Type is wrong."); 391 | } 392 | } 393 | 394 | private bool ValidateReceiveClientSpan(Span y, string serviceName, ushort port) 395 | { 396 | var firstannotation = (Annotation)y.Annotations[0]; 397 | var firstEndpoint = (Endpoint)firstannotation.Host; 398 | 399 | Assert.AreEqual(serviceName, firstEndpoint.ServiceName); 400 | Assert.AreEqual(port, firstEndpoint.Port); 401 | Assert.AreEqual(ZipkinConstants.ClientSend, firstannotation.Value); 402 | Assert.IsNotNull(firstannotation.Timestamp); 403 | 404 | var secondAnnotation = (Annotation)y.Annotations[1]; 405 | var secondEndpoint = (Endpoint)secondAnnotation.Host; 406 | 407 | Assert.AreEqual(serviceName, secondEndpoint.ServiceName); 408 | Assert.AreEqual(port, secondEndpoint.Port); 409 | Assert.AreEqual(ZipkinConstants.ClientReceive, secondAnnotation.Value); 410 | Assert.IsNotNull(secondAnnotation.Timestamp); 411 | 412 | return true; 413 | } 414 | 415 | private bool ValidateSendServerSpan(Span y, string serviceName) 416 | { 417 | var firstAnnotation = (Annotation)y.Annotations[0]; 418 | var firstEndpoint = (Endpoint)firstAnnotation.Host; 419 | 420 | Assert.AreEqual(serviceName, firstEndpoint.ServiceName); 421 | Assert.AreEqual(ZipkinConstants.ServerReceive, firstAnnotation.Value); 422 | Assert.IsNotNull(firstAnnotation.Timestamp); 423 | 424 | var secondAnnotation = (Annotation)y.Annotations[1]; 425 | var secondEndpoint = (Endpoint)secondAnnotation.Host; 426 | 427 | Assert.AreEqual(serviceName, secondEndpoint.ServiceName); 428 | Assert.AreEqual(ZipkinConstants.ServerSend, secondAnnotation.Value); 429 | Assert.IsNotNull(secondAnnotation.Timestamp); 430 | 431 | return true; 432 | } 433 | 434 | private void AssertBinaryAnnotations(IEnumerable list, string key, string value) 435 | { 436 | Assert.AreEqual(value, list.Where(x => x.Key.Equals(key)).Select(x => x.Value).First()); 437 | } 438 | } 439 | } -------------------------------------------------------------------------------- /tests/Medidata.ZipkinTracer.Core.Test/TraceProviderTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.Owin; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using Ploeh.AutoFixture; 6 | using Rhino.Mocks; 7 | using System.Text.RegularExpressions; 8 | 9 | namespace Medidata.ZipkinTracer.Core.Test 10 | { 11 | [TestClass] 12 | public class TraceProviderTests 13 | { 14 | private const string regex128BitPattern = @"^[a-f0-9]{32}$"; 15 | private const string regex64BitPattern = @"^[a-f0-9]{16}$"; 16 | 17 | [TestMethod] 18 | public void Constructor_GeneratingNew64BitTraceId() 19 | { 20 | // Arrange 21 | var config = new ZipkinConfig 22 | { 23 | Create128BitTraceId = false 24 | }; 25 | 26 | // Arrange & Act 27 | var traceProvider = new TraceProvider(config); 28 | 29 | // Assert 30 | Assert.IsTrue(Regex.IsMatch(traceProvider.TraceId, regex64BitPattern)); 31 | Assert.IsTrue(Regex.IsMatch(traceProvider.SpanId, regex64BitPattern)); 32 | Assert.AreEqual(string.Empty, traceProvider.ParentSpanId); 33 | Assert.AreEqual(false, traceProvider.IsSampled); 34 | } 35 | 36 | [TestMethod] 37 | public void Constructor_GeneratingNew128BitTraceId() 38 | { 39 | // Arrange 40 | var config = new ZipkinConfig 41 | { 42 | Create128BitTraceId = true 43 | }; 44 | 45 | // Arrange & Act 46 | var traceProvider = new TraceProvider(config); 47 | 48 | // Assert 49 | Assert.IsTrue(Regex.IsMatch(traceProvider.TraceId, regex128BitPattern)); 50 | Assert.IsTrue(Regex.IsMatch(traceProvider.SpanId, regex64BitPattern)); 51 | Assert.AreEqual(string.Empty, traceProvider.ParentSpanId); 52 | Assert.AreEqual(false, traceProvider.IsSampled); 53 | } 54 | 55 | [TestMethod] 56 | public void Constructor_HavingTraceProviderInContext() 57 | { 58 | // Arrange 59 | var context = MockRepository.GenerateStub(); 60 | var providerInContext = MockRepository.GenerateStub(); 61 | var environment = new Dictionary 62 | { 63 | { "Medidata.ZipkinTracer.Core.TraceProvider", providerInContext } 64 | }; 65 | context.Stub(x => x.Environment).Return(environment); 66 | 67 | // Act 68 | var sut = new TraceProvider(new ZipkinConfig(), context); 69 | 70 | // Assert 71 | Assert.AreEqual(providerInContext.TraceId, sut.TraceId); 72 | Assert.AreEqual(providerInContext.SpanId, sut.SpanId); 73 | Assert.AreEqual(providerInContext.ParentSpanId, sut.ParentSpanId); 74 | Assert.AreEqual(providerInContext.IsSampled, sut.IsSampled); 75 | } 76 | 77 | [TestMethod] 78 | public void Constructor_AcceptingHeadersWith64BitTraceId() 79 | { 80 | // Arrange 81 | var fixture = new Fixture(); 82 | var traceId = Convert.ToString(fixture.Create(), 16); 83 | var spanId = Convert.ToString(fixture.Create(), 16); 84 | var parentSpanId = Convert.ToString(fixture.Create(), 16); 85 | var isSampled = fixture.Create(); 86 | 87 | var context = GenerateContext( 88 | traceId, 89 | spanId, 90 | parentSpanId, 91 | isSampled.ToString()); 92 | 93 | // Act 94 | var sut = new TraceProvider(new ZipkinConfig(), context); 95 | 96 | // Assert 97 | Assert.AreEqual(traceId, sut.TraceId); 98 | Assert.AreEqual(spanId, sut.SpanId); 99 | Assert.AreEqual(parentSpanId, sut.ParentSpanId); 100 | Assert.AreEqual(isSampled, sut.IsSampled); 101 | } 102 | 103 | [TestMethod] 104 | public void Constructor_AcceptingHeadersWithLessThan16HexCharacters() 105 | { 106 | // Arrange 107 | var fixture = new Fixture(); 108 | var traceId = Convert.ToString(fixture.Create(), 16).Substring(1); 109 | var spanId = Convert.ToString(fixture.Create(), 16); 110 | var parentSpanId = Convert.ToString(fixture.Create(), 16); 111 | var isSampled = fixture.Create(); 112 | 113 | var context = GenerateContext( 114 | traceId, 115 | spanId, 116 | parentSpanId, 117 | isSampled.ToString()); 118 | 119 | // Act 120 | var sut = new TraceProvider(new ZipkinConfig(), context); 121 | 122 | // Assert 123 | Assert.AreEqual(traceId, sut.TraceId); 124 | Assert.AreEqual(spanId, sut.SpanId); 125 | Assert.AreEqual(parentSpanId, sut.ParentSpanId); 126 | Assert.AreEqual(isSampled, sut.IsSampled); 127 | } 128 | 129 | [TestMethod] 130 | public void Constructor_AcceptingHeadersWith128BitTraceId() 131 | { 132 | // Arrange 133 | var fixture = new Fixture(); 134 | var traceId = Guid.NewGuid().ToString("N"); 135 | var spanId = Convert.ToString(fixture.Create(), 16); 136 | var parentSpanId = Convert.ToString(fixture.Create(), 16); 137 | var isSampled = fixture.Create(); 138 | 139 | var context = GenerateContext( 140 | traceId, 141 | spanId, 142 | parentSpanId, 143 | isSampled.ToString()); 144 | 145 | // Act 146 | var sut = new TraceProvider(new ZipkinConfig(), context); 147 | 148 | // Assert 149 | Assert.AreEqual(traceId, sut.TraceId); 150 | Assert.AreEqual(spanId, sut.SpanId); 151 | Assert.AreEqual(parentSpanId, sut.ParentSpanId); 152 | Assert.AreEqual(isSampled, sut.IsSampled); 153 | } 154 | 155 | [TestMethod] 156 | public void Constructor_AcceptingHeadersWithOutIsSampled() 157 | { 158 | // Arrange 159 | var fixture = new Fixture(); 160 | var traceId = Convert.ToString(fixture.Create(), 16); 161 | var spanId = Convert.ToString(fixture.Create(), 16); 162 | var parentSpanId = Convert.ToString(fixture.Create(), 16); 163 | 164 | var context = MockRepository.GenerateStub(); 165 | var request = MockRepository.GenerateStub(); 166 | var headers = new HeaderDictionary(new Dictionary 167 | { 168 | { TraceProvider.TraceIdHeaderName, new [] { traceId } }, 169 | { TraceProvider.SpanIdHeaderName, new [] { spanId } }, 170 | { TraceProvider.ParentSpanIdHeaderName, new [] { parentSpanId } } 171 | }); 172 | var environment = new Dictionary(); 173 | 174 | request.Stub(x => x.Headers).Return(headers); 175 | context.Stub(x => x.Request).Return(request); 176 | context.Stub(x => x.Environment).Return(environment); 177 | 178 | var expectedIsSampled = fixture.Create(); 179 | var sampleFilter = MockRepository.GenerateStub(); 180 | sampleFilter.Expect(x => x.ShouldBeSampled(Arg.Is.Null, Arg.Is.Anything)).Return(expectedIsSampled); 181 | 182 | // Act 183 | var sut = new TraceProvider(sampleFilter, context); 184 | 185 | // Assert 186 | Assert.AreEqual(traceId, sut.TraceId); 187 | Assert.AreEqual(spanId, sut.SpanId); 188 | Assert.AreEqual(parentSpanId, sut.ParentSpanId); 189 | Assert.AreEqual(expectedIsSampled, sut.IsSampled); 190 | } 191 | 192 | [TestMethod] 193 | public void Constructor_AcceptingHeadersWithInvalidIdValues() 194 | { 195 | // Arrange 196 | var fixture = new Fixture(); 197 | var traceId = fixture.Create().ToString("N").Substring(1); 198 | var spanId = fixture.Create(); 199 | var parentSpanId = fixture.Create(); 200 | var isSampled = fixture.Create(); 201 | 202 | var context = GenerateContext( 203 | traceId, 204 | spanId, 205 | parentSpanId, 206 | isSampled); 207 | 208 | var expectedIsSampled = fixture.Create(); 209 | var sampleFilter = MockRepository.GenerateStub(); 210 | sampleFilter.Expect(x => x.ShouldBeSampled(Arg.Is(isSampled), Arg.Is.Anything)).Return(expectedIsSampled); 211 | 212 | // Act 213 | var sut = new TraceProvider(sampleFilter, context); 214 | 215 | // Assert 216 | Assert.AreNotEqual(traceId, sut.TraceId); 217 | Assert.AreNotEqual(spanId, sut.SpanId); 218 | Assert.AreEqual(string.Empty, sut.ParentSpanId); 219 | Assert.AreEqual(expectedIsSampled, sut.IsSampled); 220 | } 221 | 222 | [TestMethod] 223 | [ExpectedException(typeof(ArgumentException))] 224 | public void Constructor_AcceptingHeadersWithSpanAndParentSpan() 225 | { 226 | // Arrange 227 | var fixture = new Fixture(); 228 | var traceId = Convert.ToString(fixture.Create(), 16); 229 | var spanId = Convert.ToString(fixture.Create(), 16); 230 | var parentSpanId = spanId; 231 | var isSampled = fixture.Create(); 232 | 233 | var context = GenerateContext( 234 | traceId, 235 | spanId, 236 | parentSpanId, 237 | isSampled.ToString()); 238 | 239 | // Act 240 | new TraceProvider(new ZipkinConfig(), context); 241 | } 242 | 243 | [TestMethod] 244 | public void GetNext() 245 | { 246 | // Arrange 247 | var fixture = new Fixture(); 248 | var traceId = Convert.ToString(fixture.Create(), 16); 249 | var spanId = Convert.ToString(fixture.Create(), 16); 250 | var parentSpanId = Convert.ToString(fixture.Create(), 16); 251 | var isSampled = fixture.Create(); 252 | 253 | var context = GenerateContext( 254 | traceId, 255 | spanId, 256 | parentSpanId, 257 | isSampled.ToString()); 258 | 259 | var sut = new TraceProvider(new ZipkinConfig(), context); 260 | 261 | // Act 262 | var nextTraceProvider = sut.GetNext(); 263 | 264 | // Assert 265 | Assert.AreEqual(sut.TraceId, nextTraceProvider.TraceId); 266 | Assert.IsTrue(Regex.IsMatch(nextTraceProvider.SpanId, regex64BitPattern)); 267 | Assert.AreEqual(sut.SpanId, nextTraceProvider.ParentSpanId); 268 | Assert.AreEqual(sut.IsSampled, nextTraceProvider.IsSampled); 269 | } 270 | 271 | private IOwinContext GenerateContext(string traceId, string spanId, string parentSpanId, string isSampled) 272 | { 273 | var context = MockRepository.GenerateStub(); 274 | var request = MockRepository.GenerateStub(); 275 | var headers = new HeaderDictionary(new Dictionary 276 | { 277 | { TraceProvider.TraceIdHeaderName, new [] { traceId } }, 278 | { TraceProvider.SpanIdHeaderName, new [] { spanId } }, 279 | { TraceProvider.ParentSpanIdHeaderName, new [] { parentSpanId } }, 280 | { TraceProvider.SampledHeaderName, new [] { isSampled } } 281 | }); 282 | var environment = new Dictionary(); 283 | 284 | request.Stub(x => x.Headers).Return(headers); 285 | context.Stub(x => x.Request).Return(request); 286 | context.Stub(x => x.Environment).Return(environment); 287 | 288 | return context; 289 | } 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /tests/Medidata.ZipkinTracer.Core.Test/ZipkinClientTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using Medidata.ZipkinTracer.Core.Logging; 6 | using Medidata.ZipkinTracer.Models; 7 | using Microsoft.Owin; 8 | using Microsoft.VisualStudio.TestTools.UnitTesting; 9 | using Ploeh.AutoFixture; 10 | using Rhino.Mocks; 11 | 12 | namespace Medidata.ZipkinTracer.Core.Test 13 | { 14 | [TestClass] 15 | public class ZipkinClientTests 16 | { 17 | private IFixture fixture; 18 | private SpanCollector spanCollectorStub; 19 | private SpanTracer spanTracerStub; 20 | private ITraceProvider traceProvider; 21 | private ILog logger; 22 | private IOwinContext owinContext; 23 | private IDictionary headers; 24 | 25 | [TestInitialize] 26 | public void Init() 27 | { 28 | fixture = new Fixture(); 29 | traceProvider = MockRepository.GenerateStub(); 30 | logger = MockRepository.GenerateStub(); 31 | owinContext = MockRepository.GenerateStub(); 32 | owinContext.Stub(x => x.Environment).Return(new Dictionary()); 33 | var request = MockRepository.GenerateStub(); 34 | owinContext.Stub(x => x.Request).Return(request); 35 | headers = new Dictionary(); 36 | request.Stub(x => x.Headers).Return(new HeaderDictionary(headers)); 37 | } 38 | 39 | [TestMethod] 40 | [ExpectedException(typeof(ArgumentNullException))] 41 | public void CTOR_WithNullConfig() 42 | { 43 | new ZipkinClient(null, owinContext); 44 | } 45 | 46 | [TestMethod] 47 | [ExpectedException(typeof(ArgumentNullException))] 48 | public void CTOR_WithNullContext() 49 | { 50 | new ZipkinClient(new ZipkinConfig(), null); 51 | } 52 | 53 | [TestMethod] 54 | public void CTOR_WithNullCollector_create_default_collector() 55 | { 56 | var zipkinConfigStub = CreateZipkinConfigWithDefaultValues(sampleRate: 1); 57 | 58 | var client = new ZipkinClient(zipkinConfigStub, owinContext, null); 59 | 60 | Assert.IsNotNull(client.spanCollector); 61 | } 62 | 63 | [TestMethod] 64 | public void multiple_Client_WithNullCTORCollector_share_same_collector() 65 | { 66 | var zipkinConfigStub = CreateZipkinConfigWithDefaultValues(sampleRate: 1); 67 | 68 | var client1 = new ZipkinClient(zipkinConfigStub, owinContext, null); 69 | var client2 = new ZipkinClient(zipkinConfigStub, owinContext, null); 70 | 71 | Assert.IsNotNull(client1.spanCollector); 72 | Assert.ReferenceEquals(client1.spanCollector, client2.spanCollector); 73 | } 74 | 75 | [TestMethod] 76 | public void CTOR_WithTraceIdNullOrEmpty() 77 | { 78 | var zipkinConfigStub = CreateZipkinConfigWithDefaultValues(); 79 | 80 | AddTraceId(string.Empty); 81 | AddSampled(false); 82 | 83 | spanCollectorStub = MockRepository.GenerateStub(new Uri("http://localhost"), (uint)0); 84 | var zipkinClient = new ZipkinClient(zipkinConfigStub, owinContext, spanCollectorStub); 85 | Assert.IsFalse(zipkinClient.IsTraceOn); 86 | } 87 | 88 | [TestMethod] 89 | public void CTOR_WithIsSampledFalse() 90 | { 91 | var zipkinConfigStub = CreateZipkinConfigWithDefaultValues(); 92 | 93 | AddTraceId(fixture.Create()); 94 | AddSampled(false); 95 | 96 | spanCollectorStub = MockRepository.GenerateStub(new Uri("http://localhost"), (uint)0); 97 | var zipkinClient = new ZipkinClient(zipkinConfigStub, owinContext, spanCollectorStub); 98 | Assert.IsFalse(zipkinClient.IsTraceOn); 99 | } 100 | 101 | [TestMethod] 102 | public void CTOR_StartCollector() 103 | { 104 | var zipkinClient = (ZipkinClient)SetupZipkinClient(); 105 | Assert.IsNotNull(zipkinClient.spanCollector); 106 | Assert.IsNotNull(zipkinClient.spanTracer); 107 | } 108 | 109 | [TestMethod] 110 | public void Shutdown_StopCollector() 111 | { 112 | var zipkinClient = (ZipkinClient)SetupZipkinClient(); 113 | 114 | zipkinClient.ShutDown(); 115 | 116 | spanCollectorStub.AssertWasCalled(x => x.Stop()); 117 | } 118 | 119 | [TestMethod] 120 | public void Shutdown_CollectorNullDoesntThrow() 121 | { 122 | var zipkinClient = (ZipkinClient)SetupZipkinClient(); 123 | zipkinClient.spanCollector = null; 124 | 125 | zipkinClient.ShutDown(); 126 | } 127 | 128 | [TestMethod] 129 | public void StartServerSpan() 130 | { 131 | var tracerClient = SetupZipkinClient(); 132 | var zipkinClient = (ZipkinClient)tracerClient; 133 | spanTracerStub = GetSpanTracerStub(); 134 | zipkinClient.spanTracer = spanTracerStub; 135 | var uriHost = "https://www.x@y.com"; 136 | var uriAbsolutePath = "/object"; 137 | var methodName = "GET"; 138 | var spanName = methodName; 139 | var requestUri = new Uri(uriHost + uriAbsolutePath); 140 | 141 | var expectedSpan = new Span(); 142 | spanTracerStub.Expect( 143 | x => x.ReceiveServerSpan( 144 | Arg.Is.Equal(spanName.ToLower()), 145 | Arg.Is.Equal(traceProvider.TraceId), 146 | Arg.Is.Equal(traceProvider.ParentSpanId), 147 | Arg.Is.Equal(traceProvider.SpanId), 148 | Arg.Is.Equal(requestUri))).Return(expectedSpan); 149 | 150 | var result = tracerClient.StartServerTrace(requestUri, methodName); 151 | 152 | Assert.AreEqual(expectedSpan, result); 153 | } 154 | 155 | [TestMethod] 156 | public void StartServerSpan_Exception() 157 | { 158 | var tracerClient = SetupZipkinClient(); 159 | var zipkinClient = (ZipkinClient)tracerClient; 160 | spanTracerStub = GetSpanTracerStub(); 161 | zipkinClient.spanTracer = spanTracerStub; 162 | var uriHost = "https://www.x@y.com"; 163 | var uriAbsolutePath = "/object"; 164 | var methodName = "GET"; 165 | var spanName = methodName; 166 | var requestUri = new Uri(uriHost + uriAbsolutePath); 167 | 168 | spanTracerStub.Expect( 169 | x => x.ReceiveServerSpan( 170 | Arg.Is.Equal(spanName.ToLower()), 171 | Arg.Is.Anything, 172 | Arg.Is.Anything, 173 | Arg.Is.Anything, 174 | Arg.Is.Equal(requestUri))).Throw(new Exception()); 175 | 176 | var result = tracerClient.StartServerTrace(requestUri, methodName); 177 | 178 | Assert.IsNull(result); 179 | } 180 | 181 | [TestMethod] 182 | public void StartServerSpan_IsTraceOnIsFalse() 183 | { 184 | var tracerClient = SetupZipkinClient(); 185 | var zipkinClient = (ZipkinClient)tracerClient; 186 | zipkinClient.IsTraceOn = false; 187 | var uriHost = "https://www.x@y.com"; 188 | var uriAbsolutePath = "/object"; 189 | var methodName = "GET"; 190 | 191 | var result = tracerClient.StartServerTrace(new Uri(uriHost + uriAbsolutePath), methodName); 192 | 193 | Assert.IsNull(result); 194 | } 195 | 196 | [TestMethod] 197 | public void EndServerSpan() 198 | { 199 | var tracerClient = SetupZipkinClient(); 200 | var zipkinClient = (ZipkinClient)tracerClient; 201 | spanTracerStub = GetSpanTracerStub(); 202 | zipkinClient.spanTracer = spanTracerStub; 203 | var serverSpan = new Span(); 204 | 205 | tracerClient.EndServerTrace(serverSpan); 206 | 207 | spanTracerStub.AssertWasCalled(x => x.SendServerSpan(serverSpan)); 208 | } 209 | 210 | [TestMethod] 211 | public void EndServerSpan_Exception() 212 | { 213 | var tracerClient = SetupZipkinClient(); 214 | var zipkinClient = (ZipkinClient)tracerClient; 215 | spanTracerStub = GetSpanTracerStub(); 216 | zipkinClient.spanTracer = spanTracerStub; 217 | var serverSpan = new Span(); 218 | 219 | spanTracerStub.Expect(x => x.SendServerSpan(serverSpan)).Throw(new Exception()); 220 | 221 | tracerClient.EndServerTrace(serverSpan); 222 | } 223 | 224 | [TestMethod] 225 | public void EndServerSpan_IsTraceOnIsFalse_DoesntThrow() 226 | { 227 | var tracerClient = SetupZipkinClient(); 228 | var zipkinClient = (ZipkinClient)tracerClient; 229 | zipkinClient.IsTraceOn = false; 230 | var serverSpan = new Span(); 231 | 232 | tracerClient.EndServerTrace(serverSpan); 233 | } 234 | 235 | [TestMethod] 236 | public void EndServerSpan_NullServerSpan_DoesntThrow() 237 | { 238 | var tracerClient = SetupZipkinClient(); 239 | 240 | tracerClient.EndServerTrace(null); 241 | } 242 | 243 | [TestMethod] 244 | public void StartClientSpan() 245 | { 246 | var tracerClient = SetupZipkinClient(); 247 | var zipkinClient = (ZipkinClient)tracerClient; 248 | spanTracerStub = GetSpanTracerStub(); 249 | zipkinClient.spanTracer = spanTracerStub; 250 | var clientServiceName = "abc-sandbox"; 251 | var uriAbsolutePath = "/object"; 252 | var methodName = "GET"; 253 | var spanName = methodName; 254 | 255 | var expectedSpan = new Span(); 256 | spanTracerStub.Expect( 257 | x => x.SendClientSpan( 258 | Arg.Is.Equal(spanName.ToLower()), 259 | Arg.Is.Equal(traceProvider.TraceId), 260 | Arg.Is.Equal(traceProvider.ParentSpanId), 261 | Arg.Is.Equal(traceProvider.SpanId), 262 | Arg.Is.Anything)).Return(expectedSpan); 263 | 264 | var result = tracerClient.StartClientTrace(new Uri("https://" + clientServiceName + ".xyz.net:8000" + uriAbsolutePath), methodName, traceProvider); 265 | 266 | Assert.AreEqual(expectedSpan, result); 267 | } 268 | 269 | [TestMethod] 270 | public void StartClientSpan_UsingIpAddress() 271 | { 272 | var tracerClient = SetupZipkinClient(); 273 | var zipkinClient = (ZipkinClient)tracerClient; 274 | spanTracerStub = GetSpanTracerStub(); 275 | zipkinClient.spanTracer = spanTracerStub; 276 | var clientServiceName = "192.168.178.178"; 277 | var uriAbsolutePath = "/object"; 278 | var methodName = "GET"; 279 | var spanName = methodName; 280 | 281 | var expectedSpan = new Span(); 282 | spanTracerStub.Expect( 283 | x => x.SendClientSpan( 284 | Arg.Is.Equal(spanName.ToLower()), 285 | Arg.Is.Equal(traceProvider.TraceId), 286 | Arg.Is.Equal(traceProvider.ParentSpanId), 287 | Arg.Is.Equal(traceProvider.SpanId), 288 | Arg.Is.Anything)).Return(expectedSpan); 289 | 290 | var result = tracerClient.StartClientTrace(new Uri("https://" + clientServiceName + ".xyz.net:8000" + uriAbsolutePath), methodName, traceProvider); 291 | 292 | Assert.AreEqual(expectedSpan, result); 293 | } 294 | 295 | [TestMethod] 296 | public void StartClientSpan_MultipleDomainList() 297 | { 298 | var zipkinConfig = CreateZipkinConfigWithDefaultValues(); 299 | zipkinConfig.NotToBeDisplayedDomainList = new List { ".abc.net", ".xyz.net" }; 300 | var tracerClient = SetupZipkinClient(zipkinConfig); 301 | var zipkinClient = (ZipkinClient)tracerClient; 302 | spanTracerStub = GetSpanTracerStub(); 303 | zipkinClient.spanTracer = spanTracerStub; 304 | var clientServiceName = "abc-sandbox"; 305 | var uriAbsolutePath = "/object"; 306 | var methodName = "GET"; 307 | var spanName = methodName; 308 | 309 | var expectedSpan = new Span(); 310 | spanTracerStub.Expect( 311 | x => x.SendClientSpan( 312 | Arg.Is.Equal(spanName.ToLower()), 313 | Arg.Is.Equal(traceProvider.TraceId), 314 | Arg.Is.Equal(traceProvider.ParentSpanId), 315 | Arg.Is.Equal(traceProvider.SpanId), 316 | Arg.Is.Anything)).Return(expectedSpan); 317 | 318 | var result = tracerClient.StartClientTrace(new Uri("https://" + clientServiceName + ".xyz.net:8000" + uriAbsolutePath), methodName, traceProvider); 319 | 320 | Assert.AreEqual(expectedSpan, result); 321 | } 322 | 323 | [TestMethod] 324 | public void StartClientSpan_Exception() 325 | { 326 | var tracerClient = SetupZipkinClient(); 327 | var zipkinClient = (ZipkinClient)tracerClient; 328 | spanTracerStub = GetSpanTracerStub(); 329 | zipkinClient.spanTracer = spanTracerStub; 330 | var clientServiceName = "abc-sandbox"; 331 | var uriAbsolutePath = "/object"; 332 | var methodName = "GET"; 333 | var spanName = methodName; 334 | 335 | spanTracerStub.Expect( 336 | x => x.SendClientSpan( 337 | Arg.Is.Equal(spanName.ToLower()), 338 | Arg.Is.Anything, 339 | Arg.Is.Anything, 340 | Arg.Is.Anything, 341 | Arg.Is.Anything)).Throw(new Exception()); 342 | 343 | var result = tracerClient.StartClientTrace(new Uri("https://" + clientServiceName + ".xyz.net:8000" + uriAbsolutePath), methodName, traceProvider); 344 | 345 | Assert.IsNull(result); 346 | } 347 | 348 | [TestMethod] 349 | public void StartClientSpan_IsTraceOnIsFalse() 350 | { 351 | var tracerClient = SetupZipkinClient(); 352 | var zipkinClient = (ZipkinClient)tracerClient; 353 | zipkinClient.IsTraceOn = false; 354 | var clientServiceName = "abc-sandbox"; 355 | var clientServiceUri = new Uri("https://" + clientServiceName + ".xyz.net:8000"); 356 | var methodName = "GET"; 357 | 358 | var result = tracerClient.StartClientTrace(clientServiceUri, methodName, traceProvider); 359 | 360 | Assert.IsNull(result); 361 | } 362 | 363 | [TestMethod] 364 | public void EndClientSpan() 365 | { 366 | var returnCode = fixture.Create(); 367 | var tracerClient = SetupZipkinClient(); 368 | var zipkinClient = (ZipkinClient)tracerClient; 369 | spanTracerStub = GetSpanTracerStub(); 370 | zipkinClient.spanTracer = spanTracerStub; 371 | var clientSpan = new Span(); 372 | 373 | tracerClient.EndClientTrace(clientSpan, returnCode); 374 | 375 | spanTracerStub.AssertWasCalled(x => x.ReceiveClientSpan(clientSpan, returnCode)); 376 | } 377 | 378 | [TestMethod] 379 | public void EndClientSpan_Exception() 380 | { 381 | var returnCode = fixture.Create(); 382 | var tracerClient = SetupZipkinClient(); 383 | var zipkinClient = (ZipkinClient)tracerClient; 384 | spanTracerStub = GetSpanTracerStub(); 385 | zipkinClient.spanTracer = spanTracerStub; 386 | var clientSpan = new Span(); 387 | 388 | spanTracerStub.Expect(x => x.ReceiveClientSpan(clientSpan, returnCode)).Throw(new Exception()); 389 | 390 | tracerClient.EndClientTrace(clientSpan, returnCode); 391 | } 392 | 393 | [TestMethod] 394 | public void EndClientSpan_NullClientTrace_DoesntThrow() 395 | { 396 | var returnCode = fixture.Create(); 397 | var tracerClient = SetupZipkinClient(); 398 | spanTracerStub = GetSpanTracerStub(); 399 | 400 | var called = false; 401 | spanTracerStub.Stub(x => x.ReceiveClientSpan(Arg.Is.Anything, Arg.Is.Equal(returnCode))) 402 | .WhenCalled(x => { called = true; }); 403 | 404 | tracerClient.EndClientTrace(null, returnCode); 405 | 406 | Assert.IsFalse(called); 407 | } 408 | 409 | [TestMethod] 410 | public void EndClientSpan_IsTraceOnIsFalse_DoesntThrow() 411 | { 412 | var returnCode = fixture.Create(); 413 | var tracerClient = SetupZipkinClient(); 414 | spanTracerStub = GetSpanTracerStub(); 415 | var zipkinClient = (ZipkinClient)tracerClient; 416 | zipkinClient.IsTraceOn = false; 417 | 418 | var called = false; 419 | spanTracerStub.Stub(x => x.ReceiveClientSpan(Arg.Is.Anything, Arg.Is.Equal(returnCode))) 420 | .WhenCalled(x => { called = true; }); 421 | 422 | tracerClient.EndClientTrace(new Span(), returnCode); 423 | 424 | Assert.IsFalse(called); 425 | } 426 | 427 | [TestMethod] 428 | [TestCategory("TraceRecordTests")] 429 | public void Record_IsTraceOnIsFalse_DoesNotAddAnnotation() 430 | { 431 | // Arrange 432 | var tracerClient = SetupZipkinClient(); 433 | spanTracerStub = GetSpanTracerStub(); 434 | var zipkinClient = (ZipkinClient)tracerClient; 435 | zipkinClient.IsTraceOn = false; 436 | 437 | var testSpan = new Span(); 438 | 439 | // Act 440 | tracerClient.Record(testSpan, "irrelevant"); 441 | 442 | // Assert 443 | Assert.IsFalse(testSpan.Annotations.Any(), "There are annotations but the trace is off."); 444 | } 445 | 446 | [TestMethod] 447 | [TestCategory("TraceRecordTests")] 448 | public void Record_WithoutValue_AddsAnnotationWithCallerName() 449 | { 450 | // Arrange 451 | var callerMemberName = new StackTrace().GetFrame(0).GetMethod().Name; 452 | var tracerClient = SetupZipkinClient(); 453 | spanTracerStub = GetSpanTracerStub(); 454 | var zipkinClient = (ZipkinClient)tracerClient; 455 | zipkinClient.IsTraceOn = true; 456 | 457 | var testSpan = new Span(); 458 | 459 | // Act 460 | tracerClient.Record(testSpan); 461 | 462 | // Assert 463 | Assert.AreEqual(1, testSpan.Annotations.Count, "There is not exactly one annotation added."); 464 | Assert.IsNotNull( 465 | testSpan.GetAnnotationsByType().SingleOrDefault(a => (string)a.Value == callerMemberName), 466 | "The record with the caller name is not found in the Annotations." 467 | ); 468 | } 469 | 470 | [TestMethod] 471 | [TestCategory("TraceRecordTests")] 472 | public void RecordBinary_IsTraceOnIsFalse_DoesNotAddBinaryAnnotation() 473 | { 474 | // Arrange 475 | var keyName = "TestKey"; 476 | var testValue = "Some Value"; 477 | var tracerClient = SetupZipkinClient(); 478 | spanTracerStub = GetSpanTracerStub(); 479 | var zipkinClient = (ZipkinClient)tracerClient; 480 | zipkinClient.IsTraceOn = false; 481 | 482 | var testSpan = new Span(); 483 | 484 | // Act 485 | tracerClient.RecordBinary(testSpan, keyName, testValue); 486 | 487 | // Assert 488 | Assert.IsFalse(testSpan.GetAnnotationsByType().Any(), "There are annotations but the trace is off."); 489 | } 490 | 491 | [TestMethod] 492 | [TestCategory("TraceRecordTests")] 493 | public void RecordLocalComponent_WithNotNullValue_AddsLocalComponentAnnotation() 494 | { 495 | // Arrange 496 | var testValue = "Some Value"; 497 | var tracerClient = SetupZipkinClient(); 498 | spanTracerStub = GetSpanTracerStub(); 499 | var zipkinClient = (ZipkinClient)tracerClient; 500 | zipkinClient.IsTraceOn = true; 501 | 502 | var testSpan = new Span(); 503 | 504 | // Act 505 | tracerClient.RecordLocalComponent(testSpan, testValue); 506 | 507 | // Assert 508 | var annotation = testSpan.GetAnnotationsByType().SingleOrDefault(a => a.Key == ZipkinConstants.LocalComponent); 509 | Assert.IsNotNull(annotation, "There is no local trace annotation in the binary annotations."); 510 | Assert.AreEqual(testValue, annotation.Value, "The local component annotation value is not correct."); 511 | } 512 | 513 | [TestMethod] 514 | [TestCategory("TraceRecordTests")] 515 | public void RecordLocalComponent_IsTraceOnIsFalse_DoesNotAddLocalComponentAnnotation() 516 | { 517 | // Arrange 518 | var testValue = "Some Value"; 519 | var tracerClient = SetupZipkinClient(); 520 | spanTracerStub = GetSpanTracerStub(); 521 | var zipkinClient = (ZipkinClient)tracerClient; 522 | zipkinClient.IsTraceOn = false; 523 | 524 | var testSpan = new Span(); 525 | 526 | // Act 527 | tracerClient.RecordBinary(testSpan, ZipkinConstants.LocalComponent, testValue); 528 | 529 | // Assert 530 | Assert.IsFalse(testSpan.GetAnnotationsByType().Any(), "There are annotations but the trace is off."); 531 | } 532 | 533 | private ITracerClient SetupZipkinClient(IZipkinConfig zipkinConfig = null) 534 | { 535 | spanCollectorStub = MockRepository.GenerateStub(new Uri("http://localhost"), (uint)0); 536 | 537 | traceProvider.Stub(x => x.TraceId).Return(fixture.Create()); 538 | traceProvider.Stub(x => x.SpanId).Return(fixture.Create()); 539 | traceProvider.Stub(x => x.ParentSpanId).Return(fixture.Create()); 540 | traceProvider.Stub(x => x.IsSampled).Return(true); 541 | 542 | var context = MockRepository.GenerateStub(); 543 | var request = MockRepository.GenerateStub(); 544 | context.Stub(x => x.Request).Return(request); 545 | context.Stub(x => x.Environment).Return(new Dictionary { { TraceProvider.Key, traceProvider } }); 546 | 547 | IZipkinConfig zipkinConfigSetup = zipkinConfig; 548 | if (zipkinConfig == null) 549 | { 550 | zipkinConfigSetup = CreateZipkinConfigWithDefaultValues(); 551 | } 552 | 553 | return new ZipkinClient(zipkinConfigSetup, context, spanCollectorStub); 554 | } 555 | 556 | static readonly char[] separators = new[] { ',', ';' }; 557 | static readonly Func> SplitFunc = s => s.Split(separators).Select(e => e.Trim()).ToList(); 558 | 559 | private IZipkinConfig CreateZipkinConfigWithDefaultValues(string uriSt = "http://zipkin.com", string domainSt = "http://server.com", 560 | uint spanProcessorBatchSize = 123, string excludedPathList = "/foo, /bar, /baz", double sampleRate = 0.5, string notToBeDisplayedDomainList = ".xyz.net") 561 | { 562 | return new ZipkinConfig 563 | { 564 | ZipkinBaseUri = new Uri(uriSt), 565 | Domain = r => new Uri(domainSt), 566 | SpanProcessorBatchSize = spanProcessorBatchSize, 567 | ExcludedPathList = SplitFunc(excludedPathList), 568 | SampleRate = sampleRate, 569 | NotToBeDisplayedDomainList = SplitFunc(notToBeDisplayedDomainList), 570 | }; 571 | } 572 | 573 | private SpanTracer GetSpanTracerStub() 574 | { 575 | return 576 | MockRepository.GenerateStub( 577 | spanCollectorStub, 578 | MockRepository.GenerateStub(), 579 | new List(), 580 | new Uri("http://server.com") 581 | ); 582 | } 583 | 584 | private void AddTraceId(string traceId) 585 | { 586 | headers.Add(TraceProvider.TraceIdHeaderName, new[] { traceId }); 587 | } 588 | 589 | private void AddSampled(bool sampled) 590 | { 591 | headers.Add(TraceProvider.SampledHeaderName, new[] { sampled.ToString() }); 592 | } 593 | } 594 | } 595 | -------------------------------------------------------------------------------- /tests/Medidata.ZipkinTracer.Core.Test/ZipkinConfigTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using Ploeh.AutoFixture; 5 | 6 | namespace Medidata.ZipkinTracer.Core.Test 7 | { 8 | [TestClass] 9 | public class ZipkinConfigTests 10 | { 11 | private ZipkinConfig _sut; 12 | 13 | [TestInitialize] 14 | public void Init() 15 | { 16 | var fixture = new Fixture(); 17 | _sut = new ZipkinConfig 18 | { 19 | ZipkinBaseUri = new Uri("http://zipkin.com"), 20 | Domain = r => new Uri("http://server.com"), 21 | SpanProcessorBatchSize = fixture.Create(), 22 | ExcludedPathList = new List(), 23 | SampleRate = 0, 24 | NotToBeDisplayedDomainList = new List() 25 | }; 26 | } 27 | 28 | [TestMethod] 29 | public void Validate() 30 | { 31 | _sut.Validate(); 32 | } 33 | 34 | [TestMethod] 35 | [ExpectedException(typeof(ArgumentNullException))] 36 | public void ValidateWithNullDontSampleList() 37 | { 38 | _sut.ExcludedPathList = null; 39 | _sut.Validate(); 40 | } 41 | 42 | [TestMethod] 43 | [ExpectedException(typeof(ArgumentException))] 44 | public void ValidateWithInvalirdDontSampleListItem() 45 | { 46 | _sut.ExcludedPathList = new List { "xxx" }; 47 | _sut.Validate(); 48 | } 49 | 50 | [TestMethod] 51 | [ExpectedException(typeof(ArgumentException))] 52 | public void ValidateWithNegativeSampleRate() 53 | { 54 | _sut.SampleRate = -1; 55 | _sut.Validate(); 56 | } 57 | 58 | [TestMethod] 59 | [ExpectedException(typeof(ArgumentException))] 60 | public void ValidateWithInvalidSampleRate() 61 | { 62 | _sut.SampleRate = 1.1; 63 | _sut.Validate(); 64 | } 65 | 66 | [TestMethod] 67 | [ExpectedException(typeof(ArgumentNullException))] 68 | public void ValidateWithNullNotToBeDisplayedDomainList() 69 | { 70 | _sut.NotToBeDisplayedDomainList = null; 71 | _sut.Validate(); 72 | } 73 | 74 | /// 75 | /// TODO: Use XUnit to do easier unit test for inline data 76 | /// 77 | private class ShouldBeSampledCondition 78 | { 79 | public string SampledFlag { get; set; } 80 | public string RequestPath { get; set; } 81 | public double SampleRate { get; set; } 82 | public List ExcludedPathList { get; set; } 83 | public bool ExpectedOutcome { get; set; } 84 | 85 | public ShouldBeSampledCondition( 86 | string sampledFlag, 87 | string requestPath, 88 | double sampleRate, 89 | List excludedPathList, 90 | bool expectedOutcome) 91 | { 92 | SampledFlag = sampledFlag; 93 | RequestPath = requestPath; 94 | SampleRate = sampleRate; 95 | ExcludedPathList = excludedPathList; 96 | ExpectedOutcome = expectedOutcome; 97 | } 98 | } 99 | 100 | [TestMethod] 101 | public void ShouldBeSampled() 102 | { 103 | // Arrange 104 | List testScenarios = new List() { 105 | // sampledFlag has a valid bool string value 106 | { new ShouldBeSampledCondition("0", null, 0, new List(), false) }, 107 | { new ShouldBeSampledCondition("1", null, 0, new List(), true) }, 108 | { new ShouldBeSampledCondition("false", null, 0, new List(), false) }, 109 | { new ShouldBeSampledCondition("true", null, 0, new List(), true) }, 110 | { new ShouldBeSampledCondition("FALSE", null, 0, new List(), false) }, 111 | { new ShouldBeSampledCondition("TRUE", null, 0, new List(), true) }, 112 | { new ShouldBeSampledCondition("FalSe", null, 0, new List(), false) }, 113 | { new ShouldBeSampledCondition("TrUe", null, 0, new List(), true) }, 114 | // sampledFlag has an invalid bool string value and requestPath is IsInDontSampleList 115 | { new ShouldBeSampledCondition(null, "/x", 0, new List { "/x" }, false) }, 116 | { new ShouldBeSampledCondition("", "/x", 0, new List { "/x" }, false) }, 117 | { new ShouldBeSampledCondition("invalidValue", "/x", 0, new List() { "/x" }, false) }, 118 | // sampledFlag has an invalid bool string value, requestPath not in IsInDontSampleList, and sample rate is 0 119 | { new ShouldBeSampledCondition(null, null, 0, new List(), false) }, 120 | { new ShouldBeSampledCondition(null, "/x", 0, new List(), false) }, 121 | // sampledFlag has an invalid bool string value, requestPath not in IsInDontSampleList, and sample rate is 1 122 | { new ShouldBeSampledCondition(null, null, 1, new List(), true) }, 123 | }; 124 | 125 | foreach (var testScenario in testScenarios) 126 | { 127 | var fixture = new Fixture(); 128 | _sut = new ZipkinConfig 129 | { 130 | ExcludedPathList = testScenario.ExcludedPathList, 131 | SampleRate = testScenario.SampleRate 132 | }; 133 | 134 | // Act 135 | var result = _sut.ShouldBeSampled(testScenario.SampledFlag, testScenario.RequestPath); 136 | 137 | // Assert 138 | Assert.AreEqual( 139 | testScenario.ExpectedOutcome, 140 | result, 141 | "Scenario: " + 142 | $"SampledFlag({testScenario.SampledFlag ?? "null"}), " + 143 | $"RequestPath({testScenario.RequestPath ?? "null"}), " + 144 | $"SampleRate({testScenario.SampleRate}), " + 145 | $"ExcludedPathList({string.Join(",", testScenario.ExcludedPathList)}),"); 146 | } 147 | } 148 | } 149 | } -------------------------------------------------------------------------------- /tests/Medidata.ZipkinTracer.Core.Test/ZipkinEndpointTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using Ploeh.AutoFixture; 4 | 5 | namespace Medidata.ZipkinTracer.Core.Test 6 | { 7 | [TestClass] 8 | public class ZipkinEndpointTests 9 | { 10 | private IFixture fixture; 11 | 12 | [TestInitialize] 13 | public void Init() 14 | { 15 | fixture = new Fixture(); 16 | } 17 | 18 | [TestMethod] 19 | public void GetLocalEndpoint() 20 | { 21 | var serviceName = fixture.Create(); 22 | var port = fixture.Create(); 23 | 24 | var zipkinEndpoint = new ServiceEndpoint(); 25 | var endpoint = zipkinEndpoint.GetLocalEndpoint(serviceName, port); 26 | 27 | Assert.IsNotNull(endpoint); 28 | Assert.AreEqual(serviceName, endpoint.ServiceName); 29 | Assert.IsNotNull(endpoint.IPAddress); 30 | Assert.IsNotNull(endpoint.Port); 31 | } 32 | 33 | [TestMethod] 34 | public void GetRemoteEndpoint() 35 | { 36 | var remoteUri = new Uri("http://localhost"); 37 | var serviceName = fixture.Create(); 38 | 39 | var zipkinEndpoint = new ServiceEndpoint(); 40 | var endpoint = zipkinEndpoint.GetRemoteEndpoint(remoteUri, serviceName); 41 | 42 | Assert.IsNotNull(endpoint); 43 | Assert.AreEqual(serviceName, endpoint.ServiceName); 44 | Assert.IsNotNull(endpoint.IPAddress); 45 | Assert.IsNotNull(endpoint.Port); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/Medidata.ZipkinTracer.Core.Test/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /tests/Medidata.ZipkinTracer.Core.Test/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | --------------------------------------------------------------------------------