├── .circleci └── config.yml ├── .editorconfig ├── .gitignore ├── .vscode └── tasks.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── LightStep.sln ├── NuGet.config ├── README.md ├── build.cake ├── build.sh ├── examples ├── LightStep.CSharpAspectTestApp │ ├── App.config │ ├── Aspects │ │ └── Traceable.cs │ ├── HttpWorker.cs │ ├── LightStep.CSharpAspectTestApp.csproj │ ├── Program.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── README.md │ └── packages.config ├── LightStep.CSharpDITestApp │ ├── LightStep.CSharpDITestApp.csproj │ ├── Program.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ └── packages.config └── LightStep.CSharpTestApp │ ├── LightStep.CSharpTestApp.csproj │ └── Program.cs ├── ls128x128.png ├── src └── LightStep │ ├── Baggage.cs │ ├── Collector │ ├── Collector.cs │ ├── ILightStepHttpClient.cs │ ├── IReportTranslator.cs │ ├── LightStepHttpClient.cs │ ├── ProtoConverter.cs │ ├── ProtoConverterExtensions.cs │ └── ReportTranslator.cs │ ├── HighResolutionDateTime.cs │ ├── ISpanRecorder.cs │ ├── LightStep.cs │ ├── LightStep.csproj │ ├── LightStepConstants.cs │ ├── LightStepSpanRecorder.cs │ ├── LogData.cs │ ├── Options.cs │ ├── Propagation │ ├── B3Propagator.cs │ ├── BinaryPropagator.cs │ ├── ConsolePropagator.cs │ ├── EnvoyPropagator.cs │ ├── HttpHeadersPropagator.cs │ ├── IPropagator.cs │ ├── Keys.cs │ ├── PropagatorStack.cs │ ├── Propagators.cs │ └── TextMapPropagator.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── Reference.cs │ ├── SatelliteOptions.cs │ ├── Span.cs │ ├── SpanBuilder.cs │ ├── SpanContext.cs │ ├── SpanData.cs │ ├── Tracer.cs │ ├── TransportOptions.cs │ ├── TypedContextExtensions.cs │ ├── Utilities.cs │ ├── collector.proto │ ├── google │ ├── api │ │ ├── annotations.proto │ │ └── http.proto │ └── protobuf │ │ └── timestamp.proto │ └── lightstep.proto ├── test ├── LightStep.Tests │ ├── LightStep.Tests.csproj │ ├── LightStepProtoTests.cs │ ├── MockLogProvider.cs │ ├── PropagatorTests.cs │ ├── SimpleMockRecorder.cs │ ├── SpanTests.cs │ └── TracerLogTest.cs └── LightStep.TracerPerf.Tests │ ├── LightStep.TracerPerf.Tests.csproj │ ├── README.md │ ├── TracerLogTest.cs │ ├── TracerMemoryTest.cs │ ├── TracerNetworkTest.cs │ └── TracerTestBase.cs └── tracerSign.snk /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build-and-test: 4 | docker: 5 | - image: aparker/circleci-dotnet-mono:latest 6 | steps: 7 | - checkout 8 | - run: dotnet tool install -g dotnet-xunit-to-junit 9 | - run: dotnet tool install -g Cake.Tool 10 | - run: export PATH="$PATH:/root/.dotnet/tools" 11 | - run: 12 | name: Update PATH and Define Environment Variable at Runtime 13 | command: | 14 | echo 'export PATH="$PATH:/root/.dotnet/tools"' >> $BASH_ENV 15 | source $BASH_ENV 16 | - run: dotnet cake build.cake --target=Test 17 | - run: dotnet xunit-to-junit ./build/test_results.xml ./build/xunit/LightStep.Tests.dll.junit.xml 18 | - run: bash <(curl -s https://codecov.io/bash) || echo "Codecov did not collect coverage reports" 19 | - store_test_results: 20 | path: build 21 | publish: 22 | docker: 23 | - image: aparker/circleci-dotnet-mono:latest 24 | steps: 25 | - checkout 26 | - run: dotnet tool install -g dotnet-xunit-to-junit 27 | - run: dotnet tool install -g Cake.Tool 28 | - run: 29 | name: Update PATH and Define Environment Variable at Runtime 30 | command: | 31 | echo 'export PATH="$PATH:/root/.dotnet/tools"' >> $BASH_ENV 32 | source $BASH_ENV 33 | - run: dotnet cake build.cake --target=Publish 34 | - run: dotnet xunit-to-junit ./build/test_results.xml ./build/xunit/LightStep.Tests.dll.junit.xml 35 | - run: bash <(curl -s https://codecov.io/bash) || echo "Codecov did not collect coverage reports" 36 | - store_test_results: 37 | path: build 38 | 39 | workflows: 40 | version: 2 41 | untagged-build: 42 | jobs: 43 | - build-and-test 44 | tagged-build: 45 | jobs: 46 | - publish: 47 | filters: 48 | branches: 49 | ignore: /.*/ 50 | tags: 51 | only: /^v.*/ -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 4 7 | insert_final_newline = false 8 | trim_trailing_whitespace = true 9 | 10 | [*.sln] 11 | indent_style = tab 12 | 13 | [*.csproj] 14 | indent_size = 2 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ 331 | 332 | # cakebuild tools 333 | tools/ 334 | 335 | dist/ 336 | build/ 337 | 338 | # this is generated at build, ignoring it here 339 | src/LightStep/Properties/AssemblyInfo.cs 340 | 341 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "cake", 8 | "script": "Default", 9 | "group": { 10 | "kind": "build", 11 | "isDefault": true 12 | } 13 | }, 14 | { 15 | "type": "cake", 16 | "script": "Test", 17 | "problemMatcher": [], 18 | "group": "test" 19 | }, 20 | { 21 | "type": "cake", 22 | "script": "Clean", 23 | "problemMatcher": [] 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | _v0.12.0_ 3 | *Note* This release modifies the `Tracer` object to make it disposable. You may wish to check your integrations and wrappers to ensure that this doesn't break anything. 4 | - The `Tracer` now implements `IDisposable`. This is intended to be used by implementors to add an "off" switch that can stop span recording. 5 | - The version of certain Protobuf libraries required has been relaxed to it's pre-0.11 state. 6 | - We no longer make a copy of the span buffer to count how many spans are in it in certain cases, improving performance. 7 | 8 | _v0.11.0_ 9 | *Note* This release changes the public API surface. It should not impact you, but it might if you're manging span contexts manually. 10 | - The `SpanContext` signature has changed and parent span ID is now a ulong as well (this continues work released in 0.8.0). Performance should be improved for serialization to proto. 11 | - The minimum target framework is now .NET 4.7.2. Packages have been updated as well. Performance is up, memory consumption and object allocations are down! 12 | 13 | _v0.10.0_ 14 | - Refactored several interfaces to allow integrators to re-implement the reporting HttpClient more easily. 15 | - Improved version detection of the underlying CLR 16 | 17 | _v0.9.0_ 18 | - Reduce logging verbosity and log output. Several statements that appeared at DEBUG now appear at TRACE. 19 | - Improve the resolution of span start/finish timestamps. 20 | - Awaited methods should no longer block on synchronous threads. 21 | 22 | _v0.8.1_ 23 | - Update Google Protobuf to latest stable 24 | 25 | _v0.8.0_ 26 | - Improves serialization performance by removing unneeded conversions 27 | 28 | _v0.7.0_ 29 | - B3 Header propagation is case-insensitive 30 | 31 | _v0.6.0_ 32 | - Update Flush to return Task and update usage in Tracer to await it's completion 33 | - Add basic .editorconfig to help standardize file formats 34 | - Update csproj to use license expression instead of url when packaging 35 | - Improve B3 header support, including 128 bit trace IDs 36 | 37 | _v0.5.0_ 38 | - Certain span operations (`Log`, `SetBaggageItem`, `Finish`, `SetTag`, `SetOperationName`) will no longer throw `InvalidOperationException` if called on a finished span. Instead, they will log a message at the error level. 39 | 40 | _v0.4.5_ 41 | - Support v0.12.1 of OpenTracing C# 42 | - Initial support for inject/extract of binary headers (`x-ot-span-context`). See usage examples in `test/Lightstep.Tests/PropagatorTests.cs`. 43 | - Headers for Http/TextMap Propagator are now case-insensitive 44 | 45 | _v0.4.5-beta_ 46 | - Headers for Http/TextMap Propagator are now case-insensitive 47 | 48 | _v0.4.4-beta_ 49 | - Initial support for inject/extract of binary headers (`x-ot-span-context`). See usage examples in `test/Lightstep.Tests/PropagatorTests.cs`. 50 | 51 | _v0.4.3_ 52 | - Improves safety of serializing reports by moving translate method to a try/catch block. 53 | 54 | _v0.4.2_ 55 | - Addresses an issue where an unhandled exception could occur if a span duration was negative. 56 | 57 | _v0.4.1_ 58 | - Addresses an issue where we would attempt to parse all strings as JSON objects when serializing spans to the wire. We now use a more performant method to determine if an input string is JSON. 59 | 60 | _v0.4.0_ 61 | - Addresses an issue where duplicate request headers were sent to the LightStep Satellite. 62 | - Updated the default host and port of the Tracer. 63 | - Removed the requirement for an access token when creating an Options object. 64 | 65 | _v0.3.0_ 66 | - Add support for meta event reporting to the LightStep SaaS. 67 | 68 | _v0.2.0_ 69 | - In order to align with other LightStep tracer implementations, `Inject` and `Extract` methods in `TextMapPropagator` have changed: 70 | - `Inject` will now convert `TraceId` and `SpanId` to a hexadecimal string in the carrier. 71 | - `Extract` will now convert the incoming `TraceId` and `SpanId` from a hexadecimal string into a `uint64`. 72 | 73 | _v0.1.2_ 74 | - Increase the verbosity level for certain frequent operations: 75 | - Inject/Extract logging from DEBUG to TRACE 76 | - Span buffer count and reset span message from DEBUG to TRACE 77 | - Certain `LightStepHttpClient` methods were `public` rather than `internal`, this has been corrected - 78 | - `Translate` 79 | - `SendReport` 80 | 81 | _v0.1.1_ 82 | - Guard against conversion of malformed `SpanData` by dropping bad spans. 83 | 84 | _v0.1.0_ 85 | - `TransportOptions` are now available. You can select JSON or Binary Protobufs. 86 | - We now build using [Cake](https://cakebuild.net). 87 | - All value types are now properly handled in span serialization. 88 | - The platform version detection code has been improved and should be resilient to dynamic injection scenarios. 89 | - Logging now exists for many operations via the LibLog library. See the README for more details. 90 | - Error handling around dropped reports has been improved, and we now report dropped spans to LightStep. 91 | - The `LightStepHttpClient` now respects the `ReportTimeout` value. **Breaking Change** 92 | - Previously, this value was not respected by the HTTP client and was using the default 100s timeout. 93 | - The `Options` class has been refactored to support a more fluent style of configuration. **Breaking Change** 94 | - By default, `SatelliteOptions` are now created when `Options` is created that points to the LightStep public satellite pool. 95 | - Please see the readme for more information on configuring the tracer. 96 | - Tags which are passed with an empty value will now set the string value "null" when reporting to LightStep. 97 | 98 | _v0.0.8_ 99 | - Addresses an issue where the `Tracer` would not `Flush` regularly. 100 | - Addresses an issue where tags with null values would cause the tracer to not report spans. 101 | - Addresses an issue where the `LightStepHttpClient` would stop sending spans if it received a non-success status code from the LightStep Satellite. 102 | - *Change* The `LightStepHttpClient` will prefer HTTP/1.1 to HTTP/2. Change ths using `Options.UseHttp2`. 103 | - The NuGet package now includes PDB files. 104 | - The `Options` object now exposes a property `Run` that will determine if the Tracer should flush spans. 105 | 106 | _v0.0.7_ 107 | - When instantiating a `Tracer`, you can now pass additional tags in `Options` to apply to all spans. You can also override existing LightStep span tags. 108 | 109 | _v0.0.6_ 110 | - Addresses an issue where the signing key wasn't actually being used to sign the assembly. 111 | - Removes unneded `net452` target framework from LightStep project. 112 | - Reduced C# target version to 7.0 to allow builds on unpatched VS2017. 113 | 114 | _v0.0.5_ 115 | - Addresses an issue where incoming headers using base16 would cause exceptions on conversion. 116 | 117 | _v0.0.4_ 118 | - Expose a new option to force HTTP/1.1 rather than attempting to use HTTP/2 (`Options.UseHttp2`) which defaults to `true`. 119 | - The LightStep Assembly is now signed via Strong Naming. 120 | - Addresses an issue where OpenTracing Header key values were not set correctly. 121 | - Addresses an issue where span duration was improperly reported as ticks rather than microseconds. 122 | - Addresses an issue where on older .NET versions, secure connections would fail due to TLS negotiation. We now force all versions to attempt to negotiate TLS 1.2, then fall back. 123 | - Addresses an issue where rapid creation of Spans in a short timeframe belonging to the same Trace could result in Span ID collisions. 124 | 125 | _v0.0.3_ 126 | - Addresses an issue where we would always try to connect to the Satellite via `http`, even if `usePlaintext` was set to `false`. 127 | 128 | _v0.0.2_ 129 | - Add support for parsing B3 Headers (such as those used by Zipkin) 130 | - Support `net45` and `netstandard2` 131 | 132 | _v0.0.1_ 133 | - Initial Release 134 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guide 2 | 3 | First, thanks for your contribution! 🎉 4 | 5 | We gladly accept issues and pull requests to this repository. If this is a security-related issue, please email us directly at infosec@lightstep.com. 6 | 7 | Please note the following general guidelines and advice - 8 | 9 | ## Issues 10 | When submitting an issue, please include the following in addition to an explanation of the issue: 11 | - Version of the library you're using. 12 | - The runtime and platform version you're using (e.g., .NET 4.6.1 or .NET Core 2.1). 13 | - Any stack trace or diagnostic logs you may have that demonstrate the issue. 14 | 15 | ## Pull Requests 16 | Before making a pull request for a feature, please open an issue in order to gain consensus on the work you're doing. 17 | 18 | All pull requests should be rebased against `master`, and all tests should pass before a PR will be merged. 19 | 20 | In addition, ensure that: 21 | - Test coverage has been added, where appropriate. 22 | - The CHANGELOG and README have been updated. 23 | - If you are making a breaking change, indicate it in the CHANGELOG. 24 | 25 | ## Releases 26 | To make a release, commit a tag to master of the format `vmajor.minor.patch` or `vmajor.minor.patch-alpha/beta`. CircleCI should automatically build and publish the resulting artifact. 27 | 28 | ## Developing 29 | 30 | This library is intended for cross-platform usage, as well as cross-platform development. Please ensure that any dependencies added or changed fully support cross-platform .NET via Mono and .NET Core. 31 | 32 | _Development Dependencies_ 33 | - .NET Framework 4.5+ (On MacOS/Linux, you need Mono 5.16, stable channel). **If on MacOS, install Mono via its installer and not Homebrew** 34 | - .NET Core 2.1+ 35 | - Cake (see _Local Builds_) 36 | - PostSharp (Windows only, for `LightStep.CSharpAspectTestApp`) 37 | 38 | _Local Builds_ 39 | 40 | We use [Cake](https://cakebuild.net/) as a build tool. Run `dotnet tool install -g Cake.Tool` to make Cake globally available, then run `dotnet cake build.cake` to run tests. This requires .NET Core 2.1+. 41 | 42 | You should be able to use any C# development environment, such as [Visual Studio Code](https://code.visualstudio.com/) with the C# extension, [Visual Studio 2017](https://visualstudio.microsoft.com/), or [JetBrains Rider](https://www.jetbrains.com/rider/). -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 LightStep 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LightStep.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30517.126 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LightStep", "src\LightStep\LightStep.csproj", "{E5F75D2A-B882-46BB-A173-0F77E46498E2}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{BE409569-9FA2-4741-8ABF-327D56E6598E}" 9 | ProjectSection(SolutionItems) = preProject 10 | CHANGELOG.md = CHANGELOG.md 11 | README.md = README.md 12 | EndProjectSection 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{1359ACF9-43F2-4E09-94FD-32EB2C6239BA}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LightStep.Tests", "test\LightStep.Tests\LightStep.Tests.csproj", "{2DB2D4A0-64AB-4C5B-ACB5-0C2EB76D756C}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LightStep.CSharpTestApp", "examples\LightStep.CSharpTestApp\LightStep.CSharpTestApp.csproj", "{65A11CA6-E7E9-43B6-A36D-6B54EA9BD758}" 19 | EndProject 20 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{113EDBB5-081F-4581-B687-9293B4D74312}" 21 | EndProject 22 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LightStep.CSharpAspectTestApp", "examples\LightStep.CSharpAspectTestApp\LightStep.CSharpAspectTestApp.csproj", "{1B83F7CF-E2AA-4A81-9C0B-2DA716D5C982}" 23 | EndProject 24 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LightStep.CSharpDITestApp", "examples\LightStep.CSharpDITestApp\LightStep.CSharpDITestApp.csproj", "{358E688A-D19E-4149-85CD-E804738C0C3F}" 25 | EndProject 26 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LightStep.TracerPerf.Tests", "test\LightStep.TracerPerf.Tests\LightStep.TracerPerf.Tests.csproj", "{B54D212A-812F-4102-9852-15D4F084434D}" 27 | EndProject 28 | Global 29 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 30 | Debug|Any CPU = Debug|Any CPU 31 | Release|Any CPU = Release|Any CPU 32 | EndGlobalSection 33 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 34 | {E5F75D2A-B882-46BB-A173-0F77E46498E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {E5F75D2A-B882-46BB-A173-0F77E46498E2}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {E5F75D2A-B882-46BB-A173-0F77E46498E2}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {E5F75D2A-B882-46BB-A173-0F77E46498E2}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {2DB2D4A0-64AB-4C5B-ACB5-0C2EB76D756C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {2DB2D4A0-64AB-4C5B-ACB5-0C2EB76D756C}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {2DB2D4A0-64AB-4C5B-ACB5-0C2EB76D756C}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {2DB2D4A0-64AB-4C5B-ACB5-0C2EB76D756C}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {65A11CA6-E7E9-43B6-A36D-6B54EA9BD758}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {65A11CA6-E7E9-43B6-A36D-6B54EA9BD758}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {65A11CA6-E7E9-43B6-A36D-6B54EA9BD758}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {65A11CA6-E7E9-43B6-A36D-6B54EA9BD758}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {1B83F7CF-E2AA-4A81-9C0B-2DA716D5C982}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {1B83F7CF-E2AA-4A81-9C0B-2DA716D5C982}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {1B83F7CF-E2AA-4A81-9C0B-2DA716D5C982}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {1B83F7CF-E2AA-4A81-9C0B-2DA716D5C982}.Release|Any CPU.Build.0 = Release|Any CPU 50 | {358E688A-D19E-4149-85CD-E804738C0C3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 51 | {358E688A-D19E-4149-85CD-E804738C0C3F}.Debug|Any CPU.Build.0 = Debug|Any CPU 52 | {358E688A-D19E-4149-85CD-E804738C0C3F}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {358E688A-D19E-4149-85CD-E804738C0C3F}.Release|Any CPU.Build.0 = Release|Any CPU 54 | {B54D212A-812F-4102-9852-15D4F084434D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 55 | {B54D212A-812F-4102-9852-15D4F084434D}.Debug|Any CPU.Build.0 = Debug|Any CPU 56 | {B54D212A-812F-4102-9852-15D4F084434D}.Release|Any CPU.ActiveCfg = Release|Any CPU 57 | {B54D212A-812F-4102-9852-15D4F084434D}.Release|Any CPU.Build.0 = Release|Any CPU 58 | EndGlobalSection 59 | GlobalSection(SolutionProperties) = preSolution 60 | HideSolutionNode = FALSE 61 | EndGlobalSection 62 | GlobalSection(NestedProjects) = preSolution 63 | {E5F75D2A-B882-46BB-A173-0F77E46498E2} = {BE409569-9FA2-4741-8ABF-327D56E6598E} 64 | {2DB2D4A0-64AB-4C5B-ACB5-0C2EB76D756C} = {1359ACF9-43F2-4E09-94FD-32EB2C6239BA} 65 | {65A11CA6-E7E9-43B6-A36D-6B54EA9BD758} = {113EDBB5-081F-4581-B687-9293B4D74312} 66 | {1B83F7CF-E2AA-4A81-9C0B-2DA716D5C982} = {113EDBB5-081F-4581-B687-9293B4D74312} 67 | {358E688A-D19E-4149-85CD-E804738C0C3F} = {113EDBB5-081F-4581-B687-9293B4D74312} 68 | {B54D212A-812F-4102-9852-15D4F084434D} = {1359ACF9-43F2-4E09-94FD-32EB2C6239BA} 69 | EndGlobalSection 70 | GlobalSection(ExtensibilityGlobals) = postSolution 71 | SolutionGuid = {0890664C-9CE4-4960-90A4-AD6565C970E3} 72 | EndGlobalSection 73 | EndGlobal 74 | -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lightstep-tracer-csharp 2 | 3 | > ❗ **This instrumentation is no longer recommended**. Please review [documentation on setting up and configuring the OpenTelemetry client for .NET](https://github.com/open-telemetry/opentelemetry-dotnet) for more information on migrating. 4 | 5 | The LightStep distributed tracing library for C# 6 | 7 | [![NuGet](https://img.shields.io/nuget/v/LightStep.svg)](https://www.nuget.org/packages/LightStep) [![CircleCI](https://circleci.com/gh/lightstep/lightstep-tracer-csharp.svg?style=svg)](https://circleci.com/gh/lightstep/lightstep-tracer-csharp) [![codecov](https://codecov.io/gh/lightstep/lightstep-tracer-csharp/branch/master/graph/badge.svg)](https://codecov.io/gh/lightstep/lightstep-tracer-csharp) 8 | 9 | # Installation 10 | Install the package via NuGet into your solution, or use `Install-Package LightStep` / `dotnet add package LightStep`. 11 | 12 | # Basic Usage 13 | It's recommended to initialize the tracer once at the beginning of your application and assign it as the global tracer, as follows: 14 | ```c# 15 | var tracerOptions = new Options(); 16 | var tracer = new Tracer(tracerOptions); 17 | GlobalTracer.Register(tracer); 18 | ``` 19 | 20 | Once you've initialized a tracer, you can begin to create and emit spans. 21 | 22 | Please refer to the [OpenTracing C# documentation](https://github.com/opentracing/opentracing-csharp) for information on how to create spans. 23 | 24 | You can also refer to the `examples` folder for sample code and projects. 25 | 26 | # Advanced Usage 27 | 28 | There's several options that can be adjusted when instantiating a `LightStepTracer`. 29 | 30 | ## `Options` 31 | | Method | Description | 32 | | -------- | ----------- | 33 | | WithTags(IDictionary) | Default tags to apply to all spans created by the tracer. | 34 | | WithReportPeriod(TimeSpan) | How frequently the Tracer should batch and send Spans to LightStep (5s default) | 35 | | WithReportTimeout(TimeSpan) | Timeout for sending spans to the Satellite (30s default) | 36 | | WithToken(string) | The LightStep Project Access Token | 37 | | WithSatellite(SatelliteOptions) | A SatelliteOptions object that specifies the host, port, and if we should use HTTPS | 38 | | WithHttp2(bool) | If this is true, we use HTTP/2 to communicate with the Satellite. We reccomend you enable this option if you're on a modern version of .NET (4.6.1+ or .NET Core) | 39 | | WithAutomaticReporting(bool) | If false, disables the automatic flushing of buffered spans. | 40 | | WithMaxBufferedSpans(int) | The maximum amount of spans to record in a single buffer. | 41 | | WithTransport(enum) | Which transport to use when sending spans to the Satellite. | 42 | 43 | ## `SatelliteOptions` 44 | | Property | Description | 45 | | -------- | ----------- | 46 | | SatelliteHost | The hostname of a Satelite (i.e., `collector.lightstep.com`) 47 | | SatellitePort | The port number where the Satellite is listening for HTTP traffic (defaults to 443) 48 | | UsePlaintext | Should we use HTTP or HTTPS traffic? (Defaults to HTTPS) 49 | 50 | The C# Tracer will prefer TLS 1.2 when available on all .NET Runtime versions, but should fall back to TLS 1.1 or 1.0 in that order. 51 | 52 | The following is an example of overriding the LightStep Component Name and adding a new custom tag for all spans - 53 | 54 | ```csharp 55 | var satelliteOptions = new SatelliteOptions("satellite.mydomain.com"); 56 | var overrideTags = new Dictionary 57 | { 58 | {LightStepConstants.ComponentNameKey, "test_component"}, 59 | {"my_tag", "foobar"} 60 | }; 61 | var tracerOptions = new Options("TEST_TOKEN").WithSatellite(satelliteOptions).WithTags(overrideTags); 62 | var tracer = new Tracer(tracerOptions); 63 | ``` 64 | 65 | ## Logging 66 | This tracer uses [LibLog](https://github.com/damianh/LibLog), a transparent logging abstraction that provides built-in support for NLog, Log4Net, Serilog, and Loupe. 67 | If you use a logging provider that isn't identified by LibLog, see [this gist](https://gist.github.com/damianh/fa529b8346a83f7f49a9) on how to implement a custom logging provider. 68 | 69 | ## Integration Notes 70 | You may notice that there's a lot of overloads for creating a `Tracer`! You have the flexibility to override and re-implement much of this library. In 0.10.0+, the `ILightStepHttpClient` interface has 71 | been decoupled from report translation, allowing you control over the exact mechanism by which the tracer reports spans to Lightstep. You can reference [this issue](https://github.com/lightstep/lightstep-tracer-csharp/issues/92) 72 | for more information and a discussion about why you might want to do this. 73 | 74 | For most users, sticking with the defaults is fine. You should also look at the `Tracer(Options, IPropagator, ILightStepHttpClient)` ctor for custom integration - the span recorder and span translator overloads are not terribly interesting. 75 | Creating and managing propagators allows you to either select a built-in propagator (textmap, B3, etc.), create a propagator 'stack' (if you potentially have multiple input or output trace context formats), or write your own propagator. 76 | -------------------------------------------------------------------------------- /build.cake: -------------------------------------------------------------------------------- 1 | #tool "xunit.runner.console" 2 | #addin nuget:?package=Cake.Coverlet 3 | 4 | var target = Argument("target", "Default"); 5 | var configuration = Argument("configuration", "Release"); 6 | var debugConfiguration = Argument("configuration", "Debug"); 7 | var buildDir = Directory("./build"); 8 | var distDir = Directory("./dist"); 9 | var solution = "./LightStep.sln"; 10 | var library = "./src/LightStep/LightStep.csproj"; 11 | var testLib = "./test/LightStep.Tests/LightStep.Tests.csproj"; 12 | var lightStepAssemblyInfoFile = "./src/LightStep/Properties/AssemblyInfo.cs"; 13 | var version = EnvironmentVariable("CIRCLE_TAG") ?? "v0.0.0"; 14 | version = version.TrimStart('v'); 15 | var buildNo = String.IsNullOrWhiteSpace(EnvironmentVariable("CIRCLE_BUILD_NUM")) ? "0" : EnvironmentVariable("CIRCLE_BUILD_NUM"); 16 | var semVersion = string.Concat(version + "-" + buildNo); 17 | var transformedVersion = string.Concat(version + "." + buildNo); 18 | if (version.Contains("-")) 19 | { 20 | transformedVersion = string.Concat(version.Substring(0, version.LastIndexOf("-")) + "." + buildNo); 21 | } 22 | var nuGetApiKey = EnvironmentVariable("NuGet"); 23 | var testAssemblyFriendlyName = "LightStep.Tests,PublicKey=002400000480000094000000060200000024000052534131000400000100010099deeadb052e9763d2dc7827700d80e349e5d16585c92416171e6689a4bd38a3acea971d5899d5e2cd4239c3dc799558138e961f8d0f5095fef969672172833868f2cc2d908970370af834beef9dad328182fee2aaf0d0bb568ffd1f829362b88718734541d334c6a2cdf0049f5a0ee5e4962d0db3f49f86bf742f9531bd9c8c"; 24 | 25 | Task("Clean") 26 | .Does( ()=> 27 | { 28 | CleanDirectory(buildDir); 29 | CleanDirectory(distDir); 30 | CleanDirectories("./**/obj/*.*"); 31 | CleanDirectories($"./**/bin/{configuration}/*.*"); 32 | CleanDirectories($"./**/bin/{debugConfiguration}/*.*"); 33 | }); 34 | 35 | Task("Restore") 36 | .IsDependentOn("Clean") 37 | .Does( ()=> 38 | { 39 | DotNetCoreRestore(library); 40 | DotNetCoreRestore(testLib); 41 | }); 42 | 43 | Task("Build") 44 | .IsDependentOn("Restore") 45 | .Does(() => 46 | { 47 | CreateAssemblyInfo(lightStepAssemblyInfoFile, new AssemblyInfoSettings { 48 | Product = "LightStep", 49 | Version = transformedVersion, 50 | FileVersion = transformedVersion, 51 | InformationalVersion = version, 52 | Copyright = string.Format("Copyright (c) LightStep 2018 - {0}", DateTime.Now.Year), 53 | InternalsVisibleTo = new List() { testAssemblyFriendlyName } 54 | }); 55 | var assemblyInfo = ParseAssemblyInfo(lightStepAssemblyInfoFile); 56 | Information("Version: {0}", assemblyInfo.AssemblyVersion); 57 | Information("File version: {0}", assemblyInfo.AssemblyFileVersion); 58 | Information("Informational version: {0}", assemblyInfo.AssemblyInformationalVersion); 59 | MSBuild(library, settings => settings 60 | .SetConfiguration(configuration) 61 | .WithTarget("Rebuild") 62 | .WithProperty("Version", assemblyInfo.AssemblyInformationalVersion) 63 | .SetVerbosity(Verbosity.Minimal)); 64 | }); 65 | 66 | Task("Test") 67 | .IsDependentOn("Build") 68 | .Does(() => 69 | { 70 | var unitProject = "./test/LightStep.Tests/LightStep.Tests.csproj"; 71 | var coverletSettings = new CoverletSettings { 72 | CollectCoverage = true, 73 | CoverletOutputFormat = CoverletOutputFormat.opencover, 74 | CoverletOutputDirectory = Directory("./build"), 75 | CoverletOutputName = $"coverage.xml", 76 | ExcludeByFile = { "../../src/LightStep/Collector/Collector.cs", "../../src/LightStep/LightStep.cs" } 77 | }; 78 | DotNetCoreTest(unitProject, new DotNetCoreTestSettings { 79 | Logger = "xunit;LogFilePath=../../build/test_results.xml" 80 | }, coverletSettings); 81 | }); 82 | 83 | Task("Publish") 84 | .IsDependentOn("Test") 85 | .WithCriteria(() => EnvironmentVariable("CI") == "true") 86 | .Does(() => 87 | { 88 | var nupkg = GetFiles("./src/LightStep/bin/Release/*.nupkg").First(); 89 | DotNetCoreNuGetPush(nupkg.FullPath, new DotNetCoreNuGetPushSettings { 90 | Source = "https://www.nuget.org/api/v2/package", 91 | ApiKey = nuGetApiKey 92 | }); 93 | }); 94 | 95 | Task("Default") 96 | .IsDependentOn("Test"); 97 | 98 | RunTarget(target); -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ########################################################################## 4 | # This is the Cake bootstrapper script for Linux and OS X. 5 | # This file was downloaded from https://github.com/cake-build/resources 6 | # Feel free to change this file to fit your needs. 7 | ########################################################################## 8 | 9 | # Define directories. 10 | SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) 11 | TOOLS_DIR=$SCRIPT_DIR/tools 12 | ADDINS_DIR=$TOOLS_DIR/Addins 13 | MODULES_DIR=$TOOLS_DIR/Modules 14 | NUGET_EXE=$TOOLS_DIR/nuget.exe 15 | CAKE_EXE=$TOOLS_DIR/Cake/Cake.exe 16 | PACKAGES_CONFIG=$TOOLS_DIR/packages.config 17 | PACKAGES_CONFIG_MD5=$TOOLS_DIR/packages.config.md5sum 18 | ADDINS_PACKAGES_CONFIG=$ADDINS_DIR/packages.config 19 | MODULES_PACKAGES_CONFIG=$MODULES_DIR/packages.config 20 | 21 | # Define md5sum or md5 depending on Linux/OSX 22 | MD5_EXE= 23 | if [[ "$(uname -s)" == "Darwin" ]]; then 24 | MD5_EXE="md5 -r" 25 | else 26 | MD5_EXE="md5sum" 27 | fi 28 | 29 | # Define default arguments. 30 | SCRIPT="build.cake" 31 | CAKE_ARGUMENTS=() 32 | 33 | # Parse arguments. 34 | for i in "$@"; do 35 | case $1 in 36 | -s|--script) SCRIPT="$2"; shift ;; 37 | --) shift; CAKE_ARGUMENTS+=("$@"); break ;; 38 | *) CAKE_ARGUMENTS+=("$1") ;; 39 | esac 40 | shift 41 | done 42 | 43 | # Make sure the tools folder exist. 44 | if [ ! -d "$TOOLS_DIR" ]; then 45 | mkdir "$TOOLS_DIR" 46 | fi 47 | 48 | # Make sure that packages.config exist. 49 | if [ ! -f "$TOOLS_DIR/packages.config" ]; then 50 | echo "Downloading packages.config..." 51 | curl -Lsfo "$TOOLS_DIR/packages.config" https://cakebuild.net/download/bootstrapper/packages 52 | if [ $? -ne 0 ]; then 53 | echo "An error occurred while downloading packages.config." 54 | exit 1 55 | fi 56 | fi 57 | 58 | # Download NuGet if it does not exist. 59 | if [ ! -f "$NUGET_EXE" ]; then 60 | echo "Downloading NuGet..." 61 | curl -Lsfo "$NUGET_EXE" https://dist.nuget.org/win-x86-commandline/latest/nuget.exe 62 | if [ $? -ne 0 ]; then 63 | echo "An error occurred while downloading nuget.exe." 64 | exit 1 65 | fi 66 | fi 67 | 68 | # Restore tools from NuGet. 69 | pushd "$TOOLS_DIR" >/dev/null 70 | if [ ! -f "$PACKAGES_CONFIG_MD5" ] || [ "$( cat "$PACKAGES_CONFIG_MD5" | sed 's/\r$//' )" != "$( $MD5_EXE "$PACKAGES_CONFIG" | awk '{ print $1 }' )" ]; then 71 | find . -type d ! -name . ! -name 'Cake.Bakery' | xargs rm -rf 72 | fi 73 | 74 | mono "$NUGET_EXE" install -ExcludeVersion 75 | if [ $? -ne 0 ]; then 76 | echo "Could not restore NuGet tools." 77 | exit 1 78 | fi 79 | 80 | $MD5_EXE "$PACKAGES_CONFIG" | awk '{ print $1 }' >| "$PACKAGES_CONFIG_MD5" 81 | 82 | popd >/dev/null 83 | 84 | # Restore addins from NuGet. 85 | if [ -f "$ADDINS_PACKAGES_CONFIG" ]; then 86 | pushd "$ADDINS_DIR" >/dev/null 87 | 88 | mono "$NUGET_EXE" install -ExcludeVersion 89 | if [ $? -ne 0 ]; then 90 | echo "Could not restore NuGet addins." 91 | exit 1 92 | fi 93 | 94 | popd >/dev/null 95 | fi 96 | 97 | # Restore modules from NuGet. 98 | if [ -f "$MODULES_PACKAGES_CONFIG" ]; then 99 | pushd "$MODULES_DIR" >/dev/null 100 | 101 | mono "$NUGET_EXE" install -ExcludeVersion 102 | if [ $? -ne 0 ]; then 103 | echo "Could not restore NuGet modules." 104 | exit 1 105 | fi 106 | 107 | popd >/dev/null 108 | fi 109 | 110 | # Make sure that Cake has been installed. 111 | if [ ! -f "$CAKE_EXE" ]; then 112 | echo "Could not find Cake.exe at '$CAKE_EXE'." 113 | exit 1 114 | fi 115 | 116 | # Start Cake 117 | exec mono "$CAKE_EXE" $SCRIPT "${CAKE_ARGUMENTS[@]}" 118 | -------------------------------------------------------------------------------- /examples/LightStep.CSharpAspectTestApp/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/LightStep.CSharpAspectTestApp/Aspects/Traceable.cs: -------------------------------------------------------------------------------- 1 | using OpenTracing.Util; 2 | using PostSharp.Aspects; 3 | using PostSharp.Serialization; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace LightStep.CSharpAspectTestApp.Aspects 11 | { 12 | [PSerializable] 13 | public sealed class Traceable : OnMethodBoundaryAspect 14 | { 15 | public override void OnEntry(MethodExecutionArgs args) 16 | { 17 | // when we enter a method, build a new span and mark it as active 18 | var scope = GlobalTracer.Instance.BuildSpan(args.Method.Name).StartActive(); 19 | Console.WriteLine($"new span id {scope.Span.Context.SpanId}"); 20 | } 21 | 22 | public override void OnSuccess(MethodExecutionArgs args) 23 | { 24 | // only finish a span in OnExit, as it gets called as a finalizer 25 | Console.WriteLine("The {0} method executed successfully.", args.Method.Name); 26 | } 27 | 28 | public override void OnExit(MethodExecutionArgs args) 29 | { 30 | // when a method exits (either successfully or unsuccessfully), dispose of the scope to free it on our singleton tracer 31 | var scope = GlobalTracer.Instance.ScopeManager.Active; 32 | Console.WriteLine($"finishing span id {scope.Span.Context.SpanId}"); 33 | scope.Dispose(); 34 | } 35 | 36 | public override void OnException(MethodExecutionArgs args) 37 | { 38 | // if an exception occurs, get the active scope then set the exception log, add tags, whatever. 39 | var scope = GlobalTracer.Instance.ScopeManager.Active; 40 | Console.WriteLine("An exception was thrown in {0}.", args.Method.Name); 41 | scope.Span.Log(args.Exception.StackTrace); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /examples/LightStep.CSharpAspectTestApp/HttpWorker.cs: -------------------------------------------------------------------------------- 1 | using LightStep.CSharpAspectTestApp.Aspects; 2 | using OpenTracing.Util; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Net.Http; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace LightStep.CSharpAspectTestApp 11 | { 12 | class HttpWorker 13 | { 14 | private HttpClient httpClient; 15 | 16 | public HttpWorker() 17 | { 18 | httpClient = new HttpClient(); 19 | } 20 | 21 | [Traceable] 22 | public async void Get(string url) 23 | { 24 | GlobalTracer.Instance.ActiveSpan.SetTag("args", url); 25 | var content = await GetString(url); 26 | Write(content); 27 | } 28 | 29 | [Traceable] 30 | public static void Write(string content) 31 | { 32 | Console.WriteLine(content); 33 | } 34 | 35 | [Traceable] 36 | public async Task GetString(string url) 37 | { 38 | var content = await httpClient.GetStringAsync(url); 39 | GlobalTracer.Instance.ActiveSpan.Log(content); 40 | return content; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /examples/LightStep.CSharpAspectTestApp/LightStep.CSharpAspectTestApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | Debug 7 | AnyCPU 8 | {1B83F7CF-E2AA-4A81-9C0B-2DA716D5C982} 9 | Exe 10 | LightStep.CSharpAspectTestApp 11 | LightStep.CSharpAspectTestApp 12 | v4.6.1 13 | 512 14 | true 15 | 16 | 17 | 18 | 19 | AnyCPU 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | 28 | 29 | AnyCPU 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | ..\..\packages\OpenTracing.0.12.1-rc4\lib\net45\OpenTracing.dll 40 | True 41 | 42 | 43 | ..\..\packages\PostSharp.Redist.6.0.27\lib\net45\PostSharp.dll 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | {e5f75d2a-b882-46bb-a173-0f77e46498e2} 72 | LightStep 73 | 74 | 75 | 76 | 77 | 78 | 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}. 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /examples/LightStep.CSharpAspectTestApp/Program.cs: -------------------------------------------------------------------------------- 1 | using LightStep.CSharpAspectTestApp.Aspects; 2 | using LightStep; 3 | using OpenTracing.Util; 4 | using System; 5 | using System.Configuration; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace LightStep.CSharpAspectTestApp 12 | { 13 | class Program 14 | { 15 | static void Main(string[] args) 16 | { 17 | // create your tracer options, initialize it, assign it to the GlobalTracer 18 | var lsKey = Environment.GetEnvironmentVariable("LS_KEY"); 19 | var lsSettings = new SatelliteOptions("collector.lightstep.com"); 20 | var lsOptions = new Options(lsKey).WithSatellite(lsSettings); 21 | var tracer = new Tracer(lsOptions); 22 | 23 | GlobalTracer.Register(tracer); 24 | 25 | // do some work in parallel, this work also includes awaited calls 26 | Parallel.For(1, 100, i => DoThing(i)); 27 | 28 | // block until you enter a key 29 | Console.ReadKey(); 30 | } 31 | 32 | [Traceable] 33 | static void DoThing(int idx) 34 | { 35 | GlobalTracer.Instance.ActiveSpan.SetTag("args", idx); 36 | var client = new HttpWorker(); 37 | client.Get($"https://jsonplaceholder.typicode.com/todos/{idx}"); 38 | } 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/LightStep.CSharpAspectTestApp/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("LightStep.CSharpAspectTestApp")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("LightStep.CSharpAspectTestApp")] 13 | [assembly: AssemblyCopyright("Copyright © 2018")] 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("1b83f7cf-e2aa-4a81-9c0b-2da716d5c982")] 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("0.1.29.0")] 35 | [assembly: AssemblyVersion("0.1.29.0")] 36 | [assembly: AssemblyFileVersion("0.1.29.0")] 37 | 38 | [assembly: AssemblyInformationalVersion("0.1.29-enhancement-addCake.1-2018-11-12")] 39 | -------------------------------------------------------------------------------- /examples/LightStep.CSharpAspectTestApp/README.md: -------------------------------------------------------------------------------- 1 | # Tracing With Aspects 2 | 3 | This is a small sample application demonstrating how to use an Aspect framework (such as PostSharp) to apply Tracing to methods. 4 | 5 | ## Installation and Setup 6 | 7 | Set the `LS_KEY` environment variable to your project's API Key. 8 | 9 | **Important**: This sample will only build on Windows platforms, as PostSharp does not support all CoreCLR targets for compilation. -------------------------------------------------------------------------------- /examples/LightStep.CSharpAspectTestApp/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/LightStep.CSharpDITestApp/LightStep.CSharpDITestApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {358E688A-D19E-4149-85CD-E804738C0C3F} 8 | Exe 9 | Properties 10 | LightStep.CSharpDITestApp 11 | LightStep.CSharpDITestApp 12 | v4.7.1 13 | 512 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | ..\..\packages\Castle.Core.4.2.0\lib\net45\Castle.Core.dll 38 | True 39 | 40 | 41 | ..\..\packages\Castle.Windsor.4.1.1\lib\net45\Castle.Windsor.dll 42 | True 43 | 44 | 45 | ..\..\packages\OpenTracing.0.12.1-rc4\lib\net45\OpenTracing.dll 46 | True 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | {e5f75d2a-b882-46bb-a173-0f77e46498e2} 66 | LightStep 67 | 68 | 69 | 70 | 77 | -------------------------------------------------------------------------------- /examples/LightStep.CSharpDITestApp/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using Castle.MicroKernel.Registration; 4 | using Castle.Windsor; 5 | using OpenTracing; 6 | using LightStep; 7 | 8 | namespace LightStep.CSharpDITestApp 9 | { 10 | internal class Program 11 | { 12 | public static void Main(string[] args) 13 | { 14 | // replace these options with your satellite and api key 15 | var tracerOptions = new Options("TEST_TOKEN").WithSatellite(new SatelliteOptions("localhost", 9996, true)); 16 | var container = new WindsorContainer(); 17 | // during registration, pass your options to the concrete LightStep Tracer implementation 18 | container.Register(Component.For().ImplementedBy().DependsOn(Dependency.OnValue("options", tracerOptions))); 19 | var tracer = container.Resolve(); 20 | 21 | // create some spans 22 | for (var i = 0; i < 500; i++) 23 | using (var scope = tracer.BuildSpan("testParent").WithTag("testSpan", "true").StartActive(true)) 24 | { 25 | scope.Span.Log("test"); 26 | tracer.ActiveSpan.Log($"iteration {i}"); 27 | Console.WriteLine("sleeping for a bit"); 28 | Thread.Sleep(new Random().Next(5, 10)); 29 | var innerSpan = tracer.BuildSpan("childSpan").Start(); 30 | innerSpan.SetTag("innerTestTag", "true"); 31 | Console.WriteLine("sleeping more..."); 32 | Thread.Sleep(new Random().Next(10, 20)); 33 | innerSpan.Finish(); 34 | } 35 | 36 | // note that OpenTracing.ITracer does not have flush as a method, so to manually flush you'll need to 37 | // get a cast of the tracer. 38 | Tracer t = (Tracer) tracer; 39 | t.Flush(); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /examples/LightStep.CSharpDITestApp/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("LightStep.CSharpDITestApp")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("LightStep.CSharpDITestApp")] 12 | [assembly: AssemblyCopyright("Copyright © 2018")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("358E688A-D19E-4149-85CD-E804738C0C3F")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] -------------------------------------------------------------------------------- /examples/LightStep.CSharpDITestApp/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/LightStep.CSharpTestApp/LightStep.CSharpTestApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | netcoreapp2.1 5 | LightStep.CSharpTestApp 6 | LightStep.CSharpTestApp 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ..\..\..\..\.nuget\packages\opentracing\0.12.0\lib\net45\OpenTracing.dll 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/LightStep.CSharpTestApp/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading; 4 | using Google.Protobuf.WellKnownTypes; 5 | using OpenTracing.Util; 6 | using global::Serilog; 7 | using Serilog.Events; 8 | using Serilog.Sinks.SystemConsole; 9 | 10 | namespace LightStep.CSharpTestApp 11 | { 12 | internal class Program 13 | { 14 | private static void Main(string[] args) 15 | { 16 | Log.Logger = new LoggerConfiguration() 17 | .MinimumLevel.Debug() 18 | .WriteTo.Console() 19 | .CreateLogger(); 20 | 21 | var tracer = new Tracer(new Options()); 22 | GlobalTracer.Register(tracer); 23 | 24 | for (var i = 0; i < 500; i++) 25 | using (var scope = tracer.BuildSpan("testParent").WithTag("testSpan", "true").StartActive(true)) 26 | { 27 | scope.Span.Log("test"); 28 | tracer.ActiveSpan.Log($"iteration {i}"); 29 | 30 | Thread.Sleep(new Random().Next(5, 10)); 31 | var innerSpan = tracer.BuildSpan("childSpan").Start(); 32 | innerSpan.SetTag("innerTestTag", "true"); 33 | 34 | Thread.Sleep(new Random().Next(10, 20)); 35 | innerSpan.Finish(); 36 | } 37 | tracer.Flush(); 38 | Console.ReadKey(); 39 | } 40 | 41 | } 42 | } -------------------------------------------------------------------------------- /ls128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lightstep/lightstep-tracer-csharp/59c3e63658fd69bf732ddc019e90607d9b014c11/ls128x128.png -------------------------------------------------------------------------------- /src/LightStep/Baggage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace LightStep 5 | { 6 | /// 7 | /// Baggage is a dictionary of key:value information that is propagated via the SpanContext. 8 | /// 9 | public class Baggage 10 | { 11 | private readonly IDictionary _items = 12 | new Dictionary(StringComparer.OrdinalIgnoreCase); 13 | 14 | /// 15 | /// Sets key to value. 16 | /// 17 | /// 18 | /// 19 | public void Set(string key, string value) 20 | { 21 | _items[key] = value; 22 | } 23 | 24 | /// 25 | /// Gets value for key; Will return null if key does not exist. 26 | /// 27 | /// 28 | /// 29 | public string Get(string key) 30 | { 31 | string value; 32 | return _items.TryGetValue(key, out value) ? value : null; 33 | } 34 | 35 | /// 36 | /// Gets all from baggage. 37 | /// 38 | /// 39 | public IEnumerable> GetAll() 40 | { 41 | return _items; 42 | } 43 | 44 | /// 45 | /// Combines two baggage into one. 46 | /// 47 | /// 48 | public void Merge(Baggage other) 49 | { 50 | Merge(other?.GetAll()); 51 | } 52 | 53 | private void Merge(IEnumerable> other) 54 | { 55 | if (other == null) 56 | return; 57 | 58 | // Copy entries into local dictionary instead of setting it directly 59 | // to make sure the case insensitive comparer is used. 60 | foreach (var kvp in other) Set(kvp.Key, kvp.Value); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /src/LightStep/Collector/ILightStepHttpClient.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using System.Threading.Tasks; 3 | 4 | namespace LightStep.Collector 5 | { 6 | public interface ILightStepHttpClient 7 | { 8 | /// 9 | /// Send a report of spans to the LightStep Satellite. 10 | /// 11 | /// An 12 | /// A . This is usually not very interesting. 13 | Task SendReport(ReportRequest report); 14 | } 15 | } -------------------------------------------------------------------------------- /src/LightStep/Collector/IReportTranslator.cs: -------------------------------------------------------------------------------- 1 | namespace LightStep.Collector 2 | { 3 | public interface IReportTranslator 4 | { 5 | /// 6 | /// Translate SpanData to a protobuf ReportRequest for sending to the Satellite. 7 | /// 8 | /// An enumerable of 9 | /// A 10 | ReportRequest Translate(ISpanRecorder spanBuffer); 11 | } 12 | } -------------------------------------------------------------------------------- /src/LightStep/Collector/LightStepHttpClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Net.Http; 6 | using System.Net.Http.Headers; 7 | using System.Threading.Tasks; 8 | using Google.Protobuf; 9 | using LightStep.Logging; 10 | using Google.Protobuf.WellKnownTypes; 11 | 12 | namespace LightStep.Collector 13 | { 14 | /// 15 | /// Contains methods to communicate to a LightStep Satellite via Proto over HTTP. 16 | /// 17 | public class LightStepHttpClient : ILightStepHttpClient 18 | { 19 | private readonly Options _options; 20 | private HttpClient _client; 21 | private readonly string _url; 22 | private static readonly ILog _logger = LogProvider.GetCurrentClassLogger(); 23 | 24 | /// 25 | /// Create a new client. 26 | /// 27 | /// URL to send results to. 28 | /// An object. 29 | /// An object. If none is provide, a default client will be used. 30 | public LightStepHttpClient(string url, Options options) 31 | { 32 | _url = url; 33 | _options = options; 34 | _client = new HttpClient() {Timeout = _options.ReportTimeout}; 35 | } 36 | 37 | internal HttpRequestMessage CreateStringRequest(ReportRequest report) 38 | { 39 | var jsonFormatter = new JsonFormatter(new JsonFormatter.Settings(true)); 40 | var jsonReport = jsonFormatter.Format(report); 41 | var request = new HttpRequestMessage(HttpMethod.Post, _url) 42 | { 43 | Version = _options.UseHttp2 ? new Version(2, 0) : new Version(1, 1), 44 | Content = new StringContent(jsonReport) 45 | }; 46 | request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); 47 | return request; 48 | } 49 | 50 | internal HttpRequestMessage CreateBinaryRequest(ReportRequest report) 51 | { 52 | var binaryReport = report.ToByteArray(); 53 | var request = new HttpRequestMessage(HttpMethod.Post, _url) 54 | { 55 | Version = _options.UseHttp2 ? new Version(2, 0) : new Version(1, 1), 56 | Content = new ByteArrayContent(binaryReport) 57 | }; 58 | request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); 59 | return request; 60 | } 61 | 62 | internal HttpRequestMessage BuildRequest(ReportRequest report) 63 | { 64 | HttpRequestMessage requestMessage = (_options.Transport & TransportOptions.JsonProto) != 0 ? CreateStringRequest(report) : CreateBinaryRequest(report); 65 | 66 | // add LightStep access token to request header 67 | requestMessage.Content.Headers.Add(LightStepConstants.AccessTokenConstant, report.Auth.AccessToken); 68 | 69 | return requestMessage; 70 | 71 | } 72 | 73 | /// 74 | /// Send a report of spans to the LightStep Satellite. 75 | /// 76 | /// An 77 | /// A . This is usually not very interesting. 78 | [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] 79 | public async Task SendReport(ReportRequest report) 80 | { 81 | // force net45 to attempt tls12 first and fallback appropriately 82 | ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; 83 | 84 | _client.DefaultRequestHeaders.Accept.Clear(); 85 | _client.DefaultRequestHeaders.Accept.Add( 86 | new MediaTypeWithQualityHeaderValue("application/octet-stream")); 87 | 88 | var requestMessage = BuildRequest(report); 89 | 90 | ReportResponse responseValue; 91 | 92 | try 93 | { 94 | var response = await _client.SendAsync(requestMessage); 95 | response.EnsureSuccessStatusCode(); 96 | var responseData = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); 97 | responseValue = ReportResponse.Parser.ParseFrom(responseData); 98 | _logger.Trace($"Report HTTP Response {response.StatusCode}"); 99 | } 100 | catch (HttpRequestException ex) 101 | { 102 | _logger.WarnException("Exception caught while sending report, resetting HttpClient", ex); 103 | _client.Dispose(); 104 | _client = new HttpClient {Timeout = _options.ReportTimeout}; 105 | throw; 106 | } 107 | catch (Exception ex) when (ex is TaskCanceledException || ex is OperationCanceledException) 108 | { 109 | _logger.WarnException("Timed out sending report to satellite", ex); 110 | throw; 111 | } 112 | catch (Exception ex) 113 | { 114 | _logger.WarnException("Unknown error sending report.", ex); 115 | throw; 116 | } 117 | 118 | return responseValue; 119 | } 120 | } 121 | } -------------------------------------------------------------------------------- /src/LightStep/Collector/ProtoConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Google.Protobuf.WellKnownTypes; 5 | 6 | namespace LightStep.Collector 7 | { 8 | /// 9 | public partial class SpanContext 10 | { 11 | /// 12 | /// Converts a to a 13 | /// 14 | /// A SpanContext 15 | /// Proto SpanContext 16 | public SpanContext MakeSpanContextFromOtSpanContext(LightStep.SpanContext ctx) 17 | { 18 | SpanId = ctx.SpanIdValue; 19 | TraceId = ctx.TraceIdValue; 20 | 21 | ctx.GetBaggageItems().ToList().ForEach(baggage => Baggage.Add(baggage.Key, baggage.Value)); 22 | return this; 23 | } 24 | } 25 | 26 | /// 27 | public partial class Span 28 | { 29 | const long TicksPerMicrosecond = 10; 30 | /// 31 | /// Converts a to a 32 | /// 33 | /// A SpanData 34 | /// Proto Span 35 | public Span MakeSpanFromSpanData(SpanData span) 36 | { 37 | // ticks are not equal to microseconds, so convert 38 | DurationMicros = Convert.ToUInt64(Math.Abs(span.Duration.Ticks) / TicksPerMicrosecond); 39 | OperationName = span.OperationName; 40 | SpanContext = new SpanContext().MakeSpanContextFromOtSpanContext(span.Context); 41 | StartTimestamp = Timestamp.FromDateTime(span.StartTimestamp.UtcDateTime); 42 | foreach (var logData in span.LogData) Logs.Add(new Log().MakeLogFromLogData(logData)); 43 | foreach (var keyValuePair in span.Tags) Tags.Add(new KeyValue().MakeKeyValueFromKvp(keyValuePair)); 44 | 45 | if (span.Context.ParentSpanIdValue != 0L) 46 | References.Add(Reference.MakeReferenceFromParentSpanIdValue(span.Context.ParentSpanIdValue)); 47 | 48 | return this; 49 | } 50 | } 51 | 52 | /// 53 | public partial class Reference 54 | { 55 | /// 56 | /// Converts a ParentSpanId string into a 57 | /// 58 | /// A ParentSpanId as a string 59 | /// Proto Reference 60 | public static Reference MakeReferenceFromParentSpanId(string id) 61 | { 62 | var reference = new Reference(); 63 | reference.Relationship = Types.Relationship.ChildOf; 64 | ulong spanId; 65 | try 66 | { 67 | spanId = Convert.ToUInt64(id); 68 | } 69 | catch (FormatException) 70 | { 71 | spanId = Convert.ToUInt64(id, 16); 72 | } 73 | reference.SpanContext = new SpanContext {SpanId = spanId}; 74 | 75 | return reference; 76 | } 77 | 78 | public static Reference MakeReferenceFromParentSpanIdValue(ulong value) 79 | { 80 | var reference = new Reference 81 | { 82 | Relationship = Types.Relationship.ChildOf, SpanContext = new SpanContext {SpanId = value} 83 | }; 84 | return reference; 85 | } 86 | } 87 | 88 | /// 89 | public partial class Log 90 | { 91 | /// 92 | /// Converts a into a 93 | /// 94 | /// A LogData object 95 | /// Proto Log 96 | public Log MakeLogFromLogData(LogData log) 97 | { 98 | Timestamp = Timestamp.FromDateTime(log.Timestamp.DateTime.ToUniversalTime()); 99 | foreach (var keyValuePair in log.Fields) Fields.Add(new KeyValue().MakeKeyValueFromKvp(keyValuePair)); 100 | 101 | return this; 102 | } 103 | } 104 | 105 | /// 106 | public partial class KeyValue 107 | { 108 | /// 109 | /// Converts a into a 110 | /// 111 | /// A KeyValuePair used in Tags, etc. 112 | /// Proto KeyValue 113 | public KeyValue MakeKeyValueFromKvp(KeyValuePair input) 114 | { 115 | Key = input.Key; 116 | if (input.Value == null) StringValue = "null"; 117 | else if (input.Value.IsFloatDataType()) DoubleValue = Convert.ToDouble(input.Value); 118 | else if (input.Value.IsIntDataType()) IntValue = Convert.ToInt64(input.Value); 119 | else if (input.Value.IsBooleanDataType()) BoolValue = Convert.ToBoolean(input.Value); 120 | else if (input.Value.IsJson()) JsonValue = Convert.ToString(input.Value); 121 | else StringValue = Convert.ToString(input.Value); 122 | 123 | return new KeyValue(this); 124 | } 125 | } 126 | } -------------------------------------------------------------------------------- /src/LightStep/Collector/ProtoConverterExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Json; 3 | 4 | namespace LightStep.Collector 5 | { 6 | /// 7 | /// Helper methods for the ProtoConverter 8 | /// 9 | public static class ProtoConverterExtensions 10 | { 11 | /// 12 | /// Extension method to determine if an object is a number or number-like value. 13 | /// 14 | /// Any value. 15 | /// True if the value is a number or number-like value, false otherwise. 16 | public static bool IsIntDataType(this object value) 17 | { 18 | var v = value is sbyte 19 | || value is byte 20 | || value is short 21 | || value is ushort 22 | || value is int 23 | || value is uint 24 | || value is long 25 | || value is ulong; 26 | return v; 27 | } 28 | 29 | public static bool IsFloatDataType(this object value) 30 | { 31 | var v = value is float || value is double || value is decimal; 32 | return v; 33 | } 34 | 35 | /// 36 | /// Extension method to determine if an object is a boolean value. 37 | /// 38 | /// Any value. 39 | /// True is the value is a boolean value, false otherwise. 40 | public static bool IsBooleanDataType(this object value) 41 | { 42 | var v = value is bool; 43 | return v; 44 | } 45 | 46 | public static bool IsJson(this object value) 47 | { 48 | if (value is String) { 49 | var st = (String)value; 50 | st = st.Trim(' ').Trim('"'); 51 | if (st.Length > 0) { 52 | return (st[0] == '{' || st[0] == '[') && (st[st.Length -1] == '}' || st[st.Length - 1] == ']'); 53 | } 54 | } 55 | return false; 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /src/LightStep/Collector/ReportTranslator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Google.Protobuf.WellKnownTypes; 8 | using LightStep.Logging; 9 | 10 | namespace LightStep.Collector 11 | { 12 | class ReportTranslator : IReportTranslator 13 | { 14 | private readonly Options _options; 15 | private static readonly ILog _logger = LogProvider.GetCurrentClassLogger(); 16 | 17 | public ReportTranslator(Options options) 18 | { 19 | _options = options; 20 | } 21 | 22 | /// 23 | /// Translate SpanData to a protobuf ReportRequest for sending to the Satellite. 24 | /// 25 | /// An enumerable of 26 | /// A 27 | public ReportRequest Translate(ISpanRecorder spanBuffer) 28 | { 29 | _logger.Trace($"Serializing {spanBuffer.GetSpanCount()} spans to proto."); 30 | var timer = new Stopwatch(); 31 | timer.Start(); 32 | 33 | var request = new ReportRequest 34 | { 35 | Reporter = new Reporter 36 | { 37 | ReporterId = _options.TracerGuid 38 | }, 39 | Auth = new Auth { AccessToken = _options.AccessToken } 40 | }; 41 | _options.Tags.ToList().ForEach(t => request.Reporter.Tags.Add(new KeyValue().MakeKeyValueFromKvp(t))); 42 | spanBuffer.GetSpans().ToList().ForEach(span => { 43 | try 44 | { 45 | request.Spans.Add(new Span().MakeSpanFromSpanData(span)); 46 | } 47 | catch (Exception ex) 48 | { 49 | _logger.WarnException("Caught exception converting spans.", ex); 50 | spanBuffer.DroppedSpanCount++; 51 | } 52 | }); 53 | 54 | var metrics = new InternalMetrics 55 | { 56 | StartTimestamp = Timestamp.FromDateTime(spanBuffer.ReportStartTime.ToUniversalTime()), 57 | DurationMicros = Convert.ToUInt64((spanBuffer.ReportEndTime - spanBuffer.ReportStartTime).Ticks / 10), 58 | Counts = { new MetricsSample() { Name = "spans.dropped", IntValue = spanBuffer.DroppedSpanCount } } 59 | }; 60 | request.InternalMetrics = metrics; 61 | 62 | timer.Stop(); 63 | _logger.Trace($"Serialization complete in {timer.ElapsedMilliseconds}ms. Request size: {request.CalculateSize()}b."); 64 | 65 | return request; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/LightStep/HighResolutionDateTime.cs: -------------------------------------------------------------------------------- 1 | // this code from https://raw.githubusercontent.com/openzipkin/zipkin4net/master/Src/zipkin4net/Src/Utils/HighResolutionDateTime.cs 2 | using System; 3 | #if !NET_CORE 4 | using System.Runtime.InteropServices; 5 | #endif 6 | 7 | namespace LightStep 8 | { 9 | internal static class HighResolutionDateTime 10 | { 11 | #if NET_CORE 12 | public static bool IsAvailable { get { return false; } } 13 | #else 14 | public static bool IsAvailable { get; private set; } 15 | #endif 16 | 17 | #if !NET_CORE 18 | [DllImport("Kernel32.dll", CallingConvention = CallingConvention.Winapi)] 19 | private static extern void GetSystemTimePreciseAsFileTime(out long filetime); 20 | #endif 21 | 22 | public static DateTime UtcNow 23 | { 24 | get 25 | { 26 | #if !NET_CORE 27 | if (!IsAvailable) 28 | { 29 | throw new InvalidOperationException("High resolution clock isn't available."); 30 | } 31 | 32 | long filetime; 33 | GetSystemTimePreciseAsFileTime(out filetime); 34 | 35 | return DateTime.FromFileTimeUtc(filetime); 36 | #else 37 | throw new NotImplementedException(); 38 | #endif 39 | } 40 | } 41 | 42 | #if !NET_CORE 43 | static HighResolutionDateTime() 44 | { 45 | if (!HasSufficientWindowsVersion(Environment.OSVersion.Platform, Environment.OSVersion.Version)) 46 | { 47 | IsAvailable = false; 48 | return; 49 | } 50 | 51 | // Make sure the API is actually available 52 | try 53 | { 54 | long filetime; 55 | GetSystemTimePreciseAsFileTime(out filetime); 56 | IsAvailable = true; 57 | } 58 | catch (EntryPointNotFoundException) 59 | { 60 | IsAvailable = false; 61 | } 62 | } 63 | 64 | /// 65 | /// Check whether the given OS is Windows 8, Windows Server 2012 or above. 66 | /// 67 | /// PlatformId on which the application is currently running 68 | /// Windows version on which the application is currently running 69 | /// 70 | private static bool HasSufficientWindowsVersion(PlatformID platformId, Version windowsVersion) 71 | { 72 | var minimumRequiredVersion = new Version(6, 2, 9200, 0); // Windows 8, Windows Server 2012 73 | 74 | return (platformId == PlatformID.Win32NT) && (windowsVersion >= minimumRequiredVersion); 75 | } 76 | #endif 77 | } 78 | } -------------------------------------------------------------------------------- /src/LightStep/ISpanRecorder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace LightStep 5 | { 6 | /// 7 | /// Records spans generated by the tracer. Used to batch/send spans to a collector. 8 | /// 9 | public interface ISpanRecorder 10 | { 11 | /// 12 | /// The start (creation) time of the span buffer. 13 | /// 14 | DateTime ReportStartTime { get; } 15 | /// 16 | /// The finishing (flushing) time of the span buffer. 17 | /// 18 | DateTime ReportEndTime { get; set; } 19 | /// 20 | /// The count of dropped spans for this, or a prior, span buffer. 21 | /// 22 | int DroppedSpanCount { get; set; } 23 | 24 | /// 25 | /// Saves a span. 26 | /// 27 | /// 28 | void RecordSpan(SpanData span); 29 | 30 | /// 31 | /// Returns this instance of the span buffer. 32 | /// 33 | /// 34 | ISpanRecorder GetSpanBuffer(); 35 | 36 | /// 37 | /// Clears the span record. 38 | /// 39 | void ClearSpanBuffer(); 40 | 41 | /// 42 | /// Increments the dropped span count. 43 | /// 44 | /// 45 | void RecordDroppedSpans(int count); 46 | 47 | /// 48 | /// Gets the spans stored in the buffer. 49 | /// 50 | /// 51 | IEnumerable GetSpans(); 52 | 53 | /// 54 | /// Gets the count of spans in the buffer. 55 | /// 56 | /// 57 | int GetSpanCount(); 58 | } 59 | } -------------------------------------------------------------------------------- /src/LightStep/LightStep.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | LightStep 4 | LightStep 5 | 7.0 6 | net472;netstandard2.0 7 | 8 | 9 | 10 | 11 | 12 | all 13 | runtime; build; native; contentfiles; analyzers 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | Austin Parker 44 | LightStep 45 | en-US 46 | LightStep 47 | OpenTracing compliant tracer for LightStep. 48 | LightStep 2020 49 | tracing 50 | https://github.com/lightstep/lightstep-tracer-csharp/blob/master/CHANGELOG.md 51 | https://raw.githubusercontent.com/lightstep/lightstep-tracer-csharp/master/ls128x128.png 52 | http://www.lightstep.com 53 | MIT 54 | false 55 | git 56 | https://raw.githubusercontent.com/lightstep/lightstep-tracer-csharp 57 | 58 | 59 | 60 | true 61 | false 62 | ../../tracerSign.snk 63 | <_UseRoslynPublicSignHack>false 64 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 65 | false 66 | true 67 | 68 | -------------------------------------------------------------------------------- /src/LightStep/LightStepConstants.cs: -------------------------------------------------------------------------------- 1 | namespace LightStep 2 | { 3 | /// 4 | /// Constants and other values used by LightStep. 5 | /// 6 | public static class LightStepConstants 7 | { 8 | public static readonly string ParentSpanGuidKey = "parent_span_guid"; 9 | public static readonly string GuidKey = "lightstep.guid"; 10 | public static readonly string HostnameKey = "lightstep.hostname"; 11 | public static readonly string ComponentNameKey = "lightstep.component_name"; 12 | public static readonly string CommandLineKey = "lightstep.command_line"; 13 | 14 | public static readonly string TracerPlatformKey = "lightstep.tracer_platform"; 15 | public static readonly string TracerPlatformValue = "csharp"; 16 | public static readonly string TracerPlatformVersionKey = "lightstep.tracer_platform_version"; 17 | public static readonly string TracerVersionKey = "lightstep.tracer_version"; 18 | 19 | public static readonly string SatelliteReportPath = "api/v2/reports"; 20 | 21 | public static readonly string AccessTokenConstant = "Lightstep-Access-Token"; 22 | 23 | public static class MetaEvent { 24 | public static readonly string MetaEventKey = "lightstep.meta_event"; 25 | public static readonly string PropagationFormatKey = "lightstep.propagation_format"; 26 | public static readonly string TraceIdKey = "lightstep.trace_id"; 27 | public static readonly string SpanIdKey = "lightstep.span_id"; 28 | public static readonly string TracerGuidKey = "lightstep.tracer_guid"; 29 | public static readonly string ExtractOperation = "lightstep.extract_span"; 30 | public static readonly string InjectOperation = "lightstep.inject_span"; 31 | public static readonly string SpanStartOperation = "lightstep.span_start"; 32 | public static readonly string SpanFinishOperation = "lightstep.span_finish"; 33 | public static readonly string TracerCreateOperation = "lightstep.tracer_create"; 34 | } 35 | 36 | } 37 | } -------------------------------------------------------------------------------- /src/LightStep/LightStepSpanRecorder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using LightStep.Logging; 5 | 6 | namespace LightStep 7 | { 8 | /// 9 | public sealed class LightStepSpanRecorder : ISpanRecorder 10 | { 11 | private List Spans { get; } = new List(); 12 | private static readonly ILog _logger = LogProvider.GetCurrentClassLogger(); 13 | 14 | /// 15 | public DateTime ReportStartTime { get; } = DateTime.Now; 16 | 17 | /// 18 | public DateTime ReportEndTime { get; set; } 19 | 20 | /// 21 | public int DroppedSpanCount { get; set; } 22 | 23 | /// 24 | public void RecordSpan(SpanData span) 25 | { 26 | lock (Spans) 27 | { 28 | Spans.Add(span); 29 | } 30 | } 31 | 32 | /// 33 | public ISpanRecorder GetSpanBuffer() 34 | { 35 | lock (Spans) 36 | { 37 | ReportEndTime = DateTime.Now; 38 | return this; 39 | } 40 | } 41 | 42 | /// 43 | public void ClearSpanBuffer() 44 | { 45 | lock (Spans) 46 | { 47 | Spans.Clear(); 48 | } 49 | } 50 | 51 | /// 52 | public void RecordDroppedSpans(int count) 53 | { 54 | DroppedSpanCount += count; 55 | } 56 | 57 | /// 58 | public IEnumerable GetSpans() 59 | { 60 | lock (Spans) 61 | { 62 | return Spans.ToList(); 63 | } 64 | } 65 | 66 | public int GetSpanCount() 67 | { 68 | return Spans.Count; 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /src/LightStep/LogData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace LightStep 5 | { 6 | /// 7 | /// A single log event 8 | /// 9 | public struct LogData 10 | { 11 | /// 12 | /// Create a new log event 13 | /// 14 | /// A from the beginning of the span 15 | /// An enumerable of to log. 16 | public LogData(DateTimeOffset timestamp, IEnumerable> fields) 17 | { 18 | Timestamp = timestamp; 19 | Fields = fields; 20 | } 21 | 22 | /// 23 | /// The time since the beginning of the span. 24 | /// 25 | public DateTimeOffset Timestamp { get; } 26 | 27 | /// 28 | /// The log fields. 29 | /// 30 | public IEnumerable> Fields { get; } 31 | } 32 | } -------------------------------------------------------------------------------- /src/LightStep/Options.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Runtime.Versioning; 7 | using System.Text.RegularExpressions; 8 | using LightStep.Logging; 9 | 10 | namespace LightStep 11 | { 12 | /// 13 | /// Options for configuring the LightStep tracer. 14 | /// 15 | public class Options 16 | { 17 | private static readonly ILog _logger = LogProvider.GetCurrentClassLogger(); 18 | 19 | /// 20 | /// An identifier for the Tracer. 21 | /// 22 | public readonly ulong TracerGuid = new Random().NextUInt64(); 23 | 24 | /// 25 | /// API key for a LightStep project. 26 | /// 27 | public string AccessToken { get; private set; } 28 | 29 | /// 30 | /// If true, tracer will start reporting 31 | /// 32 | /// 33 | public bool Run { get; private set; } 34 | /// 35 | /// True if the satellite connection should use HTTP/2, false otherwise. 36 | /// 37 | public bool UseHttp2 { get; private set; } 38 | 39 | /// 40 | /// LightStep Satellite endpoint configuration. 41 | /// 42 | public SatelliteOptions Satellite { get; private set; } 43 | 44 | /// 45 | /// How often the reporter will send spans to a LightStep Satellite. 46 | /// 47 | public TimeSpan ReportPeriod { get; private set; } 48 | 49 | /// 50 | /// Timeout for sending spans to a LightStep Satellite. 51 | /// 52 | public TimeSpan ReportTimeout { get; private set; } 53 | 54 | /// 55 | /// Maximum amount of spans to buffer in a single report. 56 | /// 57 | public int ReportMaxSpans { get; private set;} 58 | 59 | /// 60 | /// Tags that should be applied to each span generated by this tracer. 61 | /// 62 | public IDictionary Tags { get; private set; } 63 | 64 | /// 65 | /// If the tracer should send JSON rather than binary protobufs to the satellite. 66 | /// 67 | public TransportOptions Transport { get; private set; } 68 | 69 | /// 70 | /// Determines if tracer should report meta events to LightStep 71 | /// 72 | public Boolean EnableMetaEventLogging { get; internal set; } 73 | 74 | public Action ExceptionHandler { get; internal set; } 75 | public Boolean ExceptionHandlerRegistered { get; internal set; } 76 | 77 | public Options WithMetaEventLogging() 78 | { 79 | _logger.Debug("Enabling Meta Events"); 80 | EnableMetaEventLogging = true; 81 | return this; 82 | } 83 | 84 | public Options WithToken(string token) 85 | { 86 | _logger.Debug($"Setting access token to {token}"); 87 | AccessToken = token; 88 | return this; 89 | } 90 | 91 | public Options WithHttp2() 92 | { 93 | _logger.Debug("Enabling HTTP/2 support."); 94 | UseHttp2 = true; 95 | return this; 96 | } 97 | 98 | public Options WithSatellite(SatelliteOptions options) 99 | { 100 | _logger.Debug($"Setting satellite to {options}"); 101 | Satellite = options; 102 | return this; 103 | } 104 | 105 | public Options WithReportPeriod(TimeSpan period) 106 | { 107 | _logger.Debug($"Setting reporting period to {period}"); 108 | ReportPeriod = period; 109 | return this; 110 | } 111 | 112 | public Options WithReportTimeout(TimeSpan timeout) 113 | { 114 | _logger.Debug($"Setting report timeout to {timeout}"); 115 | ReportTimeout = timeout; 116 | return this; 117 | } 118 | 119 | public Options WithTags(IDictionary tags) 120 | { 121 | _logger.Debug($"Setting default tags to: {tags.Select(kvp => $"{kvp.Key}:{kvp.Value},")}"); 122 | Tags = MergeTags(tags); 123 | return this; 124 | } 125 | 126 | public Options WithAutomaticReporting(bool shouldRun) 127 | { 128 | _logger.Debug($"Setting automatic reporting to {shouldRun}"); 129 | Run = shouldRun; 130 | return this; 131 | } 132 | 133 | public Options WithMaxBufferedSpans(int count) 134 | { 135 | _logger.Debug($"Setting max spans per buffer to {count}"); 136 | ReportMaxSpans = count; 137 | return this; 138 | } 139 | 140 | public Options WithTransport(TransportOptions transport) 141 | { 142 | _logger.Debug($"Setting JSON reports to {transport}"); 143 | Transport = transport; 144 | return this; 145 | } 146 | 147 | public Options WithExceptionHandler(Action handler) 148 | { 149 | _logger.Debug($"Registering exception handler {handler}"); 150 | ExceptionHandler = handler; 151 | ExceptionHandlerRegistered = true; 152 | return this; 153 | } 154 | 155 | /// 156 | /// Creates a new set of options for the LightStep tracer. 157 | /// 158 | /// Project access token, if required. 159 | public Options(string token = "") 160 | { 161 | Tags = InitializeDefaultTags(); 162 | ReportPeriod = TimeSpan.FromMilliseconds(5000); 163 | ReportTimeout = TimeSpan.FromSeconds(30); 164 | AccessToken = token; 165 | Satellite = new SatelliteOptions("collector.lightstep.com", 443, false); 166 | UseHttp2 = false; 167 | Run = true; 168 | ReportMaxSpans = int.MaxValue; 169 | Transport = TransportOptions.BinaryProto; 170 | EnableMetaEventLogging = false; 171 | ExceptionHandlerRegistered = false; 172 | } 173 | 174 | private IDictionary MergeTags(IDictionary input) 175 | { 176 | var attributes = InitializeDefaultTags(); 177 | var mergedAttributes = new Dictionary(input); 178 | foreach (var item in attributes) 179 | { 180 | if (!mergedAttributes.ContainsKey(item.Key)) 181 | { 182 | mergedAttributes.Add(item.Key, item.Value); 183 | } 184 | } 185 | 186 | return mergedAttributes; 187 | } 188 | 189 | private IDictionary InitializeDefaultTags() 190 | { 191 | var attributes = new Dictionary 192 | { 193 | [LightStepConstants.TracerPlatformKey] = LightStepConstants.TracerPlatformValue, 194 | [LightStepConstants.TracerPlatformVersionKey] = GetPlatformVersion(), 195 | [LightStepConstants.TracerVersionKey] = GetTracerVersion(), 196 | [LightStepConstants.ComponentNameKey] = GetComponentName(), 197 | [LightStepConstants.HostnameKey] = GetHostName(), 198 | [LightStepConstants.CommandLineKey] = GetCommandLine() 199 | }; 200 | return attributes; 201 | } 202 | 203 | private static string GetTracerVersion() 204 | { 205 | return typeof(LightStep.Tracer).Assembly.GetCustomAttribute().InformationalVersion; 206 | } 207 | 208 | private static string GetComponentName() 209 | { 210 | var entryAssembly = ""; 211 | try 212 | { 213 | entryAssembly = Assembly.GetEntryAssembly().GetName().Name; 214 | } 215 | catch (NullReferenceException) 216 | { 217 | // could not get assembly name, possibly because we're running a test 218 | entryAssembly = "unknown"; 219 | } 220 | return entryAssembly; 221 | } 222 | 223 | private static string GetPlatformVersion() 224 | { 225 | var version = ".NET Unknown"; 226 | version = System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription; 227 | return version.Remove(0, version.IndexOf(' ', 0)); 228 | } 229 | 230 | 231 | private static string GetHostName() 232 | { 233 | return Environment.MachineName; 234 | } 235 | 236 | private static string GetCommandLine() 237 | { 238 | return Environment.CommandLine; 239 | } 240 | } 241 | } -------------------------------------------------------------------------------- /src/LightStep/Propagation/B3Propagator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using OpenTracing.Propagation; 3 | 4 | namespace LightStep.Propagation 5 | { 6 | /// 7 | public class B3Propagator : IPropagator 8 | { 9 | public const string TraceIdName = "X-B3-TraceId"; 10 | public const string SpanIdName = "X-B3-SpanId"; 11 | public const string SampledName = "X-B3-Sampled"; 12 | 13 | /// 14 | public void Inject(SpanContext context, IFormat format, TCarrier carrier) 15 | { 16 | if (carrier is ITextMap text) 17 | { 18 | text.Set(TraceIdName, context.OriginalTraceId); 19 | text.Set(SpanIdName, context.SpanId); 20 | text.Set(SampledName, "1"); 21 | } 22 | } 23 | 24 | /// 25 | public SpanContext Extract(IFormat format, TCarrier carrier) 26 | { 27 | if (carrier is ITextMap text) 28 | { 29 | ulong? traceId = null; 30 | string OriginalTraceId = null; 31 | ulong? spanId = null; 32 | 33 | foreach (var entry in text) 34 | { 35 | if (TraceIdName.Equals(entry.Key, StringComparison.OrdinalIgnoreCase)) 36 | { 37 | traceId = ParseTraceId(entry.Value); 38 | OriginalTraceId = entry.Value; 39 | } 40 | else if (SpanIdName.Equals(entry.Key, StringComparison.OrdinalIgnoreCase)) 41 | { 42 | spanId = Convert.ToUInt64(entry.Value, 16); 43 | } 44 | } 45 | 46 | if (traceId.HasValue && spanId.HasValue) 47 | { 48 | return new SpanContext(traceId.Value, spanId.Value, originalTraceId: OriginalTraceId); 49 | } 50 | } 51 | 52 | return null; 53 | } 54 | 55 | private static ulong ParseTraceId(string str) 56 | { 57 | ulong traceId; 58 | 59 | if (ContainsHexChar(str)) 60 | { 61 | if (str.Length <= 16) 62 | { 63 | traceId = Convert.ToUInt64(str, 16); 64 | } 65 | else 66 | { 67 | traceId = Convert.ToUInt64(str.Substring(str.Length - 16), 16); 68 | } 69 | } 70 | else 71 | { 72 | if (str.Length <= 20) 73 | { 74 | traceId = Convert.ToUInt64(str); 75 | } 76 | else 77 | { 78 | traceId = Convert.ToUInt64(str.Substring(str.Length - 20)); 79 | } 80 | } 81 | 82 | return traceId; 83 | } 84 | 85 | private static bool ContainsHexChar(string traceId) 86 | { 87 | foreach (var c in traceId) 88 | { 89 | if (char.IsLetter(c)) 90 | { 91 | return true; 92 | } 93 | } 94 | 95 | return false; 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /src/LightStep/Propagation/BinaryPropagator.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lightstep/lightstep-tracer-csharp/59c3e63658fd69bf732ddc019e90607d9b014c11/src/LightStep/Propagation/BinaryPropagator.cs -------------------------------------------------------------------------------- /src/LightStep/Propagation/ConsolePropagator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using OpenTracing.Propagation; 3 | 4 | namespace LightStep.Propagation 5 | { 6 | /// 7 | public class ConsolePropagator : IPropagator 8 | { 9 | /// 10 | public void Inject(SpanContext context, IFormat format, TCarrier carrier) 11 | { 12 | Console.WriteLine($"Inject({context}, {format}, {carrier})"); 13 | } 14 | 15 | /// 16 | public SpanContext Extract(IFormat format, TCarrier carrier) 17 | { 18 | Console.WriteLine($"Extract({format}, {carrier}"); 19 | return null; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/LightStep/Propagation/EnvoyPropagator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Google.Protobuf; 4 | using LightStep.Logging; 5 | using OpenTracing.Propagation; 6 | 7 | namespace LightStep.Propagation 8 | { 9 | public class EnvoyPropagator : IPropagator 10 | { 11 | private static readonly ILog _logger = LogProvider.GetCurrentClassLogger(); 12 | 13 | /// 14 | public void Inject(SpanContext context, IFormat format, TCarrier carrier) 15 | { 16 | _logger.Trace($"Injecting {context} of {format.GetType()} to {carrier.GetType()}"); 17 | if (carrier is IBinary s) 18 | { 19 | var ctx = new BinaryCarrier 20 | { 21 | BasicCtx = new BasicTracerCarrier 22 | { 23 | SpanId = context.SpanIdValue, 24 | TraceId = context.TraceIdValue, 25 | Sampled = true 26 | } 27 | }; 28 | foreach (var item in context.GetBaggageItems()) ctx.BasicCtx.BaggageItems.Add(item.Key, item.Value); 29 | var ctxArray = ctx.ToByteArray(); 30 | var ctxStream = new MemoryStream(ctxArray); 31 | s.Set(ctxStream); 32 | } 33 | } 34 | 35 | /// 36 | public SpanContext Extract(IFormat format, TCarrier carrier) 37 | { 38 | if (carrier is IBinary s) 39 | { 40 | var ctx = BinaryCarrier.Parser.ParseFrom(s.Get()); 41 | var traceId = ctx.BasicCtx.TraceId; 42 | var spanId = ctx.BasicCtx.SpanId; 43 | var baggage = new Baggage(); 44 | 45 | foreach (var item in ctx.BasicCtx.BaggageItems) 46 | { 47 | baggage.Set(item.Key, item.Value); 48 | } 49 | 50 | return new SpanContext(traceId, spanId, baggage); 51 | } 52 | 53 | return null; 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /src/LightStep/Propagation/HttpHeadersPropagator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using OpenTracing.Propagation; 3 | 4 | namespace LightStep.Propagation 5 | { 6 | /** TODO: this is a blind copy of the Java client; once HTTP carrier encoding has been defined, update this as well 7 | * 8 | */ 9 | /// 10 | public class HttpHeadersPropagator : IPropagator 11 | { 12 | /// 13 | public void Inject(SpanContext context, IFormat format, TCarrier carrier) 14 | { 15 | var textMapPropagator = new TextMapPropagator(); 16 | textMapPropagator.Inject(context, format, carrier); 17 | } 18 | 19 | /// 20 | public SpanContext Extract(IFormat format, TCarrier carrier) 21 | { 22 | var textMapPropagator = new TextMapPropagator(); 23 | return textMapPropagator.Extract(format, carrier, StringComparison.OrdinalIgnoreCase); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/LightStep/Propagation/IPropagator.cs: -------------------------------------------------------------------------------- 1 | using OpenTracing.Propagation; 2 | 3 | namespace LightStep.Propagation 4 | { 5 | /// 6 | /// Implements the and methods for passing contexts 7 | /// between process boundaries. 8 | /// 9 | public interface IPropagator 10 | { 11 | /// 12 | /// Injects a into a . 13 | /// 14 | /// The instance to inject into the 15 | /// The of the 16 | /// The carrier for the 17 | /// 18 | /// The type, which also parametrizes the 19 | /// 20 | void Inject(SpanContext context, IFormat format, TCarrier carrier); 21 | 22 | /// 23 | /// Extracts a from a . 24 | /// 25 | /// The or the 26 | /// The of the 27 | /// 28 | /// The type, which also parametrizes the 29 | /// 30 | /// The instance to create a span. 31 | SpanContext Extract(IFormat format, TCarrier carrier); 32 | } 33 | } -------------------------------------------------------------------------------- /src/LightStep/Propagation/Keys.cs: -------------------------------------------------------------------------------- 1 | namespace LightStep.Propagation 2 | { 3 | /// 4 | /// Key prefixes used in baggage/traces/etc. 5 | /// 6 | public static class Keys 7 | { 8 | /// 9 | /// OpenTracing Baggage Prefix 10 | /// 11 | public const string BaggagePrefix = "ot-baggage-"; 12 | 13 | /// 14 | /// OpenTracing TraceId Prefix 15 | /// 16 | public const string TraceId = "ot-tracer-traceid"; 17 | 18 | /// 19 | /// OpenTracing SpanId Prefix 20 | /// 21 | public const string SpanId = "ot-tracer-spanid"; 22 | 23 | /// 24 | /// OpenTracing Sampled Prefix 25 | /// 26 | public const string Sampled = "ot-tracer-sampled"; 27 | } 28 | } -------------------------------------------------------------------------------- /src/LightStep/Propagation/PropagatorStack.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using LightStep.Logging; 4 | using OpenTracing.Propagation; 5 | 6 | namespace LightStep.Propagation 7 | { 8 | /// 9 | public class PropagatorStack : IPropagator 10 | { 11 | private static readonly ILog _logger = LogProvider.GetCurrentClassLogger(); 12 | 13 | /// 14 | public PropagatorStack(IFormat format) 15 | { 16 | if (format == null) throw new ArgumentNullException(nameof(format)); 17 | _logger.Trace($"Creating new PropagatorStack with format {format}"); 18 | Format = format; 19 | Propagators = new List(); 20 | } 21 | 22 | /// 23 | /// The format of the propagators 24 | /// 25 | public IFormat Format { get; } 26 | 27 | /// 28 | /// A list of propagators to attempt to match. 29 | /// 30 | public List Propagators { get; } 31 | 32 | /// 33 | public void Inject(SpanContext context, IFormat format, TCarrier carrier) 34 | { 35 | Propagators.ForEach(propagator => 36 | { 37 | _logger.Trace($"Injecting context to {propagator.GetType()}"); 38 | propagator.Inject(context, format, carrier); 39 | } 40 | ); 41 | } 42 | 43 | /// 44 | public SpanContext Extract(IFormat format, TCarrier carrier) 45 | { 46 | for (var i = Propagators.Count - 1; i >= 0; i--) 47 | { 48 | _logger.Trace($"Trying to extract from {Propagators[i].GetType()}"); 49 | var context = Propagators[i].Extract(format, carrier); 50 | if (context != null) return context; 51 | } 52 | 53 | return null; 54 | } 55 | 56 | /// 57 | /// Add a new propagator to a stack. 58 | /// 59 | /// 60 | /// 61 | /// 62 | public PropagatorStack AddPropagator(IPropagator propagator) 63 | { 64 | if (propagator == null) throw new ArgumentNullException(nameof(propagator)); 65 | _logger.Trace($"Adding {propagator.GetType()} to PropagatorStack."); 66 | Propagators.Add(propagator); 67 | return this; 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /src/LightStep/Propagation/Propagators.cs: -------------------------------------------------------------------------------- 1 | namespace LightStep.Propagation 2 | { 3 | /// 4 | /// Static references to each propagator available. 5 | /// 6 | public static class Propagators 7 | { 8 | /// 9 | /// Writes context values to the console, used for local development. 10 | /// 11 | public static readonly IPropagator Console = new ConsolePropagator(); 12 | 13 | /// 14 | /// A key:value store for string pairs. 15 | /// 16 | public static readonly IPropagator TextMap = new TextMapPropagator(); 17 | 18 | /// 19 | /// Supports B3 headers, such as those used in Zipkin or StageMonitor. 20 | /// 21 | public static readonly IPropagator B3Propagator = new B3Propagator(); 22 | 23 | /// 24 | /// Supports HTTP Header Propagation 25 | /// 26 | public static readonly IPropagator HttpHeadersPropagator = new HttpHeadersPropagator(); 27 | } 28 | } -------------------------------------------------------------------------------- /src/LightStep/Propagation/TextMapPropagator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using LightStep.Logging; 3 | using OpenTracing.Propagation; 4 | 5 | namespace LightStep.Propagation 6 | { 7 | /// 8 | public class TextMapPropagator : IPropagator 9 | { 10 | private static readonly ILog _logger = LogProvider.GetCurrentClassLogger(); 11 | /// 12 | public void Inject(SpanContext context, IFormat format, TCarrier carrier) 13 | { 14 | _logger.Trace($"Injecting {context} of {format.GetType()} to {carrier.GetType()}"); 15 | if (carrier is ITextMap text) 16 | { 17 | foreach (var entry in context.GetBaggageItems()) text.Set(Keys.BaggagePrefix + entry.Key, entry.Value); 18 | 19 | text.Set(Keys.SpanId, context.SpanId); 20 | text.Set(Keys.TraceId, context.TraceId); 21 | text.Set(Keys.Sampled, "true"); 22 | } 23 | else 24 | { 25 | _logger.Warn($"Unknown carrier during inject."); 26 | throw new InvalidOperationException($"Unknown carrier {carrier.GetType()}"); 27 | } 28 | } 29 | 30 | /// 31 | public SpanContext Extract(IFormat format, TCarrier carrier) 32 | { 33 | return Extract(format, carrier, StringComparison.Ordinal); 34 | } 35 | 36 | public SpanContext Extract(IFormat format, TCarrier carrier, StringComparison comparison) 37 | { 38 | _logger.Trace($"Extracting {format.GetType()} from {carrier.GetType()}"); 39 | if (carrier is ITextMap text) 40 | { 41 | ulong? traceId = null; 42 | ulong? spanId = null; 43 | var baggage = new Baggage(); 44 | 45 | foreach (var entry in text) 46 | { 47 | if (Keys.TraceId.Equals(entry.Key, comparison)) 48 | { 49 | traceId = Convert.ToUInt64(entry.Value, 16); 50 | } 51 | else if (Keys.SpanId.Equals(entry.Key, comparison)) 52 | { 53 | spanId = Convert.ToUInt64(entry.Value, 16); 54 | } 55 | else if (entry.Key.StartsWith(Keys.BaggagePrefix, comparison)) 56 | { 57 | var key = entry.Key.Substring(Keys.BaggagePrefix.Length); 58 | baggage.Set(key, entry.Value); 59 | } 60 | } 61 | 62 | if (traceId.HasValue && spanId.HasValue) 63 | { 64 | _logger.Trace($"Existing trace/spanID found, returning SpanContext."); 65 | return new SpanContext(traceId.Value, spanId.Value); 66 | } 67 | } 68 | 69 | return null; 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /src/LightStep/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by Cake. 4 | // 5 | //------------------------------------------------------------------------------ 6 | using System.Reflection; 7 | using System.Runtime.CompilerServices; 8 | 9 | [assembly: AssemblyProduct("LightStep")] 10 | [assembly: AssemblyVersion("0.0.0.0")] 11 | [assembly: AssemblyFileVersion("0.0.0.0")] 12 | [assembly: AssemblyInformationalVersion("0.0.0")] 13 | [assembly: AssemblyCopyright("Copyright (c) LightStep 2018 - 2020")] 14 | 15 | [assembly: InternalsVisibleTo("LightStep.Tests,PublicKey=002400000480000094000000060200000024000052534131000400000100010099deeadb052e9763d2dc7827700d80e349e5d16585c92416171e6689a4bd38a3acea971d5899d5e2cd4239c3dc799558138e961f8d0f5095fef969672172833868f2cc2d908970370af834beef9dad328182fee2aaf0d0bb568ffd1f829362b88718734541d334c6a2cdf0049f5a0ee5e4962d0db3f49f86bf742f9531bd9c8c")] 16 | 17 | -------------------------------------------------------------------------------- /src/LightStep/Reference.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace LightStep 5 | { 6 | public sealed class Reference : IEquatable 7 | { 8 | public SpanContext Context { get; } 9 | 10 | public string ReferenceType { get; } 11 | 12 | public Reference(SpanContext context, string referenceType) 13 | { 14 | Context = context ?? throw new ArgumentNullException(nameof(context)); 15 | ReferenceType = referenceType ?? throw new ArgumentNullException(nameof(referenceType)); 16 | } 17 | 18 | public override bool Equals(object obj) 19 | { 20 | return Equals(obj as Reference); 21 | } 22 | 23 | public bool Equals(Reference other) 24 | { 25 | return other != null && 26 | EqualityComparer.Default.Equals(Context, other.Context) 27 | && ReferenceType == other.ReferenceType; 28 | } 29 | 30 | public override int GetHashCode() 31 | { 32 | var hashCode = 2083322454; 33 | hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Context); 34 | hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(ReferenceType); 35 | return hashCode; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/LightStep/SatelliteOptions.cs: -------------------------------------------------------------------------------- 1 | namespace LightStep 2 | { 3 | /// 4 | /// Options to configure a LightStep satellite. 5 | /// 6 | public class SatelliteOptions 7 | { 8 | /// 9 | /// Create a new Satellite Endpoint. 10 | /// 11 | /// satellite hostname 12 | /// satellite port 13 | /// unused 14 | public SatelliteOptions(string host, int port = 443, bool usePlaintext = false) 15 | { 16 | SatelliteHost = host; 17 | SatellitePort = port; 18 | UsePlaintext = usePlaintext; 19 | } 20 | 21 | /// 22 | /// Hostname of a Satellite 23 | /// 24 | public string SatelliteHost { get; } 25 | 26 | /// 27 | /// Port number of a Satellite 28 | /// 29 | public int SatellitePort { get; } 30 | 31 | /// 32 | /// Currently unused 33 | /// 34 | public bool UsePlaintext { get; } 35 | 36 | public override string ToString() 37 | { 38 | return $"Host: {SatelliteHost}, Port: {SatellitePort}, Use Plaintext: {UsePlaintext}"; 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /src/LightStep/Span.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Security.Cryptography; 5 | using LightStep.Logging; 6 | using OpenTracing; 7 | using OpenTracing.Tag; 8 | 9 | namespace LightStep 10 | { 11 | /// 12 | public sealed class Span : ISpan 13 | { 14 | private readonly SpanContext _context; 15 | private readonly List _errors = new List(); 16 | private readonly object _lock = new object(); 17 | private readonly List _logs = new List(); 18 | private readonly List _references; 19 | private readonly Dictionary _tags; 20 | 21 | private readonly Tracer _tracer; 22 | private bool _finished; 23 | private DateTimeOffset _finishTimestamp; 24 | 25 | private static readonly ILog _logger = LogProvider.GetCurrentClassLogger(); 26 | 27 | /// 28 | /// Create a new Span. 29 | /// 30 | /// Tracer that will record the span. 31 | /// Operation name being recorded by the span. 32 | /// The at which the span begun. 33 | /// Tags for the span. 34 | /// References to other spans. 35 | public Span( 36 | Tracer tracer, 37 | string operationName, 38 | DateTimeOffset startTimestamp, 39 | IDictionary tags, 40 | IReadOnlyCollection references) 41 | { 42 | _tracer = tracer; 43 | OperationName = operationName; 44 | StartTimestamp = startTimestamp; 45 | 46 | _tags = tags == null 47 | ? new Dictionary() 48 | : new Dictionary(tags); 49 | 50 | _references = references == null 51 | ? new List() 52 | : references.ToList(); 53 | 54 | 55 | var parentContext = FindPreferredParentRef(_references); 56 | 57 | OperationName = operationName.Trim(); 58 | StartTimestamp = startTimestamp; 59 | 60 | if (parentContext == null) 61 | { 62 | // we are a root span 63 | _context = new SpanContext(GetRandomId(), GetRandomId(), new Baggage()); 64 | ParentId = null; 65 | } 66 | else 67 | { 68 | // we are a child span 69 | _context = new SpanContext(parentContext.TraceIdValue, GetRandomId(), MergeBaggages(_references), 70 | parentContext.SpanIdValue); 71 | ParentId = parentContext.SpanId; 72 | } 73 | if (_tracer._options.EnableMetaEventLogging && Utilities.IsNotMetaSpan(this)) 74 | { 75 | _tracer.BuildSpan(LightStepConstants.MetaEvent.TracerCreateOperation) 76 | .IgnoreActiveSpan() 77 | .WithTag(LightStepConstants.MetaEvent.MetaEventKey, true) 78 | .WithTag(LightStepConstants.MetaEvent.SpanIdKey, _context.SpanId) 79 | .WithTag(LightStepConstants.MetaEvent.TraceIdKey, _context.TraceId) 80 | .Start() 81 | .Finish(); 82 | } 83 | 84 | } 85 | 86 | /// 87 | /// SpanID of this span's parent. 88 | /// 89 | private string ParentId { get; } 90 | 91 | /// 92 | /// The start time of the span. 93 | /// 94 | private DateTimeOffset StartTimestamp { get; } 95 | 96 | /// 97 | /// The finish time of the span. 98 | /// 99 | /// A span must be finished before setting the finish timestamp. 100 | private DateTimeOffset FinishTimestamp 101 | { 102 | get 103 | { 104 | if (_finishTimestamp == DateTimeOffset.MinValue) 105 | throw new InvalidOperationException("Must call Finish() before FinishTimestamp"); 106 | 107 | return _finishTimestamp; 108 | } 109 | } 110 | 111 | /// 112 | /// The operation name of the span. 113 | /// 114 | private string OperationName { get; set; } 115 | 116 | public Dictionary Tags => new Dictionary(_tags); 117 | public List Logs => new List(_logs); 118 | public List Errors => new List(_errors); 119 | 120 | /// 121 | /// The span's 122 | /// 123 | private SpanContext Context 124 | { 125 | get 126 | { 127 | lock (_lock) 128 | { 129 | return _context; 130 | } 131 | } 132 | } 133 | 134 | ISpanContext ISpan.Context => Context; 135 | 136 | /// 137 | public ISpan Log(IEnumerable> fields) 138 | { 139 | return Log(DateTimeOffset.Now, fields); 140 | } 141 | 142 | /// 143 | public ISpan Log(DateTimeOffset timestamp, IEnumerable> fields) 144 | { 145 | lock (_lock) 146 | { 147 | CheckIfSpanFinished("Adding logs {0} at {1} to already finished span.", fields, timestamp); 148 | _logs.Add(new LogData(timestamp, fields)); 149 | return this; 150 | } 151 | } 152 | 153 | /// 154 | public ISpan Log(string eventName) 155 | { 156 | return Log(DateTimeOffset.Now, eventName); 157 | } 158 | 159 | /// 160 | public ISpan Log(DateTimeOffset timestamp, string eventName) 161 | { 162 | return Log(timestamp, new Dictionary {{"event", eventName}}); 163 | } 164 | 165 | /// 166 | public ISpan SetBaggageItem(string key, string value) 167 | { 168 | lock (_lock) 169 | { 170 | CheckIfSpanFinished("Adding baggage [{0}:{1}] to finished span.", key, value); 171 | _context.SetBaggageItem(key, value); 172 | return this; 173 | } 174 | } 175 | 176 | /// 177 | public string GetBaggageItem(string key) 178 | { 179 | lock (_lock) 180 | { 181 | return _context.GetBaggageItem(key); 182 | } 183 | } 184 | 185 | /// 186 | public void Finish() 187 | { 188 | Finish(HighResolutionDateTime.IsAvailable ? new DateTimeOffset(HighResolutionDateTime.UtcNow) : DateTimeOffset.UtcNow); 189 | } 190 | 191 | /// 192 | public void Finish(DateTimeOffset finishTimestamp) 193 | { 194 | lock (_lock) 195 | { 196 | CheckIfSpanFinished("Tried to finish already finished span"); 197 | _finishTimestamp = finishTimestamp; 198 | _finished = true; 199 | OnFinished(); 200 | } 201 | } 202 | 203 | private static ulong GetRandomId() 204 | { 205 | var provider = new RNGCryptoServiceProvider(); 206 | var buffer = new byte[64]; 207 | provider.GetBytes(buffer); 208 | return BitConverter.ToUInt64(buffer, 0); 209 | } 210 | 211 | private static SpanContext FindPreferredParentRef(IList references) 212 | { 213 | if (!references.Any()) return null; 214 | 215 | foreach (var reference in references) 216 | if (References.ChildOf.Equals(reference.ReferenceType)) 217 | return reference.Context; 218 | 219 | return references.First().Context; 220 | } 221 | 222 | private static Baggage MergeBaggages(IList references) 223 | { 224 | var baggage = new Baggage(); 225 | foreach (var reference in references) 226 | if (reference.Context.GetBaggageItems() != null) 227 | foreach (var bagItem in reference.Context.GetBaggageItems()) 228 | baggage.Set(bagItem.Key, bagItem.Value); 229 | 230 | return baggage; 231 | } 232 | 233 | private void CheckIfSpanFinished(string format, params object[] args) 234 | { 235 | if (_finished) 236 | { 237 | var ex = new InvalidOperationException(string.Format(format, args)); 238 | _errors.Add(ex); 239 | _logger.Error(ex.Message); 240 | } 241 | } 242 | 243 | private ISpan SetObjectTag(string key, object value) 244 | { 245 | lock (_lock) 246 | { 247 | CheckIfSpanFinished("Setting tag [{0}:{1}] on finished span", key, value); 248 | _tags[key] = value; 249 | return this; 250 | } 251 | } 252 | 253 | private void OnFinished() 254 | { 255 | var spanData = new SpanData 256 | { 257 | Context = this.TypedContext(), 258 | OperationName = OperationName, 259 | StartTimestamp = StartTimestamp, 260 | Duration = FinishTimestamp - StartTimestamp, 261 | Tags = _tags, 262 | LogData = _logs 263 | }; 264 | 265 | _tracer.AppendFinishedSpan(spanData); 266 | if(_tracer._options.EnableMetaEventLogging && Utilities.IsNotMetaSpan(this)) 267 | { 268 | _tracer.BuildSpan(LightStepConstants.MetaEvent.SpanFinishOperation) 269 | .IgnoreActiveSpan() 270 | .WithTag(LightStepConstants.MetaEvent.MetaEventKey, true) 271 | .WithTag(LightStepConstants.MetaEvent.SpanIdKey, this.TypedContext().SpanId) 272 | .WithTag(LightStepConstants.MetaEvent.TraceIdKey, this.TypedContext().TraceId) 273 | .Start() 274 | .Finish(); 275 | } 276 | } 277 | 278 | #region Setters 279 | 280 | /// 281 | public ISpan SetOperationName(string operationName) 282 | { 283 | lock (_lock) 284 | { 285 | if (string.IsNullOrWhiteSpace(operationName)) throw new ArgumentNullException(nameof(operationName)); 286 | 287 | CheckIfSpanFinished("Setting operationName [{0}] on finished span.", operationName); 288 | OperationName = operationName; 289 | return this; 290 | } 291 | } 292 | 293 | /// 294 | public ISpan SetTag(string key, bool value) 295 | { 296 | if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullException(nameof(key)); 297 | 298 | return SetObjectTag(key, value); 299 | } 300 | 301 | /// 302 | public ISpan SetTag(string key, double value) 303 | { 304 | if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullException(nameof(key)); 305 | 306 | return SetObjectTag(key, value); 307 | } 308 | 309 | /// 310 | public ISpan SetTag(string key, int value) 311 | { 312 | if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullException(nameof(key)); 313 | 314 | return SetObjectTag(key, value); 315 | } 316 | 317 | /// 318 | public ISpan SetTag(string key, string value) 319 | { 320 | if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullException(nameof(key)); 321 | 322 | return SetObjectTag(key, value); 323 | } 324 | 325 | /// 326 | public ISpan SetTag(BooleanTag tag, bool value) 327 | { 328 | if (string.IsNullOrWhiteSpace(tag.Key)) throw new ArgumentNullException(nameof(tag.Key)); 329 | 330 | return SetObjectTag(tag.Key, value); 331 | } 332 | 333 | /// 334 | public ISpan SetTag(IntOrStringTag tag, string value) 335 | { 336 | if (string.IsNullOrWhiteSpace(tag.Key)) throw new ArgumentNullException(nameof(tag.Key)); 337 | 338 | return SetObjectTag(tag.Key, value); 339 | } 340 | 341 | /// 342 | public ISpan SetTag(IntTag tag, int value) 343 | { 344 | if (string.IsNullOrWhiteSpace(tag.Key)) throw new ArgumentNullException(nameof(tag.Key)); 345 | 346 | return SetObjectTag(tag.Key, value); 347 | } 348 | 349 | /// 350 | public ISpan SetTag(StringTag tag, string value) 351 | { 352 | if (string.IsNullOrWhiteSpace(tag.Key)) throw new ArgumentNullException(nameof(tag.Key)); 353 | 354 | return SetObjectTag(tag.Key, value); 355 | } 356 | 357 | #endregion 358 | } 359 | } -------------------------------------------------------------------------------- /src/LightStep/SpanBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using OpenTracing; 5 | using OpenTracing.Tag; 6 | 7 | namespace LightStep 8 | { 9 | /// 10 | public class SpanBuilder : ISpanBuilder 11 | { 12 | private readonly string _operationName; 13 | private readonly List _references = new List(); 14 | private readonly Tracer _tracer; 15 | private bool _ignoreActiveSpan; 16 | private DateTimeOffset _startTimestamp = HighResolutionDateTime.IsAvailable ? new DateTimeOffset(HighResolutionDateTime.UtcNow) : DateTimeOffset.UtcNow; 17 | private Dictionary _tags = new Dictionary(); 18 | 19 | /// 20 | public SpanBuilder(Tracer tracer, string operationName) 21 | { 22 | if (string.IsNullOrWhiteSpace(operationName)) throw new ArgumentNullException(nameof(operationName)); 23 | 24 | _tracer = tracer ?? throw new ArgumentNullException(nameof(tracer)); 25 | _operationName = operationName; 26 | } 27 | 28 | /// 29 | public ISpanBuilder AsChildOf(ISpanContext parent) 30 | { 31 | if (parent == null) return this; 32 | 33 | return AddReference(References.ChildOf, parent); 34 | } 35 | 36 | /// 37 | public ISpanBuilder AsChildOf(ISpan parent) 38 | { 39 | if (parent == null) return this; 40 | 41 | return AsChildOf(parent.Context); 42 | } 43 | 44 | /// 45 | public ISpanBuilder AddReference(string referenceType, ISpanContext referencedContext) 46 | { 47 | if (string.IsNullOrWhiteSpace(referenceType)) throw new ArgumentNullException(nameof(referenceType)); 48 | 49 | if (referencedContext != null) 50 | _references.Add(new Reference((SpanContext) referencedContext, referenceType)); 51 | 52 | return this; 53 | } 54 | 55 | /// 56 | public ISpanBuilder IgnoreActiveSpan() 57 | { 58 | _ignoreActiveSpan = true; 59 | return this; 60 | } 61 | 62 | /// 63 | public ISpanBuilder WithTag(StringTag tag, string value) 64 | { 65 | _tags[tag.Key] = value; 66 | return this; 67 | } 68 | 69 | /// 70 | public ISpanBuilder WithStartTimestamp(DateTimeOffset startTimestamp) 71 | { 72 | _startTimestamp = startTimestamp; 73 | return this; 74 | } 75 | 76 | /// 77 | public IScope StartActive() 78 | { 79 | return StartActive(true); 80 | } 81 | 82 | /// 83 | public IScope StartActive(bool finishSpanOnDispose) 84 | { 85 | var span = Start(); 86 | return _tracer.ScopeManager.Activate(span, finishSpanOnDispose); 87 | } 88 | 89 | /// 90 | public ISpanBuilder WithTag(string key, bool value) 91 | { 92 | if (_tags == null) _tags = new Dictionary(); 93 | 94 | _tags[key] = value; 95 | return this; 96 | } 97 | 98 | /// 99 | public ISpanBuilder WithTag(string key, double value) 100 | { 101 | if (_tags == null) _tags = new Dictionary(); 102 | 103 | _tags[key] = value; 104 | return this; 105 | } 106 | 107 | /// 108 | public ISpanBuilder WithTag(BooleanTag tag, bool value) 109 | { 110 | _tags[tag.Key] = value; 111 | return this; 112 | } 113 | 114 | /// 115 | public ISpanBuilder WithTag(IntOrStringTag tag, string value) 116 | { 117 | _tags[tag.Key] = value; 118 | return this; 119 | } 120 | 121 | /// 122 | public ISpanBuilder WithTag(IntTag tag, int value) 123 | { 124 | _tags[tag.Key] = value; 125 | return this; 126 | } 127 | 128 | /// 129 | public ISpanBuilder WithTag(string key, int value) 130 | { 131 | if (_tags == null) _tags = new Dictionary(); 132 | 133 | _tags[key] = value; 134 | return this; 135 | } 136 | 137 | /// 138 | public ISpanBuilder WithTag(string key, string value) 139 | { 140 | if (_tags == null) _tags = new Dictionary(); 141 | 142 | _tags[key] = value; 143 | return this; 144 | } 145 | 146 | /// 147 | public ISpan Start() 148 | { 149 | var activeSpanContext = _tracer.ActiveSpan?.Context; 150 | if (!_references.Any() && !_ignoreActiveSpan && activeSpanContext != null) 151 | AddReference(References.ChildOf, activeSpanContext); 152 | return new Span(_tracer, _operationName, _startTimestamp, _tags, _references); 153 | } 154 | 155 | private ISpanBuilder FollowsFrom(ISpanContext spanContext) 156 | { 157 | return AddReference(References.FollowsFrom, spanContext); 158 | } 159 | 160 | /// 161 | /// Indicates that this span follows from a prior span. 162 | /// 163 | /// An 164 | /// 165 | public ISpanBuilder FollowsFrom(ISpan span) 166 | { 167 | return FollowsFrom(span?.Context); 168 | } 169 | } 170 | } -------------------------------------------------------------------------------- /src/LightStep/SpanContext.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using OpenTracing; 3 | 4 | namespace LightStep 5 | { 6 | /// 7 | public class SpanContext : ISpanContext 8 | { 9 | private readonly Baggage _baggage = new Baggage(); 10 | 11 | /// 12 | public SpanContext(ulong traceId, ulong spanId, Baggage baggage = null, ulong parentId = 0L, string originalTraceId = null) 13 | { 14 | TraceIdValue = traceId; 15 | OriginalTraceId = originalTraceId ?? TraceId; 16 | SpanIdValue = spanId; 17 | ParentSpanIdValue = parentId; 18 | _baggage.Merge(baggage); 19 | } 20 | 21 | /// 22 | /// The parent span ID, if any. 23 | /// 24 | public ulong ParentSpanIdValue { get; } 25 | 26 | public string ParentSpanId => ParentSpanIdValue.ToString("x"); 27 | 28 | /// 29 | /// The trace ID represetned as a ulong (UInt64). 30 | /// 31 | public ulong TraceIdValue { get; } 32 | 33 | /// 34 | public string TraceId => TraceIdValue.ToString("x"); 35 | 36 | /// 37 | /// The original trace ID used to create this context. 38 | /// This may be a 64 or 128 bit hex value. 39 | /// 40 | public string OriginalTraceId { get; } 41 | 42 | /// 43 | /// The Span ID represented as a ulong (UInt64). 44 | /// 45 | public ulong SpanIdValue { get; } 46 | 47 | /// 48 | public string SpanId => SpanIdValue.ToString("x"); 49 | 50 | /// 51 | public IEnumerable> GetBaggageItems() 52 | { 53 | return _baggage.GetAll(); 54 | } 55 | 56 | /// 57 | /// Get a single item from the baggage. 58 | /// 59 | /// 60 | /// 61 | public string GetBaggageItem(string key) 62 | { 63 | return _baggage.Get(key); 64 | } 65 | 66 | /// 67 | /// Set an item on the baggage. 68 | /// 69 | /// 70 | /// 71 | /// 72 | public ISpanContext SetBaggageItem(string key, string value) 73 | { 74 | _baggage.Set(key, value); 75 | return this; 76 | } 77 | 78 | public override string ToString() 79 | { 80 | return $"[traceId: {TraceId}, spanId: {SpanId}, parentId: {ParentSpanId ?? "none"}"; 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /src/LightStep/SpanData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace LightStep 5 | { 6 | /// 7 | /// An internal representation of a span. 8 | /// 9 | public class SpanData 10 | { 11 | /// 12 | /// The Span Context 13 | /// 14 | public SpanContext Context { get; internal set; } 15 | 16 | /// 17 | /// The span operation 18 | /// 19 | public string OperationName { get; internal set; } 20 | 21 | /// 22 | /// The start of the span. 23 | /// 24 | public DateTimeOffset StartTimestamp { get; internal set; } 25 | 26 | /// 27 | /// How long the span ran. 28 | /// 29 | public TimeSpan Duration { get; internal set; } 30 | 31 | /// 32 | /// Tags for the span. 33 | /// 34 | public IDictionary Tags { get; internal set; } 35 | 36 | /// 37 | /// Logs emitted as part of the span. 38 | /// 39 | public IList LogData { get; internal set; } 40 | 41 | public override string ToString() 42 | { 43 | return 44 | $"[{OperationName}] ctx: {Context}, startTime: {StartTimestamp}, duration: {Duration.TotalMilliseconds}"; 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /src/LightStep/Tracer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net.Http; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using LightStep.Collector; 8 | using LightStep.Propagation; 9 | using OpenTracing; 10 | using OpenTracing.Propagation; 11 | using OpenTracing.Util; 12 | using LightStep.Logging; 13 | 14 | namespace LightStep 15 | { 16 | /// 17 | public sealed class Tracer : ITracer, IDisposable 18 | { 19 | private readonly object _lock = new object(); 20 | internal readonly Options _options; 21 | private readonly IPropagator _propagator; 22 | private readonly ILightStepHttpClient _httpClient; 23 | private readonly IReportTranslator _translator; 24 | private ISpanRecorder _spanRecorder; 25 | private readonly Timer _reportLoop; 26 | private static readonly ILog _logger = LogProvider.GetCurrentClassLogger(); 27 | private int currentDroppedSpanCount; 28 | private bool _firstReportHasRun; 29 | 30 | /// 31 | public Tracer(Options options) : this(new AsyncLocalScopeManager(), Propagators.TextMap, options, 32 | new LightStepSpanRecorder(), null) 33 | { 34 | } 35 | 36 | /// 37 | public Tracer(Options options, ISpanRecorder spanRecorder) : this(new AsyncLocalScopeManager(), 38 | Propagators.TextMap, options, spanRecorder, null) 39 | { 40 | } 41 | 42 | /// 43 | public Tracer(Options options, IScopeManager scopeManager) : this(scopeManager, Propagators.TextMap, options, 44 | new LightStepSpanRecorder(), null) 45 | { 46 | } 47 | 48 | /// 49 | public Tracer(Options options, ISpanRecorder spanRecorder, IPropagator propagator) : this( 50 | new AsyncLocalScopeManager(), propagator, options, spanRecorder, null) 51 | { 52 | } 53 | /// 54 | public Tracer(Options options, ISpanRecorder spanRecorder, ILightStepHttpClient client) : this( 55 | new AsyncLocalScopeManager(), Propagators.TextMap, options, spanRecorder, client) 56 | { 57 | } 58 | /// 59 | public Tracer(Options options, IPropagator propagator, ILightStepHttpClient client) : this( 60 | new AsyncLocalScopeManager(), propagator, options, new LightStepSpanRecorder(), client) 61 | { 62 | } 63 | /// 64 | public Tracer(Options options, ISpanRecorder spanRecorder, IReportTranslator translator, 65 | ILightStepHttpClient client) : this( 66 | new AsyncLocalScopeManager(), Propagators.TextMap, options, spanRecorder, client, translator) 67 | { 68 | } 69 | /// 70 | private Tracer(IScopeManager scopeManager, IPropagator propagator, Options options, ISpanRecorder spanRecorder, ILightStepHttpClient client, IReportTranslator translator = null) 71 | { 72 | ScopeManager = scopeManager; 73 | _spanRecorder = spanRecorder; 74 | _propagator = propagator; 75 | _options = options; 76 | _logger.Debug( 77 | $"Creating new tracer with GUID {_options.TracerGuid}. Project Access Token: {_options.AccessToken}, Report Period: {_options.ReportPeriod}, Report Timeout: {_options.ReportTimeout}."); 78 | var protocol = _options.Satellite.UsePlaintext ? "http" : "https"; 79 | var url = 80 | $"{protocol}://{_options.Satellite.SatelliteHost}:{_options.Satellite.SatellitePort}/{LightStepConstants.SatelliteReportPath}"; 81 | _httpClient = client ?? new LightStepHttpClient(url, _options); 82 | _translator = translator ?? new ReportTranslator(_options); 83 | _logger.Debug($"Tracer is reporting to {url}."); 84 | _reportLoop = new Timer(async e => await Flush().ConfigureAwait(false), null, TimeSpan.Zero, _options.ReportPeriod); 85 | _firstReportHasRun = false; 86 | } 87 | 88 | /// 89 | public IScopeManager ScopeManager { get; } 90 | 91 | /// 92 | public ISpan ActiveSpan => ScopeManager?.Active?.Span; 93 | 94 | /// 95 | public ISpanBuilder BuildSpan(string operationName) 96 | { 97 | return new SpanBuilder(this, operationName); 98 | } 99 | 100 | /// 101 | public void Inject(ISpanContext spanContext, IFormat format, TCarrier carrier) 102 | { 103 | _propagator.Inject((SpanContext) spanContext, format, carrier); 104 | if (_options.EnableMetaEventLogging) { 105 | this.BuildSpan(LightStepConstants.MetaEvent.InjectOperation) 106 | .IgnoreActiveSpan() 107 | .WithTag(LightStepConstants.MetaEvent.MetaEventKey, true) 108 | .WithTag(LightStepConstants.MetaEvent.SpanIdKey, spanContext.SpanId) 109 | .WithTag(LightStepConstants.MetaEvent.TraceIdKey, spanContext.TraceId) 110 | .WithTag(LightStepConstants.MetaEvent.PropagationFormatKey, format.GetType().ToString()) 111 | .Start() 112 | .Finish(); 113 | } 114 | } 115 | 116 | /// 117 | public ISpanContext Extract(IFormat format, TCarrier carrier) 118 | { 119 | var ctx = _propagator.Extract(format, carrier); 120 | if (_options.EnableMetaEventLogging) { 121 | this.BuildSpan(LightStepConstants.MetaEvent.ExtractOperation) 122 | .IgnoreActiveSpan() 123 | .WithTag(LightStepConstants.MetaEvent.MetaEventKey, true) 124 | .WithTag(LightStepConstants.MetaEvent.SpanIdKey, ctx.SpanId) 125 | .WithTag(LightStepConstants.MetaEvent.TraceIdKey, ctx.TraceId) 126 | .WithTag(LightStepConstants.MetaEvent.PropagationFormatKey, format.GetType().ToString()) 127 | .Start() 128 | .Finish(); 129 | } 130 | return ctx; 131 | } 132 | 133 | /// 134 | /// Transmits the current contents of the span buffer to the LightStep Satellite. 135 | /// Note that this creates a copy of the current spans and clears the span buffer! 136 | /// 137 | public async Task Flush() 138 | { 139 | if (_options.Run) 140 | { 141 | if (_options.EnableMetaEventLogging && _firstReportHasRun == false) 142 | { 143 | BuildSpan(LightStepConstants.MetaEvent.TracerCreateOperation) 144 | .IgnoreActiveSpan() 145 | .WithTag(LightStepConstants.MetaEvent.MetaEventKey, true) 146 | .WithTag(LightStepConstants.MetaEvent.TracerGuidKey, _options.TracerGuid) 147 | .Start() 148 | .Finish(); 149 | _firstReportHasRun = true; 150 | } 151 | // save current spans and clear the buffer 152 | ISpanRecorder currentBuffer; 153 | lock (_lock) 154 | { 155 | currentBuffer = _spanRecorder.GetSpanBuffer(); 156 | _spanRecorder = new LightStepSpanRecorder(); 157 | _logger.Trace($"{currentBuffer.GetSpanCount()} spans in buffer."); 158 | } 159 | 160 | /** 161 | * there are two ways spans can be dropped: 162 | * 1. the buffer drops a span because it's too large, malformed, etc. 163 | * 2. the report failed to be sent to the satellite. 164 | * since flush is async and there can potentially be any number of buffers in flight to the satellite, 165 | * we need to set the current drop count on the tracer to be the amount of dropped spans from the buffer 166 | * plus the existing dropped spans, then mutate the current buffer to this new total value. 167 | */ 168 | currentDroppedSpanCount += currentBuffer.DroppedSpanCount; 169 | currentBuffer.DroppedSpanCount = currentDroppedSpanCount; 170 | 171 | try 172 | { 173 | // since translate can throw exceptions, place it in the try and drop spans as appropriate 174 | var data = _translator.Translate(currentBuffer); 175 | var resp = await _httpClient.SendReport(data).ConfigureAwait(false); 176 | 177 | if (resp.Errors.Count > 0) 178 | { 179 | _logger.Warn($"Errors in report: {resp.Errors}"); 180 | } 181 | // if the satellite is in developer mode, set the tracer to development mode as well 182 | // don't re-enable if it's already enabled though 183 | // TODO: iterate through all commands to find devmode flag 184 | if (resp.Commands.Count > 0 && resp.Commands[0].DevMode && _options.EnableMetaEventLogging == false) 185 | { 186 | _logger.Info("Enabling meta event logging"); 187 | _options.EnableMetaEventLogging = true; 188 | } 189 | 190 | lock (_lock) 191 | { 192 | _logger.Trace($"Resetting tracer dropped span count as the last report was successful."); 193 | currentDroppedSpanCount = 0; 194 | } 195 | 196 | } 197 | catch (Exception ex) when (ex is HttpRequestException || ex is TaskCanceledException || ex is OperationCanceledException || ex is Exception) 198 | { 199 | lock (_lock) 200 | { 201 | _logger.Warn($"Adding {currentBuffer.GetSpanCount()} spans to dropped span count (current total: {currentDroppedSpanCount})"); 202 | currentDroppedSpanCount += currentBuffer.GetSpanCount(); 203 | if (this._options.ExceptionHandlerRegistered) 204 | { 205 | this._options.ExceptionHandler.Invoke(ex); 206 | } 207 | } 208 | } 209 | 210 | 211 | } 212 | } 213 | 214 | internal void AppendFinishedSpan(SpanData span) 215 | { 216 | lock (_lock) 217 | { 218 | if (_spanRecorder.GetSpanCount() < _options.ReportMaxSpans ) 219 | { 220 | _spanRecorder.RecordSpan(span); 221 | } 222 | else 223 | { 224 | _spanRecorder.RecordDroppedSpans(1); 225 | _logger.Warn($"Dropping span due to too many spans in buffer."); 226 | } 227 | } 228 | } 229 | 230 | public void Dispose() 231 | { 232 | _reportLoop?.Dispose(); 233 | } 234 | } 235 | } -------------------------------------------------------------------------------- /src/LightStep/TransportOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LightStep 4 | { 5 | /// 6 | /// Options for the transport used to send spans to LightStep. 7 | /// 8 | [Flags] 9 | public enum TransportOptions 10 | { 11 | /// 12 | /// Binary protobuf encoding over HTTP 13 | /// 14 | BinaryProto, 15 | /// 16 | /// JSON protobuf encoding over HTTP 17 | /// 18 | JsonProto 19 | } 20 | } -------------------------------------------------------------------------------- /src/LightStep/TypedContextExtensions.cs: -------------------------------------------------------------------------------- 1 | using OpenTracing; 2 | 3 | namespace LightStep 4 | { 5 | /// 6 | /// Extensions methods for retrieving a span context. 7 | /// 8 | public static class TypedContextExtensions 9 | { 10 | /// 11 | /// Get a strongly typed from a 12 | /// 13 | /// 14 | /// 15 | public static SpanContext TypedContext(this ISpan span) 16 | { 17 | return (SpanContext) span?.Context; 18 | } 19 | 20 | /// 21 | /// Get a strongly typed from an 22 | /// 23 | /// 24 | /// 25 | public static SpanContext TypedContext(this ISpanContext context) 26 | { 27 | return (SpanContext) context; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/LightStep/Utilities.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LightStep 4 | { 5 | /// 6 | /// Utilities and other helpers. 7 | /// 8 | public static class Utilities 9 | { 10 | /// 11 | /// Get a random uint64. 12 | /// 13 | /// 14 | /// 15 | public static ulong NextUInt64(this Random rand) 16 | { 17 | var buffer = new byte[sizeof(ulong)]; 18 | rand.NextBytes(buffer); 19 | return BitConverter.ToUInt64(buffer, 0); 20 | } 21 | 22 | public static bool IsNotMetaSpan(Span span) 23 | { 24 | return !span.Tags.ContainsKey(LightStepConstants.MetaEvent.MetaEventKey); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/LightStep/collector.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package LightStep.Collector; 4 | 5 | option go_package = "collectorpb"; 6 | option objc_class_prefix = "LSPB"; 7 | option java_multiple_files = true; 8 | option java_package = "com.lightstep.tracer.grpc"; 9 | 10 | import "google/protobuf/timestamp.proto"; 11 | import "google/api/annotations.proto"; 12 | 13 | message SpanContext { 14 | uint64 trace_id = 1; 15 | uint64 span_id = 2; 16 | map baggage = 3; 17 | } 18 | 19 | // Represent both tags and log fields. 20 | message KeyValue { 21 | string key = 1; 22 | oneof value { 23 | // Holds arbitrary string data; well-formed JSON strings should go in 24 | // json_value. 25 | string string_value = 2; 26 | int64 int_value = 3; 27 | double double_value = 4; 28 | bool bool_value = 5; 29 | // Must be a well-formed JSON value. Truncated JSON should go in 30 | // string_value. Should not be used for tags. 31 | string json_value = 6; 32 | } 33 | } 34 | 35 | message Log { 36 | google.protobuf.Timestamp timestamp = 1; 37 | repeated KeyValue fields = 2; 38 | } 39 | 40 | message Reference { 41 | enum Relationship { 42 | CHILD_OF = 0; 43 | FOLLOWS_FROM = 1; 44 | } 45 | Relationship relationship = 1; 46 | SpanContext span_context = 2; 47 | } 48 | 49 | message Span { 50 | SpanContext span_context = 1; 51 | string operation_name = 2; 52 | repeated Reference references = 3; 53 | google.protobuf.Timestamp start_timestamp = 4; 54 | uint64 duration_micros = 5; 55 | repeated KeyValue tags = 6; 56 | repeated Log logs = 7; 57 | } 58 | 59 | message Reporter { 60 | uint64 reporter_id = 1; 61 | repeated KeyValue tags = 4; 62 | } 63 | 64 | message MetricsSample { 65 | string name = 1; 66 | oneof value { 67 | int64 int_value = 2; 68 | double double_value = 3; 69 | } 70 | } 71 | 72 | message InternalMetrics { 73 | google.protobuf.Timestamp start_timestamp = 1; 74 | uint64 duration_micros = 2; 75 | repeated Log logs = 3; 76 | repeated MetricsSample counts = 4; 77 | repeated MetricsSample gauges = 5; 78 | } 79 | 80 | message Auth { 81 | string access_token = 1; 82 | } 83 | 84 | message ReportRequest { 85 | Reporter reporter = 1; 86 | Auth auth = 2; 87 | repeated Span spans = 3; 88 | int64 timestamp_offset_micros = 5; 89 | InternalMetrics internal_metrics = 6; 90 | } 91 | 92 | message Command { 93 | bool disable = 1; 94 | bool dev_mode = 2; 95 | } 96 | 97 | message ReportResponse { 98 | repeated Command commands = 1; 99 | google.protobuf.Timestamp receive_timestamp = 2; 100 | google.protobuf.Timestamp transmit_timestamp = 3; 101 | repeated string errors = 4; 102 | repeated string warnings = 5; 103 | repeated string infos = 6; 104 | } 105 | 106 | service CollectorService { 107 | rpc Report(ReportRequest) returns (ReportResponse) { 108 | option (google.api.http) = { 109 | post: "/api/v2/reports" 110 | body: "*" 111 | additional_bindings { 112 | get: "/api/v2/reports" 113 | } 114 | }; 115 | } 116 | } -------------------------------------------------------------------------------- /src/LightStep/google/api/annotations.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package google.api; 18 | 19 | import "google/api/http.proto"; 20 | import "google/protobuf/descriptor.proto"; 21 | 22 | option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; 23 | option java_multiple_files = true; 24 | option java_outer_classname = "AnnotationsProto"; 25 | option java_package = "com.google.api"; 26 | option objc_class_prefix = "GAPI"; 27 | 28 | extend google.protobuf.MethodOptions { 29 | // See `HttpRule`. 30 | HttpRule http = 72295728; 31 | } -------------------------------------------------------------------------------- /src/LightStep/google/api/http.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package google.api; 18 | 19 | option cc_enable_arenas = true; 20 | option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; 21 | option java_multiple_files = true; 22 | option java_outer_classname = "HttpProto"; 23 | option java_package = "com.google.api"; 24 | option objc_class_prefix = "GAPI"; 25 | 26 | 27 | // Defines the HTTP configuration for a service. It contains a list of 28 | // [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method 29 | // to one or more HTTP REST API methods. 30 | message Http { 31 | // A list of HTTP configuration rules that apply to individual API methods. 32 | // 33 | // **NOTE:** All service configuration rules follow "last one wins" order. 34 | repeated HttpRule rules = 1; 35 | } 36 | 37 | // `HttpRule` defines the mapping of an RPC method to one or more HTTP 38 | // REST APIs. The mapping determines what portions of the request 39 | // message are populated from the path, query parameters, or body of 40 | // the HTTP request. The mapping is typically specified as an 41 | // `google.api.http` annotation, see "google/api/annotations.proto" 42 | // for details. 43 | // 44 | // The mapping consists of a field specifying the path template and 45 | // method kind. The path template can refer to fields in the request 46 | // message, as in the example below which describes a REST GET 47 | // operation on a resource collection of messages: 48 | // 49 | // 50 | // service Messaging { 51 | // rpc GetMessage(GetMessageRequest) returns (Message) { 52 | // option (google.api.http).get = "/v1/messages/{message_id}/{sub.subfield}"; 53 | // } 54 | // } 55 | // message GetMessageRequest { 56 | // message SubMessage { 57 | // string subfield = 1; 58 | // } 59 | // string message_id = 1; // mapped to the URL 60 | // SubMessage sub = 2; // `sub.subfield` is url-mapped 61 | // } 62 | // message Message { 63 | // string text = 1; // content of the resource 64 | // } 65 | // 66 | // The same http annotation can alternatively be expressed inside the 67 | // `GRPC API Configuration` YAML file. 68 | // 69 | // http: 70 | // rules: 71 | // - selector: .Messaging.GetMessage 72 | // get: /v1/messages/{message_id}/{sub.subfield} 73 | // 74 | // This definition enables an automatic, bidrectional mapping of HTTP 75 | // JSON to RPC. Example: 76 | // 77 | // HTTP | RPC 78 | // -----|----- 79 | // `GET /v1/messages/123456/foo` | `GetMessage(message_id: "123456" sub: SubMessage(subfield: "foo"))` 80 | // 81 | // In general, not only fields but also field paths can be referenced 82 | // from a path pattern. Fields mapped to the path pattern cannot be 83 | // repeated and must have a primitive (non-message) type. 84 | // 85 | // Any fields in the request message which are not bound by the path 86 | // pattern automatically become (optional) HTTP query 87 | // parameters. Assume the following definition of the request message: 88 | // 89 | // 90 | // message GetMessageRequest { 91 | // message SubMessage { 92 | // string subfield = 1; 93 | // } 94 | // string message_id = 1; // mapped to the URL 95 | // int64 revision = 2; // becomes a parameter 96 | // SubMessage sub = 3; // `sub.subfield` becomes a parameter 97 | // } 98 | // 99 | // 100 | // This enables a HTTP JSON to RPC mapping as below: 101 | // 102 | // HTTP | RPC 103 | // -----|----- 104 | // `GET /v1/messages/123456?revision=2&sub.subfield=foo` | `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: "foo"))` 105 | // 106 | // Note that fields which are mapped to HTTP parameters must have a 107 | // primitive type or a repeated primitive type. Message types are not 108 | // allowed. In the case of a repeated type, the parameter can be 109 | // repeated in the URL, as in `...?param=A¶m=B`. 110 | // 111 | // For HTTP method kinds which allow a request body, the `body` field 112 | // specifies the mapping. Consider a REST update method on the 113 | // message resource collection: 114 | // 115 | // 116 | // service Messaging { 117 | // rpc UpdateMessage(UpdateMessageRequest) returns (Message) { 118 | // option (google.api.http) = { 119 | // put: "/v1/messages/{message_id}" 120 | // body: "message" 121 | // }; 122 | // } 123 | // } 124 | // message UpdateMessageRequest { 125 | // string message_id = 1; // mapped to the URL 126 | // Message message = 2; // mapped to the body 127 | // } 128 | // 129 | // 130 | // The following HTTP JSON to RPC mapping is enabled, where the 131 | // representation of the JSON in the request body is determined by 132 | // protos JSON encoding: 133 | // 134 | // HTTP | RPC 135 | // -----|----- 136 | // `PUT /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: "123456" message { text: "Hi!" })` 137 | // 138 | // The special name `*` can be used in the body mapping to define that 139 | // every field not bound by the path template should be mapped to the 140 | // request body. This enables the following alternative definition of 141 | // the update method: 142 | // 143 | // service Messaging { 144 | // rpc UpdateMessage(Message) returns (Message) { 145 | // option (google.api.http) = { 146 | // put: "/v1/messages/{message_id}" 147 | // body: "*" 148 | // }; 149 | // } 150 | // } 151 | // message Message { 152 | // string message_id = 1; 153 | // string text = 2; 154 | // } 155 | // 156 | // 157 | // The following HTTP JSON to RPC mapping is enabled: 158 | // 159 | // HTTP | RPC 160 | // -----|----- 161 | // `PUT /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: "123456" text: "Hi!")` 162 | // 163 | // Note that when using `*` in the body mapping, it is not possible to 164 | // have HTTP parameters, as all fields not bound by the path end in 165 | // the body. This makes this option more rarely used in practice of 166 | // defining REST APIs. The common usage of `*` is in custom methods 167 | // which don't use the URL at all for transferring data. 168 | // 169 | // It is possible to define multiple HTTP methods for one RPC by using 170 | // the `additional_bindings` option. Example: 171 | // 172 | // service Messaging { 173 | // rpc GetMessage(GetMessageRequest) returns (Message) { 174 | // option (google.api.http) = { 175 | // get: "/v1/messages/{message_id}" 176 | // additional_bindings { 177 | // get: "/v1/users/{user_id}/messages/{message_id}" 178 | // } 179 | // }; 180 | // } 181 | // } 182 | // message GetMessageRequest { 183 | // string message_id = 1; 184 | // string user_id = 2; 185 | // } 186 | // 187 | // 188 | // This enables the following two alternative HTTP JSON to RPC 189 | // mappings: 190 | // 191 | // HTTP | RPC 192 | // -----|----- 193 | // `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` 194 | // `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: "123456")` 195 | // 196 | // # Rules for HTTP mapping 197 | // 198 | // The rules for mapping HTTP path, query parameters, and body fields 199 | // to the request message are as follows: 200 | // 201 | // 1. The `body` field specifies either `*` or a field path, or is 202 | // omitted. If omitted, it assumes there is no HTTP body. 203 | // 2. Leaf fields (recursive expansion of nested messages in the 204 | // request) can be classified into three types: 205 | // (a) Matched in the URL template. 206 | // (b) Covered by body (if body is `*`, everything except (a) fields; 207 | // else everything under the body field) 208 | // (c) All other fields. 209 | // 3. URL query parameters found in the HTTP request are mapped to (c) fields. 210 | // 4. Any body sent with an HTTP request can contain only (b) fields. 211 | // 212 | // The syntax of the path template is as follows: 213 | // 214 | // Template = "/" Segments [ Verb ] ; 215 | // Segments = Segment { "/" Segment } ; 216 | // Segment = "*" | "**" | LITERAL | Variable ; 217 | // Variable = "{" FieldPath [ "=" Segments ] "}" ; 218 | // FieldPath = IDENT { "." IDENT } ; 219 | // Verb = ":" LITERAL ; 220 | // 221 | // The syntax `*` matches a single path segment. It follows the semantics of 222 | // [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String 223 | // Expansion. 224 | // 225 | // The syntax `**` matches zero or more path segments. It follows the semantics 226 | // of [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.3 Reserved 227 | // Expansion. NOTE: it must be the last segment in the path except the Verb. 228 | // 229 | // The syntax `LITERAL` matches literal text in the URL path. 230 | // 231 | // The syntax `Variable` matches the entire path as specified by its template; 232 | // this nested template must not contain further variables. If a variable 233 | // matches a single path segment, its template may be omitted, e.g. `{var}` 234 | // is equivalent to `{var=*}`. 235 | // 236 | // NOTE: the field paths in variables and in the `body` must not refer to 237 | // repeated fields or map fields. 238 | // 239 | // Use CustomHttpPattern to specify any HTTP method that is not included in the 240 | // `pattern` field, such as HEAD, or "*" to leave the HTTP method unspecified for 241 | // a given URL path rule. The wild-card rule is useful for services that provide 242 | // content to Web (HTML) clients. 243 | message HttpRule { 244 | // Selects methods to which this rule applies. 245 | // 246 | // Refer to [selector][google.api.DocumentationRule.selector] for syntax details. 247 | string selector = 1; 248 | 249 | // Determines the URL pattern is matched by this rules. This pattern can be 250 | // used with any of the {get|put|post|delete|patch} methods. A custom method 251 | // can be defined using the 'custom' field. 252 | oneof pattern { 253 | // Used for listing and getting information about resources. 254 | string get = 2; 255 | 256 | // Used for updating a resource. 257 | string put = 3; 258 | 259 | // Used for creating a resource. 260 | string post = 4; 261 | 262 | // Used for deleting a resource. 263 | string delete = 5; 264 | 265 | // Used for updating a resource. 266 | string patch = 6; 267 | 268 | // Custom pattern is used for defining custom verbs. 269 | CustomHttpPattern custom = 8; 270 | } 271 | 272 | // The name of the request field whose value is mapped to the HTTP body, or 273 | // `*` for mapping all fields not captured by the path pattern to the HTTP 274 | // body. NOTE: the referred field must not be a repeated field and must be 275 | // present at the top-level of request message type. 276 | string body = 7; 277 | 278 | // Additional HTTP bindings for the selector. Nested bindings must 279 | // not contain an `additional_bindings` field themselves (that is, 280 | // the nesting may only be one level deep). 281 | repeated HttpRule additional_bindings = 11; 282 | } 283 | 284 | // A custom pattern is used for defining custom HTTP verb. 285 | message CustomHttpPattern { 286 | // The name of this custom HTTP verb. 287 | string kind = 1; 288 | 289 | // The path matched by this custom verb. 290 | string path = 2; 291 | } -------------------------------------------------------------------------------- /src/LightStep/google/protobuf/timestamp.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option cc_enable_arenas = true; 37 | option go_package = "github.com/golang/protobuf/ptypes/timestamp"; 38 | option java_package = "com.google.protobuf"; 39 | option java_outer_classname = "TimestampProto"; 40 | option java_multiple_files = true; 41 | option objc_class_prefix = "GPB"; 42 | 43 | // A Timestamp represents a point in time independent of any time zone 44 | // or calendar, represented as seconds and fractions of seconds at 45 | // nanosecond resolution in UTC Epoch time. It is encoded using the 46 | // Proleptic Gregorian Calendar which extends the Gregorian calendar 47 | // backwards to year one. It is encoded assuming all minutes are 60 48 | // seconds long, i.e. leap seconds are "smeared" so that no leap second 49 | // table is needed for interpretation. Range is from 50 | // 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. 51 | // By restricting to that range, we ensure that we can convert to 52 | // and from RFC 3339 date strings. 53 | // See [https://www.ietf.org/rfc/rfc3339.txt](https://www.ietf.org/rfc/rfc3339.txt). 54 | // 55 | // # Examples 56 | // 57 | // Example 1: Compute Timestamp from POSIX `time()`. 58 | // 59 | // Timestamp timestamp; 60 | // timestamp.set_seconds(time(NULL)); 61 | // timestamp.set_nanos(0); 62 | // 63 | // Example 2: Compute Timestamp from POSIX `gettimeofday()`. 64 | // 65 | // struct timeval tv; 66 | // gettimeofday(&tv, NULL); 67 | // 68 | // Timestamp timestamp; 69 | // timestamp.set_seconds(tv.tv_sec); 70 | // timestamp.set_nanos(tv.tv_usec * 1000); 71 | // 72 | // Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. 73 | // 74 | // FILETIME ft; 75 | // GetSystemTimeAsFileTime(&ft); 76 | // UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; 77 | // 78 | // // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z 79 | // // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z. 80 | // Timestamp timestamp; 81 | // timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL)); 82 | // timestamp.set_nanos((INT32) ((ticks % 10000000) * 100)); 83 | // 84 | // Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. 85 | // 86 | // long millis = System.currentTimeMillis(); 87 | // 88 | // Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) 89 | // .setNanos((int) ((millis % 1000) * 1000000)).build(); 90 | // 91 | // 92 | // Example 5: Compute Timestamp from current time in Python. 93 | // 94 | // timestamp = Timestamp() 95 | // timestamp.GetCurrentTime() 96 | // 97 | // # JSON Mapping 98 | // 99 | // In JSON format, the Timestamp type is encoded as a string in the 100 | // [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the 101 | // format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" 102 | // where {year} is always expressed using four digits while {month}, {day}, 103 | // {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional 104 | // seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution), 105 | // are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone 106 | // is required. A proto3 JSON serializer should always use UTC (as indicated by 107 | // "Z") when printing the Timestamp type and a proto3 JSON parser should be 108 | // able to accept both UTC and other timezones (as indicated by an offset). 109 | // 110 | // For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past 111 | // 01:30 UTC on January 15, 2017. 112 | // 113 | // In JavaScript, one can convert a Date object to this format using the 114 | // standard [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString] 115 | // method. In Python, a standard `datetime.datetime` object can be converted 116 | // to this format using [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) 117 | // with the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one 118 | // can use the Joda Time's [`ISODateTimeFormat.dateTime()`]( 119 | // http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime-- 120 | // ) to obtain a formatter capable of generating timestamps in this format. 121 | // 122 | // 123 | message Timestamp { 124 | 125 | // Represents seconds of UTC time since Unix epoch 126 | // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to 127 | // 9999-12-31T23:59:59Z inclusive. 128 | int64 seconds = 1; 129 | 130 | // Non-negative fractions of a second at nanosecond resolution. Negative 131 | // second values with fractions must still have non-negative nanos values 132 | // that count forward in time. Must be from 0 to 999,999,999 133 | // inclusive. 134 | int32 nanos = 2; 135 | } -------------------------------------------------------------------------------- /src/LightStep/lightstep.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package LightStep; 4 | 5 | option go_package = "lightsteppb"; 6 | 7 | // The standard carrier for binary context propagation into LightStep. 8 | message BinaryCarrier { 9 | // "text_ctx" was deprecated following lightstep-tracer-cpp-0.36 10 | repeated bytes deprecated_text_ctx = 1; 11 | 12 | // The Opentracing "basictracer" proto. 13 | BasicTracerCarrier basic_ctx = 2; 14 | } 15 | 16 | // Copy of https://github.com/opentracing/basictracer-go/blob/master/wire/wire.proto 17 | message BasicTracerCarrier { 18 | fixed64 trace_id = 1; 19 | fixed64 span_id = 2; 20 | bool sampled = 3; 21 | map baggage_items = 4; 22 | } -------------------------------------------------------------------------------- /test/LightStep.Tests/LightStep.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | false 4 | netcoreapp2.1 5 | 6 | 7 | 8 | runtime; build; native; contentfiles; analyzers 9 | all 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | true 24 | false 25 | ../../tracerSign.snk 26 | <_UseRoslynPublicSignHack>false 27 | 28 | -------------------------------------------------------------------------------- /test/LightStep.Tests/LightStepProtoTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using OpenTracing.Propagation; 5 | using Xunit; 6 | using LightStep.Collector; 7 | 8 | namespace LightStep.Tests 9 | { 10 | public class LightStepProtoTests 11 | { 12 | private Tracer GetTracer(ISpanRecorder recorder = null) 13 | { 14 | var spanRecorder = recorder ?? new SimpleMockRecorder(); 15 | var satelliteOptions = new SatelliteOptions("localhost", 80, true); 16 | var tracerOptions = new Options("TEST").WithSatellite(satelliteOptions).WithAutomaticReporting(false); 17 | return new Tracer(tracerOptions, spanRecorder); 18 | } 19 | 20 | private LightStepHttpClient GetClient(TransportOptions t = TransportOptions.BinaryProto) 21 | { 22 | var satelliteOptions = new SatelliteOptions("localhost", 80, true); 23 | var tracerOptions = new Options("TEST").WithSatellite(satelliteOptions).WithAutomaticReporting(false).WithTransport(t); 24 | return new LightStepHttpClient("http://localhost:80", tracerOptions); 25 | } 26 | 27 | private ReportTranslator GetTranslator() 28 | { 29 | var satelliteOptions = new SatelliteOptions("localhost", 80, true); 30 | var tracerOptions = new Options("TEST").WithSatellite(satelliteOptions).WithAutomaticReporting(false); 31 | return new ReportTranslator(tracerOptions); 32 | } 33 | 34 | [Fact] 35 | public void ReportShouldBeJsonWithJsonOption() 36 | { 37 | var recorder = new SimpleMockRecorder(); 38 | var translator = GetTranslator(); 39 | var tracer = GetTracer(recorder); 40 | var span = tracer.BuildSpan("test").Start(); 41 | span.Finish(); 42 | 43 | var client = GetClient(TransportOptions.JsonProto); 44 | var translatedSpans = translator.Translate(recorder.GetSpanBuffer()); 45 | var report = client.BuildRequest(translatedSpans); 46 | Assert.Equal("application/json", report.Content.Headers.ContentType.MediaType); 47 | var contentString = report.Content.ReadAsStringAsync().Result; 48 | Assert.Contains("test", contentString); 49 | } 50 | 51 | [Fact] 52 | public void ReportShouldBeBinaryWithoutJsonOption() 53 | { 54 | var recorder = new SimpleMockRecorder(); 55 | var translator = GetTranslator(); 56 | var tracer = GetTracer(recorder); 57 | var span = tracer.BuildSpan("test").Start(); 58 | span.Finish(); 59 | 60 | var client = GetClient(); 61 | var translatedSpans = translator.Translate(recorder.GetSpanBuffer()); 62 | var report = client.BuildRequest(translatedSpans); 63 | Assert.Equal("application/octet-stream", report.Content.Headers.ContentType.MediaType); 64 | } 65 | 66 | [Fact] 67 | public void InternalMetricsShouldExist() 68 | { 69 | var recorder = new SimpleMockRecorder(); 70 | var translator = GetTranslator(); 71 | var tracer = GetTracer(recorder); 72 | var span = tracer.BuildSpan("test").Start(); 73 | span.Finish(); 74 | 75 | var client = GetClient(); 76 | 77 | var translatedSpans = translator.Translate(recorder.GetSpanBuffer()); 78 | Assert.Equal("spans.dropped", translatedSpans.InternalMetrics.Counts[0].Name); 79 | } 80 | 81 | [Fact] 82 | public void DroppedSpanCountShouldSerializeCorrectly() 83 | { 84 | var mockBuffer = new SimpleMockRecorder(); 85 | var translator = GetTranslator(); 86 | mockBuffer.RecordDroppedSpans(1); 87 | 88 | var client = GetClient(); 89 | var translatedBuffer = translator.Translate(mockBuffer.GetSpanBuffer()); 90 | 91 | Assert.Equal(1, translatedBuffer.InternalMetrics.Counts[0].IntValue); 92 | } 93 | 94 | [Fact] 95 | public void DroppedSpanCountShouldIncrementOnBadSpan() 96 | { 97 | var recorder = new SimpleMockRecorder(); 98 | var translator = GetTranslator(); 99 | var badSpan = new SpanData { 100 | Duration = new TimeSpan(-1), 101 | OperationName = "badSpan" 102 | }; 103 | recorder.RecordSpan(badSpan); 104 | var client = GetClient(); 105 | var translatedBuffer = translator.Translate(recorder.GetSpanBuffer()); 106 | Assert.Equal(1, translatedBuffer.InternalMetrics.Counts[0].IntValue); 107 | } 108 | 109 | [Fact] 110 | public void ConverterShouldConvertValues() 111 | { 112 | var recorder = new SimpleMockRecorder(); 113 | var translator = GetTranslator(); 114 | var tracer = GetTracer(recorder); 115 | var span = tracer.BuildSpan("testOperation") 116 | .WithTag("boolTrueTag", true) 117 | .WithTag("boolFalseTag", false) 118 | .WithTag("intTag", 0) 119 | .WithTag("stringTag", "test") 120 | .WithTag("doubleTag", 0.1) 121 | .WithTag("nullTag", null) 122 | .WithTag("jsonTag", @"{""key"":""value""}") 123 | .Start(); 124 | span.Finish(); 125 | 126 | var client = GetClient(); 127 | 128 | var translatedSpans = translator.Translate(recorder.GetSpanBuffer()); 129 | var translatedSpan = translatedSpans.Spans[0]; 130 | 131 | foreach (var tag in translatedSpan.Tags) 132 | { 133 | switch (tag.Key) 134 | { 135 | case "boolTrueFlag": 136 | Assert.True(tag.BoolValue); 137 | break; 138 | case "boolFalseFlag": 139 | Assert.False(tag.BoolValue); 140 | break; 141 | case "intTag": 142 | Assert.Equal(0, tag.IntValue); 143 | break; 144 | case "stringTag": 145 | Assert.Equal("test", tag.StringValue); 146 | break; 147 | case "doubleTag": 148 | Assert.Equal(0.1, tag.DoubleValue); 149 | break; 150 | case "nullTag": 151 | Assert.Equal("null", tag.StringValue); 152 | break; 153 | case "jsonTag": 154 | Assert.Equal(@"{""key"":""value""}", tag.JsonValue); 155 | break; 156 | default: 157 | continue; 158 | } 159 | } 160 | } 161 | 162 | [Fact] 163 | public void SpanContextSpanIdShouldConvert() 164 | { 165 | var spanContext = new SpanContext(4659, 4660); 166 | var protoSpanContext = new LightStep.Collector.SpanContext().MakeSpanContextFromOtSpanContext(spanContext); 167 | Assert.Equal(4659ul, protoSpanContext.TraceId); 168 | Assert.Equal(4660ul, protoSpanContext.SpanId); 169 | } 170 | } 171 | } -------------------------------------------------------------------------------- /test/LightStep.Tests/MockLogProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using LightStep.Logging; 4 | using LightStep.Logging.LogProviders; 5 | 6 | namespace LightStep.Tests 7 | { 8 | internal class MockLogProvider : ILogProvider 9 | { 10 | public Logger GetLogger(string name) 11 | { 12 | return Log; 13 | } 14 | 15 | public IDisposable OpenNestedContext(string message) 16 | { 17 | return new DisposableAction(() => { }); 18 | } 19 | 20 | public IDisposable OpenMappedContext(string key, object value, bool destructure = false) 21 | { 22 | return new DisposableAction(() => { }); 23 | } 24 | 25 | private static readonly object _lock = new object(); 26 | 27 | private static readonly List> Logs 28 | = new List>(); 29 | 30 | private static bool Log(LogLevel logLevel, Func messageFunc, Exception exception, 31 | params object[] formatParameters) 32 | { 33 | string message = null; 34 | if (messageFunc != null) 35 | { 36 | message = messageFunc(); 37 | if (formatParameters != null) 38 | { 39 | message = 40 | LogMessageFormatter.FormatStructuredMessage(message, 41 | formatParameters, 42 | out _); 43 | } 44 | } 45 | 46 | if (message != null || exception != null) 47 | { 48 | lock (_lock) 49 | { 50 | Logs.Add(Tuple.Create(logLevel, message, exception)); 51 | } 52 | } 53 | 54 | return true; 55 | } 56 | 57 | public static Tuple[] PurgeAndFetchLogs() 58 | { 59 | lock (_lock) 60 | { 61 | var result = Logs.ToArray(); 62 | Logs.Clear(); 63 | return result; 64 | } 65 | } 66 | 67 | public class UseMockLogProviderScope : IDisposable 68 | { 69 | private readonly ILogProvider _oldLogProvider; 70 | 71 | public UseMockLogProviderScope() 72 | { 73 | _oldLogProvider = LogProvider.CurrentLogProvider; 74 | LogProvider.SetCurrentLogProvider(new MockLogProvider()); 75 | } 76 | 77 | public void Dispose() 78 | { 79 | LogProvider.SetCurrentLogProvider(_oldLogProvider); 80 | } 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /test/LightStep.Tests/PropagatorTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | using LightStep.Propagation; 6 | using OpenTracing.Propagation; 7 | using Xunit; 8 | 9 | namespace LightStep.Tests 10 | { 11 | public class PropagatorTests 12 | { 13 | [Fact] 14 | public void PropagatorStackInTracerShouldInjectAndExtract() 15 | { 16 | var ps = new PropagatorStack(BuiltinFormats.HttpHeaders); 17 | ps.AddPropagator(new B3Propagator()); 18 | ps.AddPropagator(new HttpHeadersPropagator()); 19 | ps.AddPropagator(new TextMapPropagator()); 20 | 21 | var sr = new SimpleMockRecorder(); 22 | var satOpts = new SatelliteOptions("localhost", 80, true); 23 | var tracerOpts = new Options("TEST").WithSatellite(satOpts).WithAutomaticReporting(false); 24 | 25 | var tracer = new Tracer(tracerOpts, sr, ps); 26 | 27 | var span = tracer.BuildSpan("propTest").Start(); 28 | 29 | var traceId = span.TypedContext().TraceId; 30 | var spanId = span.TypedContext().SpanId; 31 | 32 | var data = new Dictionary(); 33 | 34 | tracer.Inject(span.Context, BuiltinFormats.HttpHeaders, new TextMapInjectAdapter(data)); 35 | 36 | Assert.Equal(traceId, data["ot-tracer-traceid"]); 37 | Assert.Equal(traceId, data["X-B3-TraceId"]); 38 | Assert.Equal(spanId, data["ot-tracer-spanid"]); 39 | Assert.Equal(spanId, data["X-B3-SpanId"]); 40 | Assert.Equal("true", data["ot-tracer-sampled"]); 41 | Assert.Equal("1", data["X-B3-Sampled"]); 42 | 43 | span.Finish(); 44 | 45 | var ctx = tracer.Extract(BuiltinFormats.HttpHeaders, new TextMapExtractAdapter(data)); 46 | 47 | Assert.Equal(ctx.SpanId, spanId); 48 | Assert.Equal(ctx.TraceId, traceId); 49 | } 50 | 51 | [Fact] 52 | public void PropagatorStackShouldAddPropagator() 53 | { 54 | var b3Propagator = new B3Propagator(); 55 | var ps = new PropagatorStack(BuiltinFormats.HttpHeaders); 56 | 57 | Assert.Equal(ps.AddPropagator(Propagators.HttpHeadersPropagator), ps); 58 | Assert.Equal(ps.AddPropagator(b3Propagator), ps); 59 | Assert.Equal(2, ps.Propagators.Count); 60 | Assert.Equal(ps.Propagators[0], Propagators.HttpHeadersPropagator); 61 | Assert.Equal(ps.Propagators[1], b3Propagator); 62 | } 63 | 64 | [Fact] 65 | public void PropagatorStackShouldHandleCustomFormatConstructor() 66 | { 67 | var customFmt = new CustomFormatter(); 68 | var ps = new PropagatorStack(customFmt); 69 | Assert.True(ps.Propagators.Count == 0); 70 | Assert.Equal(ps.Format, customFmt); 71 | } 72 | 73 | [Fact] 74 | public void PropagatorStackShouldHandleEmptyConstructor() 75 | { 76 | var ps = new PropagatorStack(BuiltinFormats.TextMap); 77 | Assert.True(ps.Propagators.Count == 0); 78 | Assert.Equal(ps.Format, BuiltinFormats.TextMap); 79 | } 80 | 81 | [Fact] 82 | public void PropagatorStackShouldInjectExtractAllPropagators() 83 | { 84 | var ps = new PropagatorStack(BuiltinFormats.TextMap); 85 | var httpPropagator = new HttpHeadersPropagator(); 86 | var b3Propagator = new B3Propagator(); 87 | var textPropagator = new TextMapPropagator(); 88 | 89 | ps.AddPropagator(httpPropagator); 90 | ps.AddPropagator(b3Propagator); 91 | ps.AddPropagator(textPropagator); 92 | 93 | var carrier = new Dictionary(); 94 | var context = new SpanContext(0, 0); 95 | 96 | ps.Inject(context, BuiltinFormats.TextMap, new TextMapInjectAdapter(carrier)); 97 | 98 | var propagators = new List {httpPropagator, b3Propagator, textPropagator}; 99 | 100 | foreach (var t in propagators) 101 | { 102 | var extractedContext = 103 | t.Extract(BuiltinFormats.TextMap, new TextMapExtractAdapter(carrier)); 104 | Assert.NotNull(extractedContext); 105 | Assert.Equal(context.TraceId, extractedContext.TraceId); 106 | Assert.Equal(context.SpanId, extractedContext.SpanId); 107 | } 108 | } 109 | 110 | [Fact] 111 | public void EnvoyPropagatorShouldDecodeABinaryContextFromString() 112 | { 113 | UInt64 traceId = 4952807665017200957; 114 | UInt64 spanId = 14848807816610383171; 115 | 116 | var base64Context = "EhQJQwUbbwmQEc4RPaEuilTou0QYAQ=="; 117 | var bs = Convert.FromBase64String(base64Context); 118 | 119 | var envoyPropagator = new EnvoyPropagator(); 120 | var streamCarrier = new MemoryStream(bs); 121 | 122 | var extractedContext = envoyPropagator.Extract(BuiltinFormats.Binary, new BinaryExtractAdapter(streamCarrier)); 123 | Assert.NotNull(extractedContext); 124 | Assert.Equal(traceId, extractedContext.SpanIdValue); 125 | Assert.Equal(spanId, extractedContext.TraceIdValue); 126 | 127 | var reStreamCarrier = new MemoryStream(); 128 | envoyPropagator.Inject(extractedContext, BuiltinFormats.Binary, new BinaryInjectAdapter(reStreamCarrier)); 129 | var reBase64String = Convert.ToBase64String(reStreamCarrier.ToArray()); 130 | Assert.NotNull(reStreamCarrier); 131 | Assert.Equal(base64Context, reBase64String); 132 | } 133 | 134 | [Fact] 135 | public void EnvoyPropagatorShouldEncodeASpanContext() 136 | { 137 | var ctx = new SpanContext(1, 1); 138 | var envoyPropagator = new EnvoyPropagator(); 139 | var carrierStream = new MemoryStream(); 140 | 141 | envoyPropagator.Inject(ctx, BuiltinFormats.Binary, new BinaryInjectAdapter(carrierStream)); 142 | 143 | Assert.NotNull(carrierStream); 144 | Assert.True(carrierStream.Length > 0); 145 | 146 | var extractedContext = 147 | envoyPropagator.Extract(BuiltinFormats.Binary, new BinaryExtractAdapter(carrierStream)); 148 | Assert.NotNull(extractedContext); 149 | Assert.Equal("1", extractedContext.SpanId); 150 | Assert.Equal("1", extractedContext.TraceId); 151 | } 152 | 153 | [Fact] 154 | public void PropagatorStackShouldThrowOnNullConstructor() 155 | { 156 | Assert.Throws(() => new PropagatorStack(null)); 157 | } 158 | 159 | [Fact] 160 | public void PropagatorStackShouldThrowOnNullPropagator() 161 | { 162 | var ps = new PropagatorStack(BuiltinFormats.TextMap); 163 | Assert.Throws(() => ps.AddPropagator(null)); 164 | } 165 | 166 | public enum CasingType 167 | { 168 | LowerCase, 169 | UpperCase, 170 | MixedCase, 171 | } 172 | 173 | private string ToCase(string input, CasingType casingType) 174 | { 175 | switch (casingType) 176 | { 177 | case CasingType.LowerCase: 178 | return input.ToLower(); 179 | case CasingType.UpperCase: 180 | return input.ToUpper(); 181 | } 182 | 183 | return input; 184 | } 185 | 186 | [Theory] 187 | [InlineData(CasingType.LowerCase)] 188 | [InlineData(CasingType.UpperCase)] 189 | [InlineData(CasingType.MixedCase)] 190 | public void HttpHeaderPropagatorIsCaseInsensitive(CasingType casingType) 191 | { 192 | UInt64 spanId = 4952807665017200957; 193 | UInt64 traceId = 14848807816610383171; 194 | 195 | var headers = new Dictionary 196 | { 197 | {ToCase("Ot-Tracer-Spanid", casingType), spanId.ToString("X")}, 198 | {ToCase("Ot-Tracer-Traceid", casingType), traceId.ToString("X")}, 199 | }; 200 | 201 | var httpPropagator = new HttpHeadersPropagator(); 202 | var extractedContext = 203 | httpPropagator.Extract(BuiltinFormats.HttpHeaders, 204 | new TextMapExtractAdapter(headers)); 205 | 206 | Assert.NotNull(extractedContext); 207 | Assert.Equal(spanId.ToString("x"), extractedContext.SpanId); 208 | Assert.Equal(traceId.ToString("x"), extractedContext.TraceId); 209 | } 210 | 211 | [Theory] 212 | [InlineData(CasingType.LowerCase)] 213 | [InlineData(CasingType.UpperCase)] 214 | [InlineData(CasingType.MixedCase)] 215 | public void TextHeaderPropagatorIsCaseSensitive(CasingType casingType) 216 | { 217 | UInt64 spanId = 4952807665017200957; 218 | UInt64 traceId = 14848807816610383171; 219 | 220 | var headers = new Dictionary 221 | { 222 | {ToCase("Ot-Tracer-Spanid", casingType), spanId.ToString("X")}, 223 | {ToCase("Ot-Tracer-Traceid", casingType), traceId.ToString("X")}, 224 | }; 225 | 226 | var textPropagator = new TextMapPropagator(); 227 | var extractedContext = 228 | textPropagator.Extract(BuiltinFormats.TextMap, 229 | new TextMapExtractAdapter(headers)); 230 | 231 | if (casingType != CasingType.LowerCase) 232 | { 233 | Assert.Null(extractedContext); 234 | return; 235 | } 236 | 237 | Assert.NotNull(extractedContext); 238 | Assert.Equal(spanId.ToString("x"), extractedContext.SpanId); 239 | Assert.Equal(traceId.ToString("x"), extractedContext.TraceId); 240 | } 241 | 242 | [Fact] 243 | public void B3PropagatorShouldHandle128bitTraceIds() 244 | { 245 | var propagator = new B3Propagator(); 246 | var traceId = "aef5705a090040838f1359ebafa5c0c6"; 247 | var truncatedTraceId = "8f1359ebafa5c0c6"; 248 | var spanId = "1"; 249 | 250 | var headers = new Dictionary 251 | { 252 | {B3Propagator.TraceIdName, traceId}, 253 | {B3Propagator.SpanIdName, spanId} 254 | }; 255 | var extractedContext = propagator.Extract(BuiltinFormats.TextMap, new TextMapExtractAdapter(headers)); 256 | Assert.Equal(traceId, extractedContext.OriginalTraceId); 257 | Assert.Equal(truncatedTraceId, extractedContext.TraceId); 258 | Assert.Equal(spanId, extractedContext.SpanId); 259 | 260 | headers.Clear(); 261 | propagator.Inject(extractedContext, BuiltinFormats.TextMap, new TextMapInjectAdapter(headers)); 262 | Assert.Equal(traceId, headers[B3Propagator.TraceIdName]); 263 | Assert.Equal(spanId, headers[B3Propagator.SpanIdName]); 264 | } 265 | } 266 | 267 | internal class CustomFormatter : IFormat 268 | { 269 | // dummy class for testing custom formatters 270 | } 271 | } -------------------------------------------------------------------------------- /test/LightStep.Tests/SimpleMockRecorder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace LightStep.Tests 6 | { 7 | public class SimpleMockRecorder : ISpanRecorder 8 | { 9 | private List Spans { get; } = new List(); 10 | 11 | public DateTime ReportStartTime { get; } = DateTime.Now; 12 | public DateTime ReportEndTime { get; set; } 13 | public int DroppedSpanCount { get; set; } 14 | 15 | public void RecordSpan(SpanData span) 16 | { 17 | Spans.Add(span); 18 | } 19 | 20 | public ISpanRecorder GetSpanBuffer() 21 | { 22 | ReportEndTime = DateTime.Now; 23 | return this; 24 | } 25 | 26 | public void ClearSpanBuffer() 27 | { 28 | Spans.Clear(); 29 | } 30 | 31 | public void RecordDroppedSpans(int count) 32 | { 33 | DroppedSpanCount += count; 34 | } 35 | 36 | public IEnumerable GetSpans() 37 | { 38 | return Spans; 39 | } 40 | 41 | public int GetSpanCount() 42 | { 43 | return Spans.Count(); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /test/LightStep.Tests/SpanTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using LightStep.Logging; 6 | using OpenTracing.Tag; 7 | using Xunit; 8 | 9 | namespace LightStep.Tests 10 | { 11 | public class SpanTests 12 | { 13 | private Tracer GetTracer(ISpanRecorder recorder = null) 14 | { 15 | var spanRecorder = recorder ?? new SimpleMockRecorder(); 16 | var satelliteOptions = new SatelliteOptions("localhost", 80, true); 17 | var tracerOptions = new Options("TEST").WithSatellite(satelliteOptions).WithAutomaticReporting(false); 18 | return new Tracer(tracerOptions, spanRecorder); 19 | } 20 | 21 | [Fact] 22 | public void SpansShouldAllowThreadSafeAccess() 23 | { 24 | var recorder = new SimpleMockRecorder(); 25 | var tracer = GetTracer(recorder); 26 | var span = tracer.BuildSpan("testOperation").Start(); 27 | 28 | var t1 = Task.Run(() => 29 | { 30 | span.Log("t1 run"); 31 | span.SetTag("sameTag", "t1"); 32 | span.SetTag("t1Tag", "t1"); 33 | }); 34 | var t2 = Task.Run(() => 35 | { 36 | span.Log("t2 run"); 37 | span.SetTag("sameTag", "t2"); 38 | span.SetTag("t2Tag", "t2"); 39 | }); 40 | 41 | t1.Wait(); 42 | t2.Wait(); 43 | 44 | span.Finish(); 45 | 46 | var finishedSpan = recorder.GetSpans().First(); 47 | 48 | // we expect there to be 2 logs and 3 tags 49 | Assert.True(finishedSpan.LogData.Count == 2); 50 | Assert.True(finishedSpan.Tags.Count == 3); 51 | } 52 | 53 | [Fact] 54 | public void SpansShouldBuildProperStringKeyTags() 55 | { 56 | var recorder = new SimpleMockRecorder(); 57 | var tracer = GetTracer(recorder); 58 | var span = tracer.BuildSpan("testOperation") 59 | .WithTag("boolTrueTag", true) 60 | .WithTag("boolFalseTag", false) 61 | .WithTag("intTag", 0) 62 | .WithTag("stringTag", "test") 63 | .WithTag("doubleTag", 0.1) 64 | .Start(); 65 | span.Finish(); 66 | var finishedSpan = recorder.GetSpans().First(); 67 | 68 | Assert.True((bool) finishedSpan.Tags["boolTrueTag"]); 69 | Assert.False((bool) finishedSpan.Tags["boolFalseTag"]); 70 | Assert.Equal(0, finishedSpan.Tags["intTag"]); 71 | Assert.Equal("test", finishedSpan.Tags["stringTag"]); 72 | Assert.Equal(0.1, finishedSpan.Tags["doubleTag"]); 73 | } 74 | 75 | [Fact] 76 | public void SpansShouldBuildProperTypedKeyTags() 77 | { 78 | var recorder = new SimpleMockRecorder(); 79 | var tracer = GetTracer(recorder); 80 | var span = tracer.BuildSpan("testOperation") 81 | .WithTag(new BooleanTag("testBoolTag"), true) 82 | .WithTag(new IntTag("testIntTag"), 1) 83 | .WithTag(new StringTag("testStringTag"), "test") 84 | .WithTag(new IntOrStringTag("testIntOrStringTagAsString"), "string") 85 | .WithTag(new IntOrStringTag("testIntOrStringTagAsInt"), 1) 86 | .Start(); 87 | span.Finish(); 88 | var finishedSpan = recorder.GetSpans().First(); 89 | 90 | Assert.True((bool) finishedSpan.Tags["testBoolTag"]); 91 | Assert.Equal(1, finishedSpan.Tags["testIntTag"]); 92 | Assert.Equal("test", finishedSpan.Tags["testStringTag"]); 93 | Assert.Equal("string", finishedSpan.Tags["testIntOrStringTagAsString"]); 94 | Assert.Equal(1, finishedSpan.Tags["testIntOrStringTagAsInt"]); 95 | } 96 | 97 | [Fact] 98 | public void SpansShouldRecordLogs() 99 | { 100 | var recorder = new SimpleMockRecorder(); 101 | var tracer = GetTracer(recorder); 102 | var span = tracer.BuildSpan("testOperation").Start(); 103 | span.Log("hello world!"); 104 | span.Finish(); 105 | 106 | var finishedSpan = recorder.GetSpans().First(); 107 | // project the sequence of logdata into an array with one item, the aforementioned log message 108 | var finishedSpanLogData = finishedSpan.LogData.Select( 109 | item => item.Fields.First( 110 | f => f.Value.Equals("hello world!") 111 | ).Value).ToArray()[0]; 112 | 113 | Assert.Equal("hello world!", (string) finishedSpanLogData); 114 | } 115 | 116 | [Fact] 117 | public void SpanIdsShouldBeRandom() 118 | { 119 | // using this like a HashSet since HashSet is not thread-safe 120 | var spanIds = new ConcurrentDictionary(); 121 | var iterations = 1_000_000; 122 | var collisionCount = 0; 123 | var tracer = GetTracer(); 124 | 125 | // CLR black magic happens here to determine degree of parallelism 126 | Parallel.For(0, iterations, i => 127 | { 128 | var span = tracer.BuildSpan("junkSpan").Start(); 129 | span.Finish(); 130 | if (!spanIds.TryAdd(span.Context.SpanId, new byte())) 131 | { 132 | collisionCount++; 133 | } 134 | }); 135 | 136 | // if there were no collisions on insert, we had uniqueness! 137 | Assert.Equal(0, collisionCount); 138 | } 139 | 140 | [Theory] 141 | [InlineData(false)] 142 | [InlineData(true)] 143 | public void SpansShouldLogErrorForAfterFinishActions(bool afterFinish) 144 | { 145 | var tracer = GetTracer(); 146 | var span = tracer.BuildSpan("randomSpan").Start(); 147 | 148 | using (new MockLogProvider.UseMockLogProviderScope()) 149 | { 150 | if (afterFinish) span.Finish(); 151 | 152 | // These actions shouldn't cause exceptions either 153 | // before or after the span is finished 154 | span.SetOperationName("somethingElseRandom"); 155 | span.SetTag("key", "value"); 156 | span.Log("event"); 157 | span.SetBaggageItem("baggageKey", "baggageValue"); 158 | 159 | if (!afterFinish) span.Finish(); 160 | 161 | // Just testing action taken after span is finished 162 | var logs = 163 | MockLogProvider.PurgeAndFetchLogs() 164 | .Where(l => l.Item1 == LogLevel.Error) 165 | .ToArray(); 166 | if (!afterFinish) 167 | { 168 | Assert.Empty(logs); 169 | return; 170 | } 171 | 172 | Assert.Equal(4, logs.Length); 173 | Assert.Contains("finished span", logs[0].Item2); 174 | Assert.Contains("finished span", logs[1].Item2); 175 | Assert.Contains("finished span", logs[2].Item2); 176 | Assert.Contains("finished span", logs[3].Item2); 177 | } 178 | } 179 | } 180 | } -------------------------------------------------------------------------------- /test/LightStep.Tests/TracerLogTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace LightStep.Tests 6 | { 7 | class TracerLogTest 8 | { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/LightStep.TracerPerf.Tests/LightStep.TracerPerf.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | false 6 | LightStep.TracerPerf.Tests 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /test/LightStep.TracerPerf.Tests/README.md: -------------------------------------------------------------------------------- 1 | # tracer-tests 2 | For benchmarking tracers of distributed tracing that implements OpenTracing 3 | 4 | To collect data in terms of memory, network, and log usages, bring in tracer dependencies and run the NUnit tests. 5 | -------------------------------------------------------------------------------- /test/LightStep.TracerPerf.Tests/TracerLogTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Serilog; 3 | 4 | namespace LightStep.TracerPerf.Tests 5 | { 6 | public class TracerLogTest : TracerTestBase 7 | { 8 | [SetUp] 9 | public void SetUp() 10 | { 11 | Log.Logger = new LoggerConfiguration() 12 | .MinimumLevel.Verbose() 13 | .WriteTo.Console() 14 | .CreateLogger(); 15 | 16 | Log.Information("Log is setup!"); 17 | } 18 | 19 | [Test] 20 | public void TestExecute([Values("NoFinishNoDispose", "ExplicitFinish", "FinishOnDispose", "DisposeNoFinish")] 21 | string tracingMethod) 22 | { 23 | Execute(TracingMethods[tracingMethod]); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /test/LightStep.TracerPerf.Tests/TracerMemoryTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NUnit.Framework; 3 | 4 | namespace LightStep.TracerPerf.Tests 5 | { 6 | [SingleThreaded] 7 | public class TracerMemoryTest : TracerTestBase 8 | { 9 | [SetUp] 10 | public void SetUp() 11 | { 12 | Iter = 100; 13 | Chunk = 10000; 14 | } 15 | 16 | [Test] 17 | public void TestExecute_NoFinishNoDispose() 18 | { 19 | Console.WriteLine(nameof(NoFinishNoDispose)); 20 | var heapInfo = Execute(NoFinishNoDispose); 21 | foreach (var num in heapInfo) 22 | { 23 | Console.WriteLine(num); 24 | } 25 | } 26 | 27 | [Test] 28 | public void TestExecute_ExplicitFinish() 29 | { 30 | Console.WriteLine(nameof(ExplicitFinish)); 31 | var heapInfo = Execute(ExplicitFinish); 32 | foreach (var num in heapInfo) 33 | { 34 | Console.WriteLine(num); 35 | } 36 | } 37 | 38 | [Test] 39 | public void TestExecute_FinishOnDispose() 40 | { 41 | Console.WriteLine(nameof(FinishOnDispose)); 42 | var heapInfo = Execute(FinishOnDispose); 43 | foreach (var num in heapInfo) 44 | { 45 | Console.WriteLine(num); 46 | } 47 | } 48 | 49 | [Test] 50 | public void TestExecute_DisposeNoFinish() 51 | { 52 | Console.WriteLine(nameof(DisposeNoFinish)); 53 | var heapInfo = Execute(DisposeNoFinish); 54 | foreach (var num in heapInfo) 55 | { 56 | Console.WriteLine(num); 57 | } 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /test/LightStep.TracerPerf.Tests/TracerNetworkTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using NUnit.Framework; 4 | 5 | namespace LightStep.TracerPerf.Tests 6 | { 7 | [SingleThreaded] 8 | public class TracerNetworkTest : TracerTestBase 9 | { 10 | [SetUp] 11 | public void SetUp() 12 | { 13 | Iter = 10000; 14 | Chunk = 1000; 15 | BufferSize = 2000; 16 | ReportPeriod = .5; 17 | } 18 | 19 | [Test, Order(1)] 20 | public void TestExecute_Localhost() 21 | { 22 | Host = "localhost"; 23 | Execute(ExplicitFinish); 24 | Thread.Sleep(TimeSpan.FromSeconds(20)); 25 | } 26 | 27 | [Test, Order(2)] 28 | public void TestExecute_GarbageHost() 29 | { 30 | Host = "garbage"; 31 | Execute(FinishOnDispose); 32 | Thread.Sleep(TimeSpan.FromSeconds(10)); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /test/LightStep.TracerPerf.Tests/TracerTestBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using LightStep; 5 | using OpenTracing; 6 | 7 | namespace LightStep.TracerPerf.Tests 8 | { 9 | public class TracerTestBase 10 | { 11 | protected string Host { get; set; } = "localhost" ; 12 | protected int Port { get; set; } = 8360; 13 | protected double ReportPeriod { get; set; } = .5; 14 | protected int BufferSize { get; set; } = 200; 15 | protected string Token { get; set; } = "developer"; 16 | protected long Iter { get; set; } = 10; 17 | protected long Chunk { get; set; } = 10; 18 | protected Tracer Tracer; 19 | 20 | protected static readonly Action NoFinishNoDispose = t => t.BuildSpan("test").StartActive(false); 21 | 22 | protected static Action ExplicitFinish => t => 23 | { 24 | t.BuildSpan("test").StartActive(true); 25 | t.ActiveSpan.Finish(); 26 | }; 27 | 28 | protected static readonly Action FinishOnDispose = t => 29 | { 30 | var scope = t.BuildSpan("test").StartActive(true); 31 | scope.Dispose(); 32 | }; 33 | 34 | protected static readonly Action DisposeNoFinish = t => 35 | { 36 | var scope = t.BuildSpan("test").StartActive(false); 37 | scope.Dispose(); 38 | }; 39 | 40 | protected readonly Dictionary> TracingMethods = new Dictionary>() 41 | { 42 | { "NoFinishNoDispose", NoFinishNoDispose }, 43 | { "ExplicitFinish", ExplicitFinish }, 44 | { "FinishOnDispose", FinishOnDispose }, 45 | { "DisposeNoFinish", DisposeNoFinish }, 46 | }; 47 | 48 | private void Init() 49 | { 50 | var overrideTags = new Dictionary 51 | { 52 | { 53 | LightStepConstants.ComponentNameKey, "ServiceName" 54 | }, 55 | }; 56 | var satelliteOptions = new SatelliteOptions(Host, Port, true); 57 | Options options = new Options(Token) 58 | .WithSatellite(satelliteOptions) 59 | .WithTags(overrideTags) 60 | .WithMaxBufferedSpans(BufferSize) 61 | .WithReportPeriod(TimeSpan.FromSeconds(ReportPeriod)); 62 | Tracer = new Tracer(options); 63 | } 64 | 65 | protected List Execute(Action buildSpan) 66 | { 67 | Init(); 68 | var heapInfo = new List(); 69 | for (var i = 0; i < Iter; i++) 70 | { 71 | for (var j = 0; j < Chunk; j++) 72 | { 73 | buildSpan(Tracer); 74 | } 75 | var gcMemoryInfo = GC.GetGCMemoryInfo(); 76 | heapInfo.Add(gcMemoryInfo.HeapSizeBytes); 77 | Console.WriteLine(gcMemoryInfo.HeapSizeBytes); 78 | Tracer.Flush(); 79 | } 80 | 81 | Tracer = null; 82 | var min = Enumerable.Min(heapInfo); 83 | return heapInfo.Select(e => e - min).ToList(); 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /tracerSign.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lightstep/lightstep-tracer-csharp/59c3e63658fd69bf732ddc019e90607d9b014c11/tracerSign.snk --------------------------------------------------------------------------------