├── .appveyor.yml ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .travis.yml ├── .vscode ├── extensions.json ├── settings.json └── tasks.json ├── Directory.Build.props ├── LICENSE ├── NuGet.config ├── OpenTracing.Contrib.sln ├── README.md ├── RELEASE.md ├── SignKey.snk ├── benchmarks └── OpenTracing.Contrib.NetCore.Benchmarks │ ├── AspNetCore │ └── RequestDiagnosticsBenchmark.cs │ ├── CoreFx │ └── HttpHandlerDiagnosticsBenchmark.cs │ ├── InstrumentationMode.cs │ ├── OpenTracing.Contrib.NetCore.Benchmarks.csproj │ ├── OpenTracingBuilderExtensions.cs │ └── Program.cs ├── build.ps1 ├── global.json ├── launch-sample.ps1 ├── package-icon.png ├── samples ├── net6.0 │ ├── CustomersApi │ │ ├── Controllers │ │ │ └── CustomersController.cs │ │ ├── CustomersApi.csproj │ │ ├── DataStore │ │ │ └── CustomerDbContext.cs │ │ ├── Program.cs │ │ ├── Properties │ │ │ └── launchSettings.json │ │ ├── Startup.cs │ │ └── appsettings.json │ ├── FrontendWeb │ │ ├── Controllers │ │ │ └── HomeController.cs │ │ ├── FrontendWeb.csproj │ │ ├── Program.cs │ │ ├── Properties │ │ │ └── launchSettings.json │ │ ├── Startup.cs │ │ ├── Views │ │ │ ├── Home │ │ │ │ ├── Index.cshtml │ │ │ │ └── PlaceOrder.cshtml │ │ │ └── _ViewImports.cshtml │ │ └── appsettings.json │ ├── OrdersApi │ │ ├── Controllers │ │ │ └── OrdersController.cs │ │ ├── DataStore │ │ │ ├── Order.cs │ │ │ └── OrdersDbContext.cs │ │ ├── OrdersApi.csproj │ │ ├── Program.cs │ │ ├── Properties │ │ │ └── launchSettings.json │ │ ├── Startup.cs │ │ └── appsettings.json │ ├── Shared │ │ ├── Constants.cs │ │ ├── Customer.cs │ │ ├── JaegerServiceCollectionExtensions.cs │ │ ├── PlaceOrderCommand.cs │ │ └── Shared.csproj │ └── TrafficGenerator │ │ ├── Program.cs │ │ ├── TrafficGenerator.csproj │ │ ├── Worker.cs │ │ └── appsettings.json ├── net7.0 │ ├── CustomersApi │ │ ├── CustomersApi.csproj │ │ ├── DataStore │ │ │ └── CustomerDbContext.cs │ │ ├── Program.cs │ │ ├── Properties │ │ │ └── launchSettings.json │ │ └── appsettings.json │ ├── FrontendWeb │ │ ├── Controllers │ │ │ └── HomeController.cs │ │ ├── FrontendWeb.csproj │ │ ├── Program.cs │ │ ├── Properties │ │ │ └── launchSettings.json │ │ ├── Views │ │ │ ├── Home │ │ │ │ ├── Index.cshtml │ │ │ │ └── PlaceOrder.cshtml │ │ │ └── _ViewImports.cshtml │ │ └── appsettings.json │ ├── OrdersApi │ │ ├── Controllers │ │ │ └── OrdersController.cs │ │ ├── DataStore │ │ │ ├── Order.cs │ │ │ └── OrdersDbContext.cs │ │ ├── OrdersApi.csproj │ │ ├── Program.cs │ │ ├── Properties │ │ │ └── launchSettings.json │ │ └── appsettings.json │ ├── Shared │ │ ├── Constants.cs │ │ ├── Customer.cs │ │ ├── JaegerServiceCollectionExtensions.cs │ │ ├── PlaceOrderCommand.cs │ │ └── Shared.csproj │ └── TrafficGenerator │ │ ├── Program.cs │ │ ├── TrafficGenerator.csproj │ │ ├── Worker.cs │ │ └── appsettings.json └── netcoreapp3.1 │ ├── CustomersApi │ ├── Controllers │ │ └── CustomersController.cs │ ├── CustomersApi.csproj │ ├── DataStore │ │ └── CustomerDbContext.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Startup.cs │ └── appsettings.json │ ├── FrontendWeb │ ├── Controllers │ │ └── HomeController.cs │ ├── FrontendWeb.csproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Startup.cs │ ├── Views │ │ ├── Home │ │ │ ├── Index.cshtml │ │ │ └── PlaceOrder.cshtml │ │ └── _ViewImports.cshtml │ └── appsettings.json │ ├── OrdersApi │ ├── Controllers │ │ └── OrdersController.cs │ ├── DataStore │ │ ├── Order.cs │ │ └── OrdersDbContext.cs │ ├── OrdersApi.csproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Startup.cs │ └── appsettings.json │ ├── Shared │ ├── Constants.cs │ ├── Customer.cs │ ├── JaegerServiceCollectionExtensions.cs │ ├── PlaceOrderCommand.cs │ └── Shared.csproj │ └── TrafficGenerator │ ├── Program.cs │ ├── TrafficGenerator.csproj │ ├── Worker.cs │ └── appsettings.json ├── src ├── Directory.Build.props └── OpenTracing.Contrib.NetCore │ ├── AspNetCore │ ├── AspNetCoreDiagnosticOptions.cs │ ├── AspNetCoreDiagnostics.cs │ ├── HostingOptions.cs │ └── RequestHeadersExtractAdapter.cs │ ├── Configuration │ ├── DiagnosticOptions.cs │ ├── IOpenTracingBuilder.cs │ ├── OpenTracingBuilder.cs │ ├── OpenTracingBuilderExtensions.cs │ └── ServiceCollectionExtensions.cs │ ├── EntityFrameworkCore │ ├── EntityFrameworkCoreDiagnosticOptions.cs │ └── EntityFrameworkCoreDiagnostics.cs │ ├── GenericListeners │ ├── GenericDiagnosticOptions.cs │ └── GenericDiagnostics.cs │ ├── HttpHandler │ ├── HttpHandlerDiagnosticOptions.cs │ ├── HttpHandlerDiagnostics.cs │ └── HttpHeadersInjectAdapter.cs │ ├── InstrumentationService.cs │ ├── Internal │ ├── DiagnosticEventObserver.cs │ ├── DiagnosticManager.cs │ ├── DiagnosticManagerOptions.cs │ ├── DiagnosticObserver.cs │ ├── GenericEventProcessor.cs │ ├── GlobalTracerAccessor.cs │ ├── IGlobalTracerAccessor.cs │ ├── PropertyFetcher.cs │ ├── SpanExtensions.cs │ └── TracerExtensions.cs │ ├── Logging │ ├── OpenTracingLogger.cs │ └── OpenTracingLoggerProvider.cs │ ├── MicrosoftSqlClient │ ├── MicrosoftSqlClientDiagnosticOptions.cs │ └── MicrosoftSqlClientDiagnostics.cs │ ├── OpenTracing.Contrib.NetCore.csproj │ ├── Properties │ └── AssemblyInfo.cs │ └── SystemSqlClient │ ├── SqlClientDiagnosticOptions.cs │ └── SqlClientDiagnostics.cs ├── test └── OpenTracing.Contrib.NetCore.Tests │ ├── AspNetCore │ └── HostingTest.cs │ ├── CoreFx │ └── HttpHandlerDiagnosticTest.cs │ ├── Internal │ ├── DiagnosticManagerTest.cs │ └── PropertyFetcherTest.cs │ ├── Logging │ ├── LoggingDependencyInjectionTest.cs │ └── LoggingTest.cs │ ├── OpenTracing.Contrib.NetCore.Tests.csproj │ └── XunitLogging │ ├── XunitLoggerFactoryExtensions.cs │ └── XunitLoggerProvider.cs └── version.props /.appveyor.yml: -------------------------------------------------------------------------------- 1 | # AppVeyor Build number is incremental and not related to actual version number of the product 2 | version: '{build}' 3 | 4 | image: Visual Studio 2019 5 | 6 | init: 7 | - cmd: git config --global core.autocrlf true 8 | 9 | environment: 10 | global: 11 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 12 | DOTNET_CLI_TELEMETRY_OPTOUT: 1 13 | 14 | build_script: 15 | - ps: Invoke-WebRequest https://dot.net/v1/dotnet-install.ps1 -OutFile .\dotnet-install.ps1 16 | - ps: .\dotnet-install.ps1 -Runtime dotnet -Version 3.1.10 17 | - ps: .\dotnet-install.ps1 -Version 6.0.100 18 | - ps: .\dotnet-install.ps1 -Version 7.0.100 19 | - ps: .\build.ps1 20 | 21 | test: off 22 | 23 | artifacts: 24 | - path: artifacts\nuget\*.nupkg 25 | name: NuGet 26 | - path: artifacts\nuget\*.snupkg 27 | name: Symbols 28 | 29 | # Deploy every successful build (except PRs) to development feed 30 | nuget: 31 | account_feed: true 32 | project_feed: true 33 | disable_publish_on_pr: true 34 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Sqlite DBs in samples 2 | *.db 3 | 4 | ## Ignore Visual Studio temporary files, build results, and 5 | ## files generated by popular Visual Studio add-ons. 6 | 7 | BenchmarkDotNet.Artifacts/ 8 | 9 | # User-specific files 10 | *.suo 11 | *.user 12 | *.userosscache 13 | *.sln.docstates 14 | 15 | # User-specific files (MonoDevelop/Xamarin Studio) 16 | *.userprefs 17 | 18 | # Build results 19 | [Dd]ebug/ 20 | [Dd]ebugPublic/ 21 | [Rr]elease/ 22 | [Rr]eleases/ 23 | x64/ 24 | x86/ 25 | bld/ 26 | [Bb]in/ 27 | [Oo]bj/ 28 | [Ll]og/ 29 | 30 | # Visual Studio 2015 cache/options directory 31 | .vs/ 32 | # Uncomment if you have tasks that create the project's static files in wwwroot 33 | #wwwroot/ 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 | # DNX 49 | project.lock.json 50 | artifacts/ 51 | .build/ 52 | 53 | *_i.c 54 | *_p.c 55 | *_i.h 56 | *.ilk 57 | *.meta 58 | *.obj 59 | *.pch 60 | *.pdb 61 | *.pgc 62 | *.pgd 63 | *.rsp 64 | *.sbr 65 | *.tlb 66 | *.tli 67 | *.tlh 68 | *.tmp 69 | *.tmp_proj 70 | *.log 71 | *.vspscc 72 | *.vssscc 73 | .builds 74 | *.pidb 75 | *.svclog 76 | *.scc 77 | 78 | # Chutzpah Test files 79 | _Chutzpah* 80 | 81 | # Visual C++ cache files 82 | ipch/ 83 | *.aps 84 | *.ncb 85 | *.opendb 86 | *.opensdf 87 | *.sdf 88 | *.cachefile 89 | *.VC.db 90 | *.VC.VC.opendb 91 | 92 | # Visual Studio profiler 93 | *.psess 94 | *.vsp 95 | *.vspx 96 | *.sap 97 | 98 | # TFS 2012 Local Workspace 99 | $tf/ 100 | 101 | # Guidance Automation Toolkit 102 | *.gpState 103 | 104 | # ReSharper is a .NET coding add-in 105 | _ReSharper*/ 106 | *.[Rr]e[Ss]harper 107 | *.DotSettings.user 108 | 109 | # JustCode is a .NET coding add-in 110 | .JustCode 111 | 112 | # TeamCity is a build add-in 113 | _TeamCity* 114 | 115 | # DotCover is a Code Coverage Tool 116 | *.dotCover 117 | 118 | # NCrunch 119 | _NCrunch_* 120 | .*crunch*.local.xml 121 | nCrunchTemp_* 122 | 123 | # MightyMoose 124 | *.mm.* 125 | AutoTest.Net/ 126 | 127 | # Web workbench (sass) 128 | .sass-cache/ 129 | 130 | # Installshield output folder 131 | [Ee]xpress/ 132 | 133 | # DocProject is a documentation generator add-in 134 | DocProject/buildhelp/ 135 | DocProject/Help/*.HxT 136 | DocProject/Help/*.HxC 137 | DocProject/Help/*.hhc 138 | DocProject/Help/*.hhk 139 | DocProject/Help/*.hhp 140 | DocProject/Help/Html2 141 | DocProject/Help/html 142 | 143 | # Click-Once directory 144 | publish/ 145 | 146 | # Publish Web Output 147 | *.[Pp]ublish.xml 148 | *.azurePubxml 149 | # TODO: Comment the next line if you want to checkin your web deploy settings 150 | # but database connection strings (with potential passwords) will be unencrypted 151 | *.pubxml 152 | *.publishproj 153 | 154 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 155 | # checkin your Azure Web App publish settings, but sensitive information contained 156 | # in these scripts will be unencrypted 157 | PublishScripts/ 158 | 159 | # NuGet Packages 160 | *.nupkg 161 | # The packages folder can be ignored because of Package Restore 162 | **/packages/* 163 | # except build/, which is used as an MSBuild target. 164 | !**/packages/build/ 165 | # Uncomment if necessary however generally it will be regenerated when needed 166 | #!**/packages/repositories.config 167 | # NuGet v3's project.json files produces more ignoreable files 168 | *.nuget.props 169 | *.nuget.targets 170 | 171 | # Microsoft Azure Build Output 172 | csx/ 173 | *.build.csdef 174 | 175 | # Microsoft Azure Emulator 176 | ecf/ 177 | rcf/ 178 | 179 | # Windows Store app package directories and files 180 | AppPackages/ 181 | BundleArtifacts/ 182 | Package.StoreAssociation.xml 183 | _pkginfo.txt 184 | 185 | # Visual Studio cache files 186 | # files ending in .cache can be ignored 187 | *.[Cc]ache 188 | # but keep track of directories ending in .cache 189 | !*.[Cc]ache/ 190 | 191 | # Others 192 | ClientBin/ 193 | ~$* 194 | *~ 195 | *.dbmdl 196 | *.dbproj.schemaview 197 | *.pfx 198 | *.publishsettings 199 | node_modules/ 200 | orleans.codegen.cs 201 | 202 | # Since there are multiple workflows, uncomment next line to ignore bower_components 203 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 204 | #bower_components/ 205 | 206 | # RIA/Silverlight projects 207 | Generated_Code/ 208 | 209 | # Backup & report files from converting an old project file 210 | # to a newer Visual Studio version. Backup files are not needed, 211 | # because we have git ;-) 212 | _UpgradeReport_Files/ 213 | Backup*/ 214 | UpgradeLog*.XML 215 | UpgradeLog*.htm 216 | 217 | # SQL Server files 218 | *.mdf 219 | *.ldf 220 | 221 | # Business Intelligence projects 222 | *.rdl.data 223 | *.bim.layout 224 | *.bim_*.settings 225 | 226 | # Microsoft Fakes 227 | FakesAssemblies/ 228 | 229 | # GhostDoc plugin setting file 230 | *.GhostDoc.xml 231 | 232 | # Node.js Tools for Visual Studio 233 | .ntvs_analysis.dat 234 | 235 | # Visual Studio 6 build log 236 | *.plg 237 | 238 | # Visual Studio 6 workspace options file 239 | *.opt 240 | 241 | # Visual Studio LightSwitch build output 242 | **/*.HTMLClient/GeneratedArtifacts 243 | **/*.DesktopClient/GeneratedArtifacts 244 | **/*.DesktopClient/ModelManifest.xml 245 | **/*.Server/GeneratedArtifacts 246 | **/*.Server/ModelManifest.xml 247 | _Pvt_Extensions 248 | 249 | # Paket dependency manager 250 | .paket/paket.exe 251 | paket-files/ 252 | 253 | # FAKE - F# Make 254 | .fake/ 255 | 256 | # JetBrains Rider 257 | .idea/ 258 | *.sln.iml 259 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: csharp 2 | 3 | matrix: 4 | include: 5 | - os: linux 6 | dist: xenial 7 | sudo: required 8 | mono: none 9 | dotnet: 6.0.100 10 | addons: 11 | apt: 12 | sources: 13 | - sourceline: "deb [arch=amd64] https://packages.microsoft.com/ubuntu/16.04/prod xenial main" 14 | key_url: "https://packages.microsoft.com/keys/microsoft.asc" 15 | packages: 16 | - powershell 17 | - dotnet-hosting-2.0.7 18 | - aspnetcore-runtime-2.1 19 | - aspnetcore-runtime-3.1 20 | - aspnetcore-runtime-5.0 21 | 22 | env: 23 | global: 24 | - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 25 | - DOTNET_CLI_TELEMETRY_OPTOUT: 1 26 | 27 | script: 28 | - pwsh ./build.ps1 29 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "EditorConfig.EditorConfig", 4 | "formulahendry.dotnet-test-explorer", 5 | "ms-vscode.csharp", 6 | "ms-vscode.powershell" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "dotnet-test-explorer.testProjectPath": "test/OpenTracing.Contrib.NetCore.Tests" 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}", 11 | "--no-restore" 12 | ], 13 | "problemMatcher": "$msCompile", 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | false 6 | 7 | latest 8 | true 9 | true 10 | $(MSBuildThisFileDirectory)SignKey.snk 11 | true 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![nuget](https://img.shields.io/nuget/v/OpenTracing.Contrib.NetCore.svg?logo=nuget)](https://www.nuget.org/packages/OpenTracing.Contrib.NetCore) 2 | 3 | # OpenTracing instrumentation for .NET Core apps 4 | 5 | This repository provides OpenTracing instrumentation for .NET Core based applications. 6 | It can be used with any OpenTracing compatible tracer. 7 | 8 | _**IMPORTANT:** OpenTracing and OpenCensus have merget to form **[OpenTelemetry](https://opentelemetry.io)**! The OpenTelemetry .NET library can be found at [https://github.com/open-telemetry/opentelemetry-dotnet](https://github.com/open-telemetry/opentelemetry-dotnet)._ 9 | 10 | ## Supported .NET versions 11 | 12 | This project currently only supports apps targeting .NET Core 3.1, .NET 6.0, or .NET 7.0! 13 | 14 | This project DOES NOT support the full .NET framework as that uses different instrumentation code. 15 | 16 | ## Supported libraries and frameworks 17 | 18 | #### DiagnosticSource based instrumentation 19 | 20 | This project supports any library or framework that uses .NET's [`DiagnosticSource`](https://github.com/dotnet/runtime/blob/master/src/libraries/System.Diagnostics.DiagnosticSource/src/DiagnosticSourceUsersGuide.md) 21 | to instrument its code. It will create a span for every [`Activity`](https://github.com/dotnet/runtime/blob/master/src/libraries/System.Diagnostics.DiagnosticSource/src/ActivityUserGuide.md) 22 | and it will create `span.Log` calls for all other diagnostic events. 23 | 24 | To further improve the tracing output, the library provides enhanced instrumentation 25 | (Inject/Extract, tags, configuration options) for the following libraries / frameworks: 26 | 27 | * ASP.NET Core 28 | * Entity Framework Core 29 | * System.Net.Http (HttpClient) 30 | * System.Data.SqlClient 31 | * Microsoft.Data.SqlClient 32 | 33 | #### Microsoft.Extensions.Logging based instrumentation 34 | 35 | This project also adds itself as a logger provider for logging events from the `Microsoft.Extensions.Logging` system. 36 | It will create `span.Log` calls for each logging event, however it will only create them if there is an active span (`ITracer.ActiveSpan`). 37 | 38 | ## Usage 39 | 40 | This project depends on several packages from Microsofts `Microsoft.Extensions.*` stack (e.g. Dependency Injection, Logging) 41 | so its main use case is ASP.NET Core apps and any other Microsoft.Extensions-based console apps. 42 | 43 | ##### 1. Add the NuGet package `OpenTracing.Contrib.NetCore` to your project. 44 | 45 | ##### 2. Add the OpenTracing services to your `IServiceCollection` via `services.AddOpenTracing()`. 46 | 47 | How you do this depends on how you've setup the `Microsoft.Extensions.DependencyInjection` system in your app. 48 | 49 | In ASP.NET Core apps you can add the call to your `ConfigureServices` method (of your `Program.cs` file): 50 | 51 | ```csharp 52 | public static IWebHost BuildWebHost(string[] args) 53 | { 54 | return WebHost.CreateDefaultBuilder(args) 55 | .UseStartup() 56 | .ConfigureServices(services => 57 | { 58 | // Enables and automatically starts the instrumentation! 59 | services.AddOpenTracing(); 60 | }) 61 | .Build(); 62 | } 63 | ``` 64 | 65 | ##### 3. Make sure `InstrumentationService`, which implements `IHostedService`, is started. 66 | 67 | `InstrumentationService` is responsible for starting and stopping the instrumentation. 68 | The service implements `IHostedService` so **it is automatically started in ASP.NET Core**, 69 | however if you have your own console host, you manually have to call `StartAsync` and `StopAsync`. 70 | 71 | Note that .NET Core 2.1 greatly simplified this setup by introducing a generic `HostBuilder` that works similar to the existing `WebHostBuilder` from ASP.NET Core. Have a look at the `TrafficGenerator` sample for an example of a `HostBuilder` based console application. 72 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Release Process 2 | 3 | The release process consists of these steps: 4 | 1. Create a GitHub release with release notes. The tag name must be a semantic version, prefixed with "v" - e.g. `v0.1.0` or `v0.1.0-rc1` 5 | 1. Wait for the AppVeyor build to finish the *tag* build: https://ci.appveyor.com/project/opentracing/csharp-netcore 6 | 1. As a signed-in AppVeyor user, click "Deploy" on the build details page and select "NuGet (OpenTracing)". 7 | This will upload the packages to NuGet.org 8 | -------------------------------------------------------------------------------- /SignKey.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentracing-contrib/csharp-netcore/f64af79ae582cebf52dcdb54797d2545a2bb0565/SignKey.snk -------------------------------------------------------------------------------- /benchmarks/OpenTracing.Contrib.NetCore.Benchmarks/AspNetCore/RequestDiagnosticsBenchmark.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using System.Threading.Tasks; 4 | using BenchmarkDotNet.Attributes; 5 | using Microsoft.AspNetCore; 6 | using Microsoft.AspNetCore.Builder; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.AspNetCore.Http; 9 | using Microsoft.AspNetCore.Mvc.Testing; 10 | using Microsoft.Extensions.DependencyInjection; 11 | using Microsoft.Extensions.Logging; 12 | 13 | namespace OpenTracing.Contrib.NetCore.Benchmarks.AspNetCore 14 | { 15 | public class TestProgramFactory : WebApplicationFactory 16 | { 17 | protected override IWebHostBuilder CreateWebHostBuilder() 18 | { 19 | var host = WebHost.CreateDefaultBuilder() 20 | // https://stackoverflow.com/a/69776251/5214796 21 | .UseSetting("TEST_CONTENTROOT_OPENTRACING_CONTRIB_NETCORE_TESTS", "") 22 | .ConfigureServices(services => 23 | { 24 | }) 25 | .Configure(app => 26 | { 27 | app.UseRouting(); 28 | app.UseEndpoints(endpoints => 29 | { 30 | endpoints.MapGet("/foo", async context => 31 | { 32 | await context.Response.WriteAsync("Hello"); 33 | }); 34 | 35 | endpoints.MapGet("/exception", _ => 36 | { 37 | throw new InvalidOperationException("You shall not pass"); 38 | }); 39 | }); 40 | }); 41 | 42 | return host; 43 | } 44 | } 45 | 46 | public class RequestDiagnosticsBenchmark 47 | { 48 | private WebApplicationFactory _factory; 49 | private HttpClient _client; 50 | 51 | [Params(InstrumentationMode.None, InstrumentationMode.Noop, InstrumentationMode.Mock)] 52 | public InstrumentationMode Mode { get; set; } 53 | 54 | [GlobalSetup] 55 | public void GlobalSetup() 56 | { 57 | _factory = new TestProgramFactory() 58 | .WithWebHostBuilder(x => 59 | { 60 | x.ConfigureLogging(l => l.ClearProviders()); 61 | 62 | x.ConfigureServices(services => 63 | { 64 | services.AddOpenTracingCoreServices(builder => 65 | { 66 | builder.AddAspNetCore(); 67 | builder.AddBenchmarkTracer(Mode); 68 | }); 69 | }); 70 | }); 71 | 72 | _client = _factory.CreateClient(); 73 | } 74 | 75 | [GlobalCleanup] 76 | public void GlobalCleanup() 77 | { 78 | _factory.Dispose(); 79 | } 80 | 81 | [Benchmark] 82 | public async Task GetAsync() 83 | { 84 | await _client.GetAsync("/foo"); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /benchmarks/OpenTracing.Contrib.NetCore.Benchmarks/CoreFx/HttpHandlerDiagnosticsBenchmark.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Http; 4 | using System.Reflection; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using BenchmarkDotNet.Attributes; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using OpenTracing.Contrib.NetCore.Internal; 10 | 11 | namespace OpenTracing.Contrib.NetCore.Benchmarks.CoreFx 12 | { 13 | public class HttpHandlerDiagnosticsBenchmark 14 | { 15 | private HttpClient _httpClient; 16 | private ServiceProvider _serviceProvider; 17 | 18 | [Params(InstrumentationMode.None, InstrumentationMode.Noop, InstrumentationMode.Mock)] 19 | public InstrumentationMode Mode { get; set; } 20 | 21 | [GlobalSetup] 22 | public void GlobalSetup() 23 | { 24 | _httpClient = CreateHttpClient(); 25 | 26 | _serviceProvider = new ServiceCollection() 27 | .AddLogging() 28 | .AddOpenTracingCoreServices(builder => 29 | { 30 | builder.AddBenchmarkTracer(Mode); 31 | builder.AddHttpHandler(); 32 | }) 33 | .BuildServiceProvider(); 34 | 35 | var diagnosticsManager = _serviceProvider.GetRequiredService(); 36 | diagnosticsManager.Start(); 37 | } 38 | 39 | [GlobalCleanup] 40 | public void GlobalCleanup() 41 | { 42 | (_serviceProvider as IDisposable).Dispose(); 43 | } 44 | 45 | [Benchmark] 46 | public Task HttpClient_GetAsync() 47 | { 48 | return _httpClient.GetAsync("http://www.example.com"); 49 | } 50 | 51 | private static HttpClient CreateHttpClient() 52 | { 53 | // Inner handler for mocking the result 54 | var httpHandler = new MockHttpMessageHandler(); 55 | 56 | // Wrap with DiagnosticsHandler (which is internal :( ) 57 | Type type = typeof(HttpClientHandler).Assembly.GetType("System.Net.Http.DiagnosticsHandler"); 58 | ConstructorInfo constructor = type.GetConstructors(BindingFlags.Instance | BindingFlags.Public)[0]; 59 | HttpMessageHandler diagnosticsHandler = (HttpMessageHandler)constructor.Invoke(new object[] { httpHandler }); 60 | 61 | return new HttpClient(diagnosticsHandler); 62 | } 63 | 64 | private class MockHttpMessageHandler : HttpMessageHandler 65 | { 66 | protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 67 | { 68 | // HACK: There MUST be an awaiter otherwise exceptions are not caught by the DiagnosticsHandler. 69 | // https://github.com/dotnet/corefx/pull/27472 70 | await Task.CompletedTask; 71 | 72 | return new HttpResponseMessage(HttpStatusCode.OK) 73 | { 74 | RequestMessage = request, 75 | Content = new StringContent("Response") 76 | }; 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /benchmarks/OpenTracing.Contrib.NetCore.Benchmarks/InstrumentationMode.cs: -------------------------------------------------------------------------------- 1 | namespace OpenTracing.Contrib.NetCore.Benchmarks 2 | { 3 | public enum InstrumentationMode 4 | { 5 | None, 6 | Noop, 7 | Mock 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /benchmarks/OpenTracing.Contrib.NetCore.Benchmarks/OpenTracing.Contrib.NetCore.Benchmarks.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp3.1;net6.0;net7.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /benchmarks/OpenTracing.Contrib.NetCore.Benchmarks/OpenTracingBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using OpenTracing.Contrib.NetCore.Internal; 4 | using OpenTracing.Mock; 5 | using OpenTracing.Noop; 6 | 7 | namespace OpenTracing.Contrib.NetCore.Benchmarks 8 | { 9 | public static class OpenTracingBuilderExtensions 10 | { 11 | public static IOpenTracingBuilder AddBenchmarkTracer(this IOpenTracingBuilder builder, InstrumentationMode mode) 12 | { 13 | if (builder == null) 14 | throw new ArgumentNullException(nameof(builder)); 15 | 16 | ITracer tracer = mode == InstrumentationMode.Mock 17 | ? new MockTracer() 18 | : NoopTracerFactory.Create(); 19 | 20 | bool startInstrumentationForNoopTracer = mode == InstrumentationMode.Noop; 21 | 22 | builder.Services.AddSingleton(tracer); 23 | builder.Services.Configure(options => options.StartInstrumentationForNoopTracer = startInstrumentationForNoopTracer); 24 | 25 | return builder; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /benchmarks/OpenTracing.Contrib.NetCore.Benchmarks/Program.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Running; 2 | 3 | namespace OpenTracing.Contrib.NetCore.Benchmarks 4 | { 5 | class Program 6 | { 7 | static void Main(string[] args) 8 | { 9 | BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding(PositionalBinding = $false)] 2 | param( 3 | [string] $ArtifactsPath = (Join-Path $PWD "artifacts"), 4 | [string] $BuildConfiguration = "Release", 5 | 6 | [bool] $RunBuild = $true, 7 | [bool] $RunTests = $true 8 | ) 9 | 10 | $ErrorActionPreference = "Stop" 11 | $Stopwatch = [System.Diagnostics.Stopwatch]::StartNew() 12 | 13 | function Task { 14 | [CmdletBinding()] param ( 15 | [Parameter(Mandatory = $true)] [string] $name, 16 | [Parameter(Mandatory = $false)] [bool] $runTask, 17 | [Parameter(Mandatory = $false)] [scriptblock] $cmd 18 | ) 19 | 20 | if ($cmd -eq $null) { 21 | throw "Command is missing for task '$name'. Make sure the starting '{' is on the same line as the term 'Task'. E.g. 'Task `"$name`" `$Run$name {'" 22 | } 23 | 24 | if ($runTask -eq $true) { 25 | Write-Host "`n------------------------- [$name] -------------------------`n" -ForegroundColor Cyan 26 | $sw = [System.Diagnostics.Stopwatch]::StartNew() 27 | & $cmd 28 | Write-Host "`nTask '$name' finished in $($sw.Elapsed.TotalSeconds) sec." 29 | } 30 | else { 31 | Write-Host "`n------------------ Skipping task '$name' ------------------" -ForegroundColor Yellow 32 | } 33 | } 34 | 35 | 36 | Task "Init" $true { 37 | 38 | if ($ArtifactsPath -eq $null) { "Property 'ArtifactsPath' may not be null." } 39 | if ($BuildConfiguration -eq $null) { throw "Property 'BuildConfiguration' may not be null." } 40 | if ((Get-Command "dotnet" -ErrorAction SilentlyContinue) -eq $null) { throw "'dotnet' command not found. Is .NET Core SDK installed?" } 41 | 42 | Write-Host "ArtifactsPath: $ArtifactsPath" 43 | Write-Host "BuildConfiguration: $BuildConfiguration" 44 | Write-Host ".NET Core SDK: $(dotnet --version)`n" 45 | 46 | Remove-Item -Path $ArtifactsPath -Recurse -Force -ErrorAction Ignore 47 | New-Item $ArtifactsPath -ItemType Directory -ErrorAction Ignore | Out-Null 48 | Write-Host "Created artifacts folder '$ArtifactsPath'" 49 | } 50 | 51 | Task "Build" $RunBuild { 52 | 53 | dotnet msbuild "/t:Restore;Build;Pack" "/p:CI=true" ` 54 | "/p:Configuration=$BuildConfiguration" ` 55 | "/p:PackageOutputPath=$(Join-Path $ArtifactsPath "nuget")" 56 | 57 | if ($LASTEXITCODE -ne 0) { throw "Build failed." } 58 | } 59 | 60 | Task "Tests" $RunTests { 61 | 62 | $testsFailed = $false 63 | Get-ChildItem -Filter *.csproj -Recurse | ForEach-Object { 64 | 65 | if (Select-Xml -Path $_.FullName -XPath "/Project/ItemGroup/PackageReference[@Include='Microsoft.NET.Test.Sdk']") { 66 | dotnet test $_.FullName -c $BuildConfiguration --no-build 67 | if ($LASTEXITCODE -ne 0) { $testsFailed = $true } 68 | } 69 | } 70 | 71 | if ($testsFailed) { throw "At least one test failed." } 72 | } 73 | 74 | Write-Host "`nBuild finished in $($Stopwatch.Elapsed.TotalSeconds) sec." -ForegroundColor Green 75 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "7.0.100", 4 | "rollForward": "feature" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /launch-sample.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding(PositionalBinding = $false)] 2 | param( 3 | [ValidateSet("net7.0", "net6.0", "netcoreapp3.1")] 4 | [string] $Framework = "net6.0" 5 | ) 6 | 7 | dotnet build 8 | if ($LASTEXITCODE -ne 0) { throw "build error" } 9 | 10 | Write-Host "Launching samples with framework $Framework" 11 | 12 | Start-Process ` 13 | -FilePath powershell.exe ` 14 | -ArgumentList @( "dotnet run -f $Framework --no-build; Read-Host 'Press enter to exit'" ) ` 15 | -WorkingDirectory "samples\$Framework\CustomersApi" 16 | 17 | Start-Sleep -Seconds 2 18 | 19 | Start-Process ` 20 | -FilePath powershell.exe ` 21 | -ArgumentList @( "dotnet run -f $Framework --no-build; Read-Host 'Press enter to exit'" ) ` 22 | -WorkingDirectory "samples\$Framework\OrdersApi" 23 | 24 | Start-Sleep -Seconds 5 25 | 26 | Start-Process ` 27 | -FilePath powershell.exe ` 28 | -ArgumentList @( "dotnet run -f $Framework --no-build; Read-Host 'Press enter to exit'" ) ` 29 | -WorkingDirectory "samples\$Framework\FrontendWeb" 30 | 31 | Start-Process ` 32 | -FilePath powershell.exe ` 33 | -ArgumentList @( "dotnet run -f $Framework --no-build; Read-Host 'Press enter to exit'" ) ` 34 | -WorkingDirectory "samples\$Framework\TrafficGenerator" 35 | -------------------------------------------------------------------------------- /package-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentracing-contrib/csharp-netcore/f64af79ae582cebf52dcdb54797d2545a2bb0565/package-icon.png -------------------------------------------------------------------------------- /samples/net6.0/CustomersApi/Controllers/CustomersController.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.Extensions.Logging; 4 | using Samples.CustomersApi.DataStore; 5 | 6 | namespace Samples.CustomersApi.Controllers 7 | { 8 | [Route("customers")] 9 | public class CustomersController : Controller 10 | { 11 | private readonly CustomerDbContext _dbContext; 12 | private readonly ILogger _logger; 13 | 14 | public CustomersController(CustomerDbContext dbContext, ILogger logger) 15 | { 16 | _dbContext = dbContext; 17 | _logger = logger; 18 | } 19 | 20 | [HttpGet] 21 | public IActionResult Index() 22 | { 23 | return Json(_dbContext.Customers.ToList()); 24 | } 25 | 26 | [HttpGet("{id:int}")] 27 | public IActionResult Index(int id) 28 | { 29 | var customer = _dbContext.Customers.FirstOrDefault(x => x.CustomerId == id); 30 | 31 | if (customer == null) 32 | return NotFound(); 33 | 34 | // ILogger events are sent to OpenTracing as well! 35 | _logger.LogInformation("Returning data for customer {CustomerId}", id); 36 | 37 | return Json(customer); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/net6.0/CustomersApi/CustomersApi.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /samples/net6.0/CustomersApi/DataStore/CustomerDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Shared; 3 | 4 | namespace Samples.CustomersApi.DataStore 5 | { 6 | public class CustomerDbContext : DbContext 7 | { 8 | public CustomerDbContext(DbContextOptions options) 9 | : base(options) 10 | { 11 | } 12 | 13 | public DbSet Customers { get; set; } 14 | 15 | public void Seed() 16 | { 17 | if (Database.EnsureCreated()) 18 | { 19 | Database.Migrate(); 20 | 21 | Customers.Add(new Customer(1, "Marcel Belding")); 22 | Customers.Add(new Customer(2, "Phyllis Schriver")); 23 | Customers.Add(new Customer(3, "Estefana Balderrama")); 24 | Customers.Add(new Customer(4, "Kenyetta Lone")); 25 | Customers.Add(new Customer(5, "Vernita Fernald")); 26 | Customers.Add(new Customer(6, "Tessie Storrs")); 27 | 28 | SaveChanges(); 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /samples/net6.0/CustomersApi/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Hosting; 4 | using Shared; 5 | 6 | namespace Samples.CustomersApi 7 | { 8 | public class Program 9 | { 10 | public static void Main(string[] args) 11 | { 12 | CreateHostBuilder(args).Build().Run(); 13 | } 14 | 15 | public static IHostBuilder CreateHostBuilder(string[] args) 16 | { 17 | return Host.CreateDefaultBuilder(args) 18 | .ConfigureWebHostDefaults(webBuilder => 19 | { 20 | webBuilder 21 | .UseStartup() 22 | .UseUrls(Constants.CustomersUrl); 23 | }) 24 | .ConfigureServices(services => 25 | { 26 | // Registers and starts Jaeger (see Shared.JaegerServiceCollectionExtensions) 27 | services.AddJaeger(); 28 | 29 | // Enables OpenTracing instrumentation for ASP.NET Core, CoreFx, EF Core 30 | services.AddOpenTracing(builder => 31 | { 32 | builder.ConfigureAspNetCore(options => 33 | { 34 | // We don't need any tracing data for our health endpoint. 35 | options.Hosting.IgnorePatterns.Add(ctx => ctx.Request.Path == "/health"); 36 | }); 37 | 38 | builder.ConfigureEntityFrameworkCore(options => 39 | { 40 | // This is an example for how certain EF Core commands can be ignored. 41 | // As en example, we're ignoring the "PRAGMA foreign_keys=ON;" commands that are executed by Sqlite. 42 | // Remove this code to see those statements. 43 | options.IgnorePatterns.Add(cmd => cmd.Command.CommandText.StartsWith("PRAGMA")); 44 | }); 45 | }); 46 | 47 | }); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /samples/net6.0/CustomersApi/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "CustomersApi": { 4 | "commandName": "Project", 5 | "launchBrowser": false, 6 | "launchUrl": "http://localhost:5001", 7 | "environmentVariables": { 8 | "ASPNETCORE_ENVIRONMENT": "Development" 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /samples/net6.0/CustomersApi/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Samples.CustomersApi.DataStore; 6 | 7 | namespace Samples.CustomersApi 8 | { 9 | public class Startup 10 | { 11 | public void ConfigureServices(IServiceCollection services) 12 | { 13 | // Adds a Sqlite DB to show EFCore traces. 14 | services 15 | .AddDbContext(options => 16 | { 17 | options.UseSqlite("Data Source=DataStore/customers.db"); 18 | }); 19 | 20 | services.AddMvc(); 21 | 22 | services.AddHealthChecks() 23 | .AddDbContextCheck(); 24 | } 25 | 26 | public void Configure(IApplicationBuilder app) 27 | { 28 | // Load some dummy data into the db. 29 | BootstrapDataStore(app.ApplicationServices); 30 | 31 | app.UseDeveloperExceptionPage(); 32 | 33 | app.UseRouting(); 34 | 35 | app.UseAuthentication(); 36 | app.UseAuthorization(); 37 | 38 | app.UseEndpoints(endpoints => 39 | { 40 | endpoints.MapControllers(); 41 | endpoints.MapHealthChecks("/health"); 42 | }); 43 | } 44 | 45 | private void BootstrapDataStore(IServiceProvider serviceProvider) 46 | { 47 | using (var scope = serviceProvider.CreateScope()) 48 | { 49 | var dbContext = scope.ServiceProvider.GetRequiredService(); 50 | dbContext.Seed(); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /samples/net6.0/CustomersApi/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /samples/net6.0/FrontendWeb/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net.Http; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Shared; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.AspNetCore.Mvc.Rendering; 10 | using Newtonsoft.Json; 11 | 12 | namespace Samples.FrontendWeb.Controllers 13 | { 14 | public class HomeController : Controller 15 | { 16 | private readonly HttpClient _httpClient; 17 | 18 | public HomeController(HttpClient httpClient) 19 | { 20 | _httpClient = httpClient; 21 | } 22 | 23 | [HttpGet] 24 | public IActionResult Index() 25 | { 26 | return View(); 27 | } 28 | 29 | [HttpGet] 30 | public async Task PlaceOrder() 31 | { 32 | ViewBag.Customers = await GetCustomers(); 33 | return View(new PlaceOrderCommand { ItemNumber = "ABC11", Quantity = 1 }); 34 | } 35 | 36 | [HttpPost, ValidateAntiForgeryToken] 37 | public async Task PlaceOrder(PlaceOrderCommand cmd) 38 | { 39 | if (!ModelState.IsValid) 40 | { 41 | ViewBag.Customers = await GetCustomers(); 42 | return View(cmd); 43 | } 44 | 45 | string body = JsonConvert.SerializeObject(cmd); 46 | 47 | var request = new HttpRequestMessage 48 | { 49 | Method = HttpMethod.Post, 50 | RequestUri = new Uri(Constants.OrdersUrl + "orders"), 51 | Content = new StringContent(body, Encoding.UTF8, "application/json") 52 | }; 53 | 54 | await _httpClient.SendAsync(request); 55 | 56 | return RedirectToAction("Index"); 57 | } 58 | 59 | private async Task> GetCustomers() 60 | { 61 | var request = new HttpRequestMessage 62 | { 63 | Method = HttpMethod.Get, 64 | RequestUri = new Uri(Constants.CustomersUrl + "customers") 65 | }; 66 | 67 | var response = await _httpClient.SendAsync(request); 68 | 69 | response.EnsureSuccessStatusCode(); 70 | 71 | var body = await response.Content.ReadAsStringAsync(); 72 | 73 | return JsonConvert.DeserializeObject>(body) 74 | .Select(x => new SelectListItem { Value = x.CustomerId.ToString(), Text = x.Name }); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /samples/net6.0/FrontendWeb/FrontendWeb.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /samples/net6.0/FrontendWeb/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Hosting; 4 | using Shared; 5 | 6 | namespace Samples.FrontendWeb 7 | { 8 | public class Program 9 | { 10 | public static void Main(string[] args) 11 | { 12 | CreateHostBuilder(args).Build().Run(); 13 | } 14 | 15 | public static IHostBuilder CreateHostBuilder(string[] args) 16 | { 17 | return Host.CreateDefaultBuilder(args) 18 | .ConfigureWebHostDefaults(webBuilder => 19 | { 20 | webBuilder 21 | .UseStartup() 22 | .UseUrls(Constants.FrontendUrl); 23 | }) 24 | .ConfigureServices(services => 25 | { 26 | // Registers and starts Jaeger (see Shared.JaegerServiceCollectionExtensions) 27 | services.AddJaeger(); 28 | 29 | // Enables OpenTracing instrumentation for ASP.NET Core, CoreFx, EF Core 30 | services.AddOpenTracing(); 31 | }); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /samples/net6.0/FrontendWeb/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "FrontendWeb": { 4 | "commandName": "Project", 5 | "launchBrowser": false, 6 | "launchUrl": "http://localhost:5000", 7 | "environmentVariables": { 8 | "ASPNETCORE_ENVIRONMENT": "Development" 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /samples/net6.0/FrontendWeb/Startup.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace Samples.FrontendWeb 6 | { 7 | public class Startup 8 | { 9 | public void ConfigureServices(IServiceCollection services) 10 | { 11 | services.AddSingleton(); 12 | 13 | services.AddMvc(); 14 | } 15 | 16 | public void Configure(IApplicationBuilder app) 17 | { 18 | app.UseDeveloperExceptionPage(); 19 | 20 | app.UseRouting(); 21 | 22 | app.UseAuthentication(); 23 | app.UseAuthorization(); 24 | 25 | app.UseEndpoints(endpoints => 26 | { 27 | endpoints.MapDefaultControllerRoute(); 28 | }); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /samples/net6.0/FrontendWeb/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | FrontendWeb 4 |

FrontendWeb

5 |

This is the beautiful web frontend.

6 |

Refresh this page a few times and check the Jaeger UI - you should already see traces!

7 | 8 |

Place an order

9 | -------------------------------------------------------------------------------- /samples/net6.0/FrontendWeb/Views/Home/PlaceOrder.cshtml: -------------------------------------------------------------------------------- 1 | @model Shared.PlaceOrderCommand 2 | 3 | 4 | FrontendWeb 5 |

FrontendWeb

6 |

Place an order

7 | 8 |
9 |
Customer: @Html.DropDownListFor(x => x.CustomerId, (IEnumerable)ViewBag.Customers)
10 |
ItemNumber: @Html.TextBoxFor(x => x.ItemNumber)
11 |
Quantity: @Html.TextBoxFor(x => x.Quantity)
12 | 13 |
14 | -------------------------------------------------------------------------------- /samples/net6.0/FrontendWeb/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Samples.FrontendWeb 2 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers -------------------------------------------------------------------------------- /samples/net6.0/FrontendWeb/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information", 8 | "OpenTracing": "Debug" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples/net6.0/OrdersApi/Controllers/OrdersController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net.Http; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.EntityFrameworkCore; 8 | using Newtonsoft.Json; 9 | using OpenTracing; 10 | using OrdersApi.DataStore; 11 | using Shared; 12 | 13 | namespace Samples.OrdersApi.Controllers 14 | { 15 | [Route("orders")] 16 | public class OrdersController : Controller 17 | { 18 | private readonly OrdersDbContext _dbContext; 19 | private readonly HttpClient _httpClient; 20 | private readonly ITracer _tracer; 21 | 22 | public OrdersController(OrdersDbContext dbContext, HttpClient httpClient, ITracer tracer) 23 | { 24 | _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext)); 25 | _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); 26 | _tracer = tracer ?? throw new ArgumentNullException(nameof(tracer)); 27 | } 28 | 29 | [HttpGet] 30 | public async Task Index() 31 | { 32 | var orders = await _dbContext.Orders.ToListAsync(); 33 | 34 | return Ok(orders.Select(x => new { x.OrderId }).ToList()); 35 | } 36 | 37 | [HttpPost] 38 | public async Task Index([FromBody] PlaceOrderCommand cmd) 39 | { 40 | var customer = await GetCustomer(cmd.CustomerId.Value); 41 | 42 | var order = new Order 43 | { 44 | CustomerId = cmd.CustomerId.Value, 45 | ItemNumber = cmd.ItemNumber, 46 | Quantity = cmd.Quantity 47 | }; 48 | 49 | _dbContext.Orders.Add(order); 50 | 51 | await _dbContext.SaveChangesAsync(); 52 | 53 | _tracer.ActiveSpan?.Log(new Dictionary { 54 | { "event", "OrderPlaced" }, 55 | { "orderId", order.OrderId }, 56 | { "customer", order.CustomerId }, 57 | { "customer_name", customer.Name }, 58 | { "item_number", order.ItemNumber }, 59 | { "quantity", order.Quantity } 60 | }); 61 | 62 | return Ok(); 63 | } 64 | 65 | private async Task GetCustomer(int customerId) 66 | { 67 | var request = new HttpRequestMessage 68 | { 69 | Method = HttpMethod.Get, 70 | RequestUri = new Uri(Constants.CustomersUrl + "customers/" + customerId) 71 | }; 72 | 73 | var response = await _httpClient.SendAsync(request); 74 | 75 | response.EnsureSuccessStatusCode(); 76 | 77 | var body = await response.Content.ReadAsStringAsync(); 78 | 79 | return JsonConvert.DeserializeObject(body); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /samples/net6.0/OrdersApi/DataStore/Order.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace OrdersApi.DataStore 5 | { 6 | public class Order 7 | { 8 | [Key] 9 | public int OrderId { get; set; } 10 | 11 | public int CustomerId { get; set; } 12 | 13 | [Required, StringLength(10)] 14 | public string ItemNumber { get; set; } 15 | 16 | [Required, Range(1, 100)] 17 | public int Quantity { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /samples/net6.0/OrdersApi/DataStore/OrdersDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | 3 | namespace OrdersApi.DataStore 4 | { 5 | public class OrdersDbContext : DbContext 6 | { 7 | public OrdersDbContext(DbContextOptions options) 8 | : base(options) 9 | { 10 | } 11 | 12 | public DbSet Orders { get; set; } 13 | 14 | public void Seed() 15 | { 16 | if (Database.EnsureCreated()) 17 | { 18 | Database.Migrate(); 19 | 20 | SaveChanges(); 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /samples/net6.0/OrdersApi/OrdersApi.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /samples/net6.0/OrdersApi/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Hosting; 4 | using Shared; 5 | 6 | namespace Samples.OrdersApi 7 | { 8 | public class Program 9 | { 10 | public static void Main(string[] args) 11 | { 12 | CreateHostBuilder(args).Build().Run(); 13 | } 14 | 15 | public static IHostBuilder CreateHostBuilder(string[] args) 16 | { 17 | return Host.CreateDefaultBuilder(args) 18 | .ConfigureWebHostDefaults(webBuilder => 19 | { 20 | webBuilder 21 | .UseStartup() 22 | .UseUrls(Constants.OrdersUrl); 23 | }) 24 | .ConfigureServices(services => 25 | { 26 | // Registers and starts Jaeger (see Shared.JaegerServiceCollectionExtensions) 27 | services.AddJaeger(); 28 | 29 | // Enables OpenTracing instrumentation for ASP.NET Core, CoreFx, EF Core 30 | services.AddOpenTracing(builder => 31 | { 32 | builder.ConfigureAspNetCore(options => 33 | { 34 | // We don't need any tracing data for our health endpoint. 35 | options.Hosting.IgnorePatterns.Add(ctx => ctx.Request.Path == "/health"); 36 | }); 37 | }); 38 | }); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /samples/net6.0/OrdersApi/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "OrdersApi": { 4 | "commandName": "Project", 5 | "launchBrowser": false, 6 | "launchUrl": "http://localhost:5002", 7 | "environmentVariables": { 8 | "ASPNETCORE_ENVIRONMENT": "Development" 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /samples/net6.0/OrdersApi/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using OrdersApi.DataStore; 7 | 8 | namespace Samples.OrdersApi 9 | { 10 | public class Startup 11 | { 12 | public void ConfigureServices(IServiceCollection services) 13 | { 14 | // Adds a SqlServer DB to show EFCore traces. 15 | services 16 | .AddDbContext(options => 17 | { 18 | options.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=Orders-net5;Trusted_Connection=True;MultipleActiveResultSets=true"); 19 | }); 20 | 21 | services.AddSingleton(); 22 | 23 | services.AddMvc(); 24 | 25 | services.AddHealthChecks() 26 | .AddDbContextCheck(); 27 | } 28 | 29 | public void Configure(IApplicationBuilder app) 30 | { 31 | // Load some dummy data into the db. 32 | BootstrapDataStore(app.ApplicationServices); 33 | 34 | app.UseDeveloperExceptionPage(); 35 | 36 | app.UseRouting(); 37 | 38 | app.UseAuthentication(); 39 | app.UseAuthorization(); 40 | 41 | app.UseEndpoints(endpoints => 42 | { 43 | endpoints.MapDefaultControllerRoute(); 44 | endpoints.MapHealthChecks("/health"); 45 | }); 46 | } 47 | 48 | private void BootstrapDataStore(IServiceProvider serviceProvider) 49 | { 50 | using (var scope = serviceProvider.CreateScope()) 51 | { 52 | var dbContext = scope.ServiceProvider.GetRequiredService(); 53 | dbContext.Seed(); 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /samples/net6.0/OrdersApi/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /samples/net6.0/Shared/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace Shared 2 | { 3 | public class Constants 4 | { 5 | public const string FrontendUrl = "http://localhost:5000/"; 6 | 7 | public const string CustomersUrl = "http://localhost:5001/"; 8 | 9 | public const string OrdersUrl = "http://localhost:5002/"; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples/net6.0/Shared/Customer.cs: -------------------------------------------------------------------------------- 1 | namespace Shared 2 | { 3 | public class Customer 4 | { 5 | public int CustomerId { get; set; } 6 | public string Name { get; set; } 7 | 8 | public Customer() 9 | { 10 | } 11 | 12 | public Customer(int customerId, string name) 13 | { 14 | CustomerId = customerId; 15 | Name = name; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /samples/net6.0/Shared/JaegerServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using Jaeger; 4 | using Jaeger.Reporters; 5 | using Jaeger.Samplers; 6 | using Jaeger.Senders.Thrift; 7 | using Microsoft.Extensions.Logging; 8 | using OpenTracing; 9 | using OpenTracing.Contrib.NetCore.Configuration; 10 | using OpenTracing.Util; 11 | 12 | namespace Microsoft.Extensions.DependencyInjection 13 | { 14 | public static class JaegerServiceCollectionExtensions 15 | { 16 | private static readonly Uri _jaegerUri = new Uri("http://localhost:14268/api/traces"); 17 | 18 | public static IServiceCollection AddJaeger(this IServiceCollection services) 19 | { 20 | if (services == null) 21 | throw new ArgumentNullException(nameof(services)); 22 | 23 | services.AddSingleton(serviceProvider => 24 | { 25 | string serviceName = Assembly.GetEntryAssembly().GetName().Name; 26 | 27 | ILoggerFactory loggerFactory = serviceProvider.GetRequiredService(); 28 | 29 | ISampler sampler = new ConstSampler(sample: true); 30 | 31 | IReporter reporter = new RemoteReporter.Builder() 32 | .WithSender(new HttpSender.Builder(_jaegerUri.ToString()).Build()) 33 | .Build(); 34 | 35 | ITracer tracer = new Tracer.Builder(serviceName) 36 | .WithLoggerFactory(loggerFactory) 37 | .WithSampler(sampler) 38 | .WithReporter(reporter) 39 | .Build(); 40 | 41 | GlobalTracer.Register(tracer); 42 | 43 | return tracer; 44 | }); 45 | 46 | // Prevent endless loops when OpenTracing is tracking HTTP requests to Jaeger. 47 | services.Configure(options => 48 | { 49 | options.IgnorePatterns.Add(request => _jaegerUri.IsBaseOf(request.RequestUri)); 50 | }); 51 | 52 | return services; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /samples/net6.0/Shared/PlaceOrderCommand.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Shared 4 | { 5 | public class PlaceOrderCommand 6 | { 7 | [Required] 8 | public int? CustomerId { get; set; } 9 | 10 | [Required, StringLength(10)] 11 | public string ItemNumber { get; set; } 12 | 13 | [Required, Range(1, 100)] 14 | public int Quantity { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/net6.0/Shared/Shared.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /samples/net6.0/TrafficGenerator/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace TrafficGenerator 5 | { 6 | class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IHostBuilder CreateHostBuilder(string[] args) => 14 | Host.CreateDefaultBuilder(args) 15 | .ConfigureServices(services => 16 | { 17 | // Registers and starts Jaeger (see Shared.JaegerServiceCollectionExtensions) 18 | services.AddJaeger(); 19 | 20 | services.AddOpenTracing(); 21 | 22 | services.AddHostedService(); 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /samples/net6.0/TrafficGenerator/TrafficGenerator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /samples/net6.0/TrafficGenerator/Worker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Microsoft.Extensions.Hosting; 6 | using Microsoft.Extensions.Logging; 7 | using Shared; 8 | 9 | namespace TrafficGenerator 10 | { 11 | public class Worker : BackgroundService 12 | { 13 | private readonly ILogger _logger; 14 | 15 | public Worker(ILogger logger) 16 | { 17 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 18 | } 19 | 20 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 21 | { 22 | try 23 | { 24 | HttpClient customersHttpClient = new HttpClient(); 25 | customersHttpClient.BaseAddress = new Uri(Constants.CustomersUrl); 26 | 27 | HttpClient ordersHttpClient = new HttpClient(); 28 | ordersHttpClient.BaseAddress = new Uri(Constants.OrdersUrl); 29 | 30 | 31 | while (!stoppingToken.IsCancellationRequested) 32 | { 33 | HttpResponseMessage ordershealthResponse = await ordersHttpClient.GetAsync("health"); 34 | _logger.LogInformation($"Health of 'orders'-endpoint: '{ordershealthResponse.StatusCode}'"); 35 | 36 | HttpResponseMessage customersHealthResponse = await customersHttpClient.GetAsync("health"); 37 | _logger.LogInformation($"Health of 'customers'-endpoint: '{customersHealthResponse.StatusCode}'"); 38 | 39 | _logger.LogInformation("Requesting customers"); 40 | 41 | HttpResponseMessage response = await customersHttpClient.GetAsync("customers"); 42 | 43 | _logger.LogInformation($"Response was '{response.StatusCode}'"); 44 | 45 | await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken); 46 | } 47 | } 48 | catch (TaskCanceledException) 49 | { 50 | /* Application should be stopped -> no-op */ 51 | } 52 | catch (Exception ex) 53 | { 54 | _logger.LogError(ex, "Unhandled exception"); 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /samples/net6.0/TrafficGenerator/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Warning", 7 | "Microsoft.AspNetCore.Hosting": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /samples/net7.0/CustomersApi/CustomersApi.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /samples/net7.0/CustomersApi/DataStore/CustomerDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Shared; 3 | 4 | namespace CustomersApi.DataStore; 5 | 6 | public class CustomerDbContext : DbContext 7 | { 8 | public CustomerDbContext(DbContextOptions options) 9 | : base(options) 10 | { 11 | } 12 | 13 | public DbSet Customers => Set(); 14 | 15 | public void Seed() 16 | { 17 | if (Database.EnsureCreated()) 18 | { 19 | Database.Migrate(); 20 | 21 | Customers.Add(new Customer(1, "Marcel Belding")); 22 | Customers.Add(new Customer(2, "Phyllis Schriver")); 23 | Customers.Add(new Customer(3, "Estefana Balderrama")); 24 | Customers.Add(new Customer(4, "Kenyetta Lone")); 25 | Customers.Add(new Customer(5, "Vernita Fernald")); 26 | Customers.Add(new Customer(6, "Tessie Storrs")); 27 | 28 | SaveChanges(); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /samples/net7.0/CustomersApi/Program.cs: -------------------------------------------------------------------------------- 1 | using CustomersApi.DataStore; 2 | using Microsoft.EntityFrameworkCore; 3 | using Shared; 4 | 5 | var builder = WebApplication.CreateBuilder(args); 6 | 7 | // Add services to the container. 8 | 9 | builder.WebHost.UseUrls(Constants.CustomersUrl); 10 | 11 | // Registers and starts Jaeger (see Shared.JaegerServiceCollectionExtensions) 12 | builder.Services.AddJaeger(); 13 | 14 | // Enables OpenTracing instrumentation for ASP.NET Core, CoreFx, EF Core 15 | builder.Services.AddOpenTracing(ot => 16 | { 17 | ot.ConfigureAspNetCore(options => 18 | { 19 | // We don't need any tracing data for our health endpoint. 20 | options.Hosting.IgnorePatterns.Add(ctx => ctx.Request.Path == "/health"); 21 | }); 22 | 23 | ot.ConfigureEntityFrameworkCore(options => 24 | { 25 | // This is an example for how certain EF Core commands can be ignored. 26 | // As en example, we're ignoring the "PRAGMA foreign_keys=ON;" commands that are executed by Sqlite. 27 | // Remove this code to see those statements. 28 | options.IgnorePatterns.Add(cmd => cmd.Command.CommandText.StartsWith("PRAGMA")); 29 | }); 30 | }); 31 | 32 | // Adds a Sqlite DB to show EFCore traces. 33 | builder.Services.AddDbContext(options => 34 | { 35 | options.UseSqlite("Data Source=DataStore/customers.db"); 36 | }); 37 | 38 | builder.Services.AddHealthChecks() 39 | .AddDbContextCheck(); 40 | 41 | 42 | var app = builder.Build(); 43 | 44 | 45 | // Load some dummy data into the db. 46 | using (var scope = app.Services.CreateScope()) 47 | { 48 | var dbContext = scope.ServiceProvider.GetRequiredService(); 49 | dbContext.Seed(); 50 | } 51 | 52 | 53 | // Configure the HTTP request pipeline. 54 | 55 | app.MapGet("/", () => "Customers API"); 56 | 57 | app.MapHealthChecks("/health"); 58 | 59 | app.MapGet("/customers", async (CustomerDbContext dbContext) => await dbContext.Customers.ToListAsync()); 60 | 61 | app.MapGet("/customers/{id}", async (int id, CustomerDbContext dbContext, ILogger logger) => 62 | { 63 | var customer = await dbContext.Customers.FirstOrDefaultAsync(x => x.CustomerId == id); 64 | 65 | if (customer == null) 66 | return Results.NotFound(); 67 | 68 | // ILogger events are sent to OpenTracing as well! 69 | logger.LogInformation("Returning data for customer {CustomerId}", id); 70 | 71 | return Results.Ok(customer); 72 | }); 73 | 74 | app.Run(); 75 | -------------------------------------------------------------------------------- /samples/net7.0/CustomersApi/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "CustomersApi": { 4 | "commandName": "Project", 5 | "launchBrowser": false, 6 | "launchUrl": "http://localhost:5001", 7 | "environmentVariables": { 8 | "ASPNETCORE_ENVIRONMENT": "Development" 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /samples/net7.0/CustomersApi/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /samples/net7.0/FrontendWeb/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.AspNetCore.Mvc.Rendering; 4 | using Newtonsoft.Json; 5 | using Shared; 6 | 7 | namespace FrontendWeb.Controllers; 8 | 9 | public class HomeController : Controller 10 | { 11 | private readonly HttpClient _httpClient; 12 | 13 | public HomeController(HttpClient httpClient) 14 | { 15 | _httpClient = httpClient; 16 | } 17 | 18 | [HttpGet] 19 | public IActionResult Index() 20 | { 21 | return View(); 22 | } 23 | 24 | [HttpGet] 25 | public async Task PlaceOrder() 26 | { 27 | ViewBag.Customers = await GetCustomers(); 28 | return View(new PlaceOrderCommand { ItemNumber = "ABC11", Quantity = 1 }); 29 | } 30 | 31 | [HttpPost, ValidateAntiForgeryToken] 32 | public async Task PlaceOrder(PlaceOrderCommand cmd) 33 | { 34 | if (!ModelState.IsValid) 35 | { 36 | ViewBag.Customers = await GetCustomers(); 37 | return View(cmd); 38 | } 39 | 40 | string body = JsonConvert.SerializeObject(cmd); 41 | 42 | var request = new HttpRequestMessage 43 | { 44 | Method = HttpMethod.Post, 45 | RequestUri = new Uri(Constants.OrdersUrl + "orders"), 46 | Content = new StringContent(body, Encoding.UTF8, "application/json") 47 | }; 48 | 49 | await _httpClient.SendAsync(request); 50 | 51 | return RedirectToAction("Index"); 52 | } 53 | 54 | private async Task> GetCustomers() 55 | { 56 | var request = new HttpRequestMessage 57 | { 58 | Method = HttpMethod.Get, 59 | RequestUri = new Uri(Constants.CustomersUrl + "customers") 60 | }; 61 | 62 | var response = await _httpClient.SendAsync(request); 63 | 64 | response.EnsureSuccessStatusCode(); 65 | 66 | var body = await response.Content.ReadAsStringAsync(); 67 | 68 | return JsonConvert.DeserializeObject>(body) 69 | .Select(x => new SelectListItem { Value = x.CustomerId.ToString(), Text = x.Name }); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /samples/net7.0/FrontendWeb/FrontendWeb.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /samples/net7.0/FrontendWeb/Program.cs: -------------------------------------------------------------------------------- 1 | using Shared; 2 | 3 | 4 | var builder = WebApplication.CreateBuilder(args); 5 | 6 | // Add services to the container. 7 | 8 | builder.WebHost.UseUrls(Constants.FrontendUrl); 9 | 10 | // Registers and starts Jaeger (see Shared.JaegerServiceCollectionExtensions) 11 | builder.Services.AddJaeger(); 12 | 13 | // Enables OpenTracing instrumentation for ASP.NET Core, CoreFx, EF Core 14 | builder.Services.AddOpenTracing(); 15 | 16 | builder.Services.AddSingleton(); 17 | 18 | builder.Services.AddMvc(); 19 | 20 | 21 | var app = builder.Build(); 22 | 23 | 24 | // Configure the HTTP request pipeline. 25 | 26 | app.MapDefaultControllerRoute(); 27 | 28 | app.Run(); 29 | -------------------------------------------------------------------------------- /samples/net7.0/FrontendWeb/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "FrontendWeb": { 4 | "commandName": "Project", 5 | "launchBrowser": false, 6 | "launchUrl": "http://localhost:5000", 7 | "environmentVariables": { 8 | "ASPNETCORE_ENVIRONMENT": "Development" 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /samples/net7.0/FrontendWeb/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | FrontendWeb 4 |

FrontendWeb

5 |

This is the beautiful web frontend.

6 |

Refresh this page a few times and check the Jaeger UI - you should already see traces!

7 | 8 |

Place an order

9 | -------------------------------------------------------------------------------- /samples/net7.0/FrontendWeb/Views/Home/PlaceOrder.cshtml: -------------------------------------------------------------------------------- 1 | @model Shared.PlaceOrderCommand 2 | 3 | 4 | FrontendWeb 5 |

FrontendWeb

6 |

Place an order

7 | 8 |
9 |
Customer: @Html.DropDownListFor(x => x.CustomerId, (IEnumerable)ViewBag.Customers)
10 |
ItemNumber: @Html.TextBoxFor(x => x.ItemNumber)
11 |
Quantity: @Html.TextBoxFor(x => x.Quantity)
12 | 13 |
14 | -------------------------------------------------------------------------------- /samples/net7.0/FrontendWeb/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using FrontendWeb 2 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 3 | -------------------------------------------------------------------------------- /samples/net7.0/FrontendWeb/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information", 8 | "OpenTracing": "Debug" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples/net7.0/OrdersApi/Controllers/OrdersController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.EntityFrameworkCore; 3 | using Newtonsoft.Json; 4 | using OpenTracing; 5 | using OrdersApi.DataStore; 6 | using Shared; 7 | 8 | namespace OrdersApi.Controllers; 9 | 10 | [Route("orders")] 11 | public class OrdersController : Controller 12 | { 13 | private readonly OrdersDbContext _dbContext; 14 | private readonly HttpClient _httpClient; 15 | private readonly ITracer _tracer; 16 | 17 | public OrdersController(OrdersDbContext dbContext, HttpClient httpClient, ITracer tracer) 18 | { 19 | _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext)); 20 | _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); 21 | _tracer = tracer ?? throw new ArgumentNullException(nameof(tracer)); 22 | } 23 | 24 | [HttpGet] 25 | public async Task Index() 26 | { 27 | var orders = await _dbContext.Orders.ToListAsync(); 28 | 29 | return Ok(orders.Select(x => new { x.OrderId }).ToList()); 30 | } 31 | 32 | [HttpPost] 33 | public async Task Index([FromBody] PlaceOrderCommand cmd) 34 | { 35 | var customer = await GetCustomer(cmd.CustomerId); 36 | 37 | var order = new Order 38 | { 39 | CustomerId = cmd.CustomerId, 40 | ItemNumber = cmd.ItemNumber, 41 | Quantity = cmd.Quantity 42 | }; 43 | 44 | _dbContext.Orders.Add(order); 45 | 46 | await _dbContext.SaveChangesAsync(); 47 | 48 | _tracer.ActiveSpan?.Log(new Dictionary { 49 | { "event", "OrderPlaced" }, 50 | { "orderId", order.OrderId }, 51 | { "customer", order.CustomerId }, 52 | { "customer_name", customer.Name }, 53 | { "item_number", order.ItemNumber }, 54 | { "quantity", order.Quantity } 55 | }); 56 | 57 | return Ok(); 58 | } 59 | 60 | private async Task GetCustomer(int customerId) 61 | { 62 | var request = new HttpRequestMessage 63 | { 64 | Method = HttpMethod.Get, 65 | RequestUri = new Uri(Constants.CustomersUrl + "customers/" + customerId) 66 | }; 67 | 68 | var response = await _httpClient.SendAsync(request); 69 | 70 | response.EnsureSuccessStatusCode(); 71 | 72 | var body = await response.Content.ReadAsStringAsync(); 73 | 74 | return JsonConvert.DeserializeObject(body); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /samples/net7.0/OrdersApi/DataStore/Order.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace OrdersApi.DataStore; 4 | 5 | public class Order 6 | { 7 | [Key] 8 | public int OrderId { get; set; } 9 | 10 | public int CustomerId { get; set; } 11 | 12 | [Required, StringLength(10)] 13 | public string ItemNumber { get; set; } = string.Empty; 14 | 15 | [Required, Range(1, 100)] 16 | public int Quantity { get; set; } 17 | } 18 | -------------------------------------------------------------------------------- /samples/net7.0/OrdersApi/DataStore/OrdersDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | 3 | namespace OrdersApi.DataStore; 4 | 5 | public class OrdersDbContext : DbContext 6 | { 7 | public OrdersDbContext(DbContextOptions options) 8 | : base(options) 9 | { 10 | } 11 | 12 | public DbSet Orders => Set(); 13 | 14 | public void Seed() 15 | { 16 | if (Database.EnsureCreated()) 17 | { 18 | Database.Migrate(); 19 | 20 | SaveChanges(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /samples/net7.0/OrdersApi/OrdersApi.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /samples/net7.0/OrdersApi/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using OrdersApi.DataStore; 3 | using Shared; 4 | 5 | var builder = WebApplication.CreateBuilder(args); 6 | 7 | // Add services to the container. 8 | 9 | builder.WebHost.UseUrls(Constants.OrdersUrl); 10 | 11 | // Registers and starts Jaeger (see Shared.JaegerServiceCollectionExtensions) 12 | builder.Services.AddJaeger(); 13 | 14 | // Enables OpenTracing instrumentation for ASP.NET Core, CoreFx, EF Core 15 | builder.Services.AddOpenTracing(builder => 16 | { 17 | builder.ConfigureAspNetCore(options => 18 | { 19 | // We don't need any tracing data for our health endpoint. 20 | options.Hosting.IgnorePatterns.Add(ctx => ctx.Request.Path == "/health"); 21 | }); 22 | }); 23 | 24 | // Adds a SqlServer DB to show EFCore traces. 25 | builder.Services.AddDbContext(options => 26 | { 27 | options.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=Orders-net5;Trusted_Connection=True;MultipleActiveResultSets=true"); 28 | }); 29 | 30 | builder.Services.AddSingleton(); 31 | 32 | builder.Services.AddMvc(); 33 | 34 | builder.Services.AddHealthChecks() 35 | .AddDbContextCheck(); 36 | 37 | 38 | var app = builder.Build(); 39 | 40 | 41 | // Load some dummy data into the db. 42 | using (var scope = app.Services.CreateScope()) 43 | { 44 | var dbContext = scope.ServiceProvider.GetRequiredService(); 45 | dbContext.Seed(); 46 | } 47 | 48 | 49 | // Configure the HTTP request pipeline. 50 | 51 | app.MapGet("/", () => "Orders API"); 52 | 53 | app.MapHealthChecks("/health"); 54 | 55 | app.MapDefaultControllerRoute(); 56 | 57 | app.Run(); 58 | -------------------------------------------------------------------------------- /samples/net7.0/OrdersApi/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "OrdersApi": { 4 | "commandName": "Project", 5 | "launchBrowser": false, 6 | "launchUrl": "http://localhost:5002", 7 | "environmentVariables": { 8 | "ASPNETCORE_ENVIRONMENT": "Development" 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /samples/net7.0/OrdersApi/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /samples/net7.0/Shared/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace Shared; 2 | 3 | public class Constants 4 | { 5 | public const string FrontendUrl = "http://localhost:5000/"; 6 | 7 | public const string CustomersUrl = "http://localhost:5001/"; 8 | 9 | public const string OrdersUrl = "http://localhost:5002/"; 10 | } 11 | -------------------------------------------------------------------------------- /samples/net7.0/Shared/Customer.cs: -------------------------------------------------------------------------------- 1 | namespace Shared; 2 | 3 | public class Customer 4 | { 5 | public int CustomerId { get; set; } 6 | public string Name { get; set; } 7 | 8 | public Customer() 9 | { 10 | CustomerId = 0; 11 | Name = string.Empty; 12 | } 13 | 14 | public Customer(int customerId, string name) 15 | { 16 | CustomerId = customerId; 17 | Name = name; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /samples/net7.0/Shared/JaegerServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using Jaeger; 3 | using Jaeger.Reporters; 4 | using Jaeger.Samplers; 5 | using Jaeger.Senders.Thrift; 6 | using Microsoft.Extensions.Logging; 7 | using OpenTracing; 8 | using OpenTracing.Contrib.NetCore.Configuration; 9 | using OpenTracing.Util; 10 | 11 | namespace Microsoft.Extensions.DependencyInjection; 12 | 13 | public static class JaegerServiceCollectionExtensions 14 | { 15 | private static readonly Uri _jaegerUri = new Uri("http://localhost:14268/api/traces"); 16 | 17 | public static IServiceCollection AddJaeger(this IServiceCollection services) 18 | { 19 | if (services == null) 20 | throw new ArgumentNullException(nameof(services)); 21 | 22 | services.AddSingleton(serviceProvider => 23 | { 24 | string serviceName = Assembly.GetEntryAssembly()?.GetName().Name ?? "unknown-service"; 25 | 26 | ILoggerFactory loggerFactory = serviceProvider.GetRequiredService(); 27 | 28 | ISampler sampler = new ConstSampler(sample: true); 29 | 30 | IReporter reporter = new RemoteReporter.Builder() 31 | .WithSender(new HttpSender.Builder(_jaegerUri.ToString()).Build()) 32 | .Build(); 33 | 34 | ITracer tracer = new Tracer.Builder(serviceName) 35 | .WithLoggerFactory(loggerFactory) 36 | .WithSampler(sampler) 37 | .WithReporter(reporter) 38 | .Build(); 39 | 40 | GlobalTracer.Register(tracer); 41 | 42 | return tracer; 43 | }); 44 | 45 | // Prevent endless loops when OpenTracing is tracking HTTP requests to Jaeger. 46 | services.Configure(options => 47 | { 48 | options.IgnorePatterns.Add(request => request.RequestUri != null && _jaegerUri.IsBaseOf(request.RequestUri)); 49 | }); 50 | 51 | return services; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /samples/net7.0/Shared/PlaceOrderCommand.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Shared; 4 | 5 | public class PlaceOrderCommand 6 | { 7 | [Required, Range(1, int.MaxValue)] 8 | public int CustomerId { get; set; } 9 | 10 | [Required, StringLength(10)] 11 | public string ItemNumber { get; set; } = string.Empty; 12 | 13 | [Required, Range(1, 100)] 14 | public int Quantity { get; set; } 15 | } 16 | -------------------------------------------------------------------------------- /samples/net7.0/Shared/Shared.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /samples/net7.0/TrafficGenerator/Program.cs: -------------------------------------------------------------------------------- 1 | using TrafficGenerator; 2 | 3 | using var host = Host.CreateDefaultBuilder(args) 4 | .ConfigureServices((hostContext, services) => 5 | { 6 | // Registers and starts Jaeger (see Shared.JaegerServiceCollectionExtensions) 7 | services.AddJaeger(); 8 | 9 | services.AddOpenTracing(); 10 | 11 | services.AddHostedService(); 12 | }) 13 | .Build(); 14 | 15 | await host.RunAsync(); 16 | -------------------------------------------------------------------------------- /samples/net7.0/TrafficGenerator/TrafficGenerator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /samples/net7.0/TrafficGenerator/Worker.cs: -------------------------------------------------------------------------------- 1 | using Shared; 2 | 3 | namespace TrafficGenerator; 4 | 5 | public class Worker : BackgroundService 6 | { 7 | private readonly ILogger _logger; 8 | 9 | public Worker(ILogger logger) 10 | { 11 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 12 | } 13 | 14 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 15 | { 16 | try 17 | { 18 | HttpClient customersHttpClient = new HttpClient(); 19 | customersHttpClient.BaseAddress = new Uri(Constants.CustomersUrl); 20 | 21 | HttpClient ordersHttpClient = new HttpClient(); 22 | ordersHttpClient.BaseAddress = new Uri(Constants.OrdersUrl); 23 | 24 | 25 | while (!stoppingToken.IsCancellationRequested) 26 | { 27 | HttpResponseMessage ordersHealthResponse = await ordersHttpClient.GetAsync("health"); 28 | _logger.LogInformation($"Health of 'orders'-endpoint: '{ordersHealthResponse.StatusCode}'"); 29 | 30 | HttpResponseMessage customersHealthResponse = await customersHttpClient.GetAsync("health"); 31 | _logger.LogInformation($"Health of 'customers'-endpoint: '{customersHealthResponse.StatusCode}'"); 32 | 33 | _logger.LogInformation("Requesting customers"); 34 | 35 | HttpResponseMessage response = await customersHttpClient.GetAsync("customers"); 36 | 37 | _logger.LogInformation($"Response was '{response.StatusCode}'"); 38 | 39 | await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken); 40 | } 41 | } 42 | catch (TaskCanceledException) 43 | { 44 | /* Application should be stopped -> no-op */ 45 | } 46 | catch (Exception ex) 47 | { 48 | _logger.LogError(ex, "Unhandled exception"); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /samples/net7.0/TrafficGenerator/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Warning", 7 | "Microsoft.AspNetCore.Hosting": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /samples/netcoreapp3.1/CustomersApi/Controllers/CustomersController.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.Extensions.Logging; 4 | using Samples.CustomersApi.DataStore; 5 | 6 | namespace Samples.CustomersApi.Controllers 7 | { 8 | [Route("customers")] 9 | public class CustomersController : Controller 10 | { 11 | private readonly CustomerDbContext _dbContext; 12 | private readonly ILogger _logger; 13 | 14 | public CustomersController(CustomerDbContext dbContext, ILogger logger) 15 | { 16 | _dbContext = dbContext; 17 | _logger = logger; 18 | } 19 | 20 | [HttpGet] 21 | public IActionResult Index() 22 | { 23 | return Json(_dbContext.Customers.ToList()); 24 | } 25 | 26 | [HttpGet("{id:int}")] 27 | public IActionResult Index(int id) 28 | { 29 | var customer = _dbContext.Customers.FirstOrDefault(x => x.CustomerId == id); 30 | 31 | if (customer == null) 32 | return NotFound(); 33 | 34 | // ILogger events are sent to OpenTracing as well! 35 | _logger.LogInformation("Returning data for customer {CustomerId}", id); 36 | 37 | return Json(customer); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/netcoreapp3.1/CustomersApi/CustomersApi.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /samples/netcoreapp3.1/CustomersApi/DataStore/CustomerDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Shared; 3 | 4 | namespace Samples.CustomersApi.DataStore 5 | { 6 | public class CustomerDbContext : DbContext 7 | { 8 | public CustomerDbContext(DbContextOptions options) 9 | : base(options) 10 | { 11 | } 12 | 13 | public DbSet Customers { get; set; } 14 | 15 | public void Seed() 16 | { 17 | if (Database.EnsureCreated()) 18 | { 19 | Database.Migrate(); 20 | 21 | Customers.Add(new Customer(1, "Marcel Belding")); 22 | Customers.Add(new Customer(2, "Phyllis Schriver")); 23 | Customers.Add(new Customer(3, "Estefana Balderrama")); 24 | Customers.Add(new Customer(4, "Kenyetta Lone")); 25 | Customers.Add(new Customer(5, "Vernita Fernald")); 26 | Customers.Add(new Customer(6, "Tessie Storrs")); 27 | 28 | SaveChanges(); 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /samples/netcoreapp3.1/CustomersApi/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Hosting; 4 | using Shared; 5 | 6 | namespace Samples.CustomersApi 7 | { 8 | public class Program 9 | { 10 | public static void Main(string[] args) 11 | { 12 | CreateHostBuilder(args).Build().Run(); 13 | } 14 | 15 | public static IHostBuilder CreateHostBuilder(string[] args) 16 | { 17 | return Host.CreateDefaultBuilder(args) 18 | .ConfigureWebHostDefaults(webBuilder => 19 | { 20 | webBuilder 21 | .UseStartup() 22 | .UseUrls(Constants.CustomersUrl); 23 | }) 24 | .ConfigureServices(services => 25 | { 26 | // Registers and starts Jaeger (see Shared.JaegerServiceCollectionExtensions) 27 | services.AddJaeger(); 28 | 29 | // Enables OpenTracing instrumentation for ASP.NET Core, CoreFx, EF Core 30 | services.AddOpenTracing(builder => 31 | { 32 | builder.ConfigureAspNetCore(options => 33 | { 34 | // We don't need any tracing data for our health endpoint. 35 | options.Hosting.IgnorePatterns.Add(ctx => ctx.Request.Path == "/health"); 36 | }); 37 | 38 | builder.ConfigureEntityFrameworkCore(options => 39 | { 40 | // This is an example for how certain EF Core commands can be ignored. 41 | // As en example, we're ignoring the "PRAGMA foreign_keys=ON;" commands that are executed by Sqlite. 42 | // Remove this code to see those statements. 43 | options.IgnorePatterns.Add(cmd => cmd.Command.CommandText.StartsWith("PRAGMA")); 44 | }); 45 | }); 46 | 47 | }); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /samples/netcoreapp3.1/CustomersApi/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "CustomersApi": { 4 | "commandName": "Project", 5 | "launchBrowser": false, 6 | "launchUrl": "http://localhost:5001", 7 | "environmentVariables": { 8 | "ASPNETCORE_ENVIRONMENT": "Development" 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /samples/netcoreapp3.1/CustomersApi/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Samples.CustomersApi.DataStore; 6 | 7 | namespace Samples.CustomersApi 8 | { 9 | public class Startup 10 | { 11 | public void ConfigureServices(IServiceCollection services) 12 | { 13 | // Adds a Sqlite DB to show EFCore traces. 14 | services 15 | .AddDbContext(options => 16 | { 17 | options.UseSqlite("Data Source=DataStore/customers.db"); 18 | }); 19 | 20 | services.AddMvc(); 21 | 22 | services.AddHealthChecks() 23 | .AddDbContextCheck(); 24 | } 25 | 26 | public void Configure(IApplicationBuilder app) 27 | { 28 | // Load some dummy data into the InMemory db. 29 | BootstrapDataStore(app.ApplicationServices); 30 | 31 | app.UseDeveloperExceptionPage(); 32 | 33 | app.UseRouting(); 34 | 35 | app.UseAuthentication(); 36 | app.UseAuthorization(); 37 | 38 | app.UseEndpoints(endpoints => 39 | { 40 | endpoints.MapControllers(); 41 | endpoints.MapHealthChecks("/health"); 42 | }); 43 | } 44 | 45 | public void BootstrapDataStore(IServiceProvider serviceProvider) 46 | { 47 | using (var scope = serviceProvider.CreateScope()) 48 | { 49 | var dbContext = scope.ServiceProvider.GetRequiredService(); 50 | dbContext.Seed(); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /samples/netcoreapp3.1/CustomersApi/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /samples/netcoreapp3.1/FrontendWeb/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net.Http; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Shared; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.AspNetCore.Mvc.Rendering; 10 | using Newtonsoft.Json; 11 | 12 | namespace Samples.FrontendWeb.Controllers 13 | { 14 | public class HomeController : Controller 15 | { 16 | private readonly HttpClient _httpClient; 17 | 18 | public HomeController(HttpClient httpClient) 19 | { 20 | _httpClient = httpClient; 21 | } 22 | 23 | [HttpGet] 24 | public IActionResult Index() 25 | { 26 | return View(); 27 | } 28 | 29 | [HttpGet] 30 | public async Task PlaceOrder() 31 | { 32 | ViewBag.Customers = await GetCustomers(); 33 | return View(new PlaceOrderCommand { ItemNumber = "ABC11", Quantity = 1 }); 34 | } 35 | 36 | [HttpPost, ValidateAntiForgeryToken] 37 | public async Task PlaceOrder(PlaceOrderCommand cmd) 38 | { 39 | if (!ModelState.IsValid) 40 | { 41 | ViewBag.Customers = await GetCustomers(); 42 | return View(cmd); 43 | } 44 | 45 | string body = JsonConvert.SerializeObject(cmd); 46 | 47 | var request = new HttpRequestMessage 48 | { 49 | Method = HttpMethod.Post, 50 | RequestUri = new Uri(Constants.OrdersUrl + "orders"), 51 | Content = new StringContent(body, Encoding.UTF8, "application/json") 52 | }; 53 | 54 | await _httpClient.SendAsync(request); 55 | 56 | return RedirectToAction("Index"); 57 | } 58 | 59 | private async Task> GetCustomers() 60 | { 61 | var request = new HttpRequestMessage 62 | { 63 | Method = HttpMethod.Get, 64 | RequestUri = new Uri(Constants.CustomersUrl + "customers") 65 | }; 66 | 67 | var response = await _httpClient.SendAsync(request); 68 | 69 | response.EnsureSuccessStatusCode(); 70 | 71 | var body = await response.Content.ReadAsStringAsync(); 72 | 73 | return JsonConvert.DeserializeObject>(body) 74 | .Select(x => new SelectListItem { Value = x.CustomerId.ToString(), Text = x.Name }); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /samples/netcoreapp3.1/FrontendWeb/FrontendWeb.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /samples/netcoreapp3.1/FrontendWeb/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Hosting; 4 | using Shared; 5 | 6 | namespace Samples.FrontendWeb 7 | { 8 | public class Program 9 | { 10 | public static void Main(string[] args) 11 | { 12 | CreateHostBuilder(args).Build().Run(); 13 | } 14 | 15 | public static IHostBuilder CreateHostBuilder(string[] args) 16 | { 17 | return Host.CreateDefaultBuilder(args) 18 | .ConfigureWebHostDefaults(webBuilder => 19 | { 20 | webBuilder 21 | .UseStartup() 22 | .UseUrls(Constants.FrontendUrl); 23 | }) 24 | .ConfigureServices(services => 25 | { 26 | // Registers and starts Jaeger (see Shared.JaegerServiceCollectionExtensions) 27 | services.AddJaeger(); 28 | 29 | // Enables OpenTracing instrumentation for ASP.NET Core, CoreFx, EF Core 30 | services.AddOpenTracing(); 31 | }); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /samples/netcoreapp3.1/FrontendWeb/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "FrontendWeb": { 4 | "commandName": "Project", 5 | "launchBrowser": false, 6 | "launchUrl": "http://localhost:5000", 7 | "environmentVariables": { 8 | "ASPNETCORE_ENVIRONMENT": "Development" 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /samples/netcoreapp3.1/FrontendWeb/Startup.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace Samples.FrontendWeb 6 | { 7 | public class Startup 8 | { 9 | public void ConfigureServices(IServiceCollection services) 10 | { 11 | services.AddSingleton(); 12 | 13 | services.AddMvc(); 14 | } 15 | 16 | public void Configure(IApplicationBuilder app) 17 | { 18 | app.UseDeveloperExceptionPage(); 19 | 20 | app.UseRouting(); 21 | 22 | app.UseAuthentication(); 23 | app.UseAuthorization(); 24 | 25 | app.UseEndpoints(endpoints => 26 | { 27 | endpoints.MapDefaultControllerRoute(); 28 | }); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /samples/netcoreapp3.1/FrontendWeb/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | FrontendWeb 4 |

FrontendWeb

5 |

This is the beautiful web frontend.

6 |

Refresh this page a few times and check the Jaeger UI - you should already see traces!

7 | 8 |

Place an order

9 | -------------------------------------------------------------------------------- /samples/netcoreapp3.1/FrontendWeb/Views/Home/PlaceOrder.cshtml: -------------------------------------------------------------------------------- 1 | @model Shared.PlaceOrderCommand 2 | 3 | 4 | FrontendWeb 5 |

FrontendWeb

6 |

Place an order

7 | 8 |
9 |
Customer: @Html.DropDownListFor(x => x.CustomerId, (IEnumerable)ViewBag.Customers)
10 |
ItemNumber: @Html.TextBoxFor(x => x.ItemNumber)
11 |
Quantity: @Html.TextBoxFor(x => x.Quantity)
12 | 13 |
14 | -------------------------------------------------------------------------------- /samples/netcoreapp3.1/FrontendWeb/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Samples.FrontendWeb 2 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers -------------------------------------------------------------------------------- /samples/netcoreapp3.1/FrontendWeb/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information", 8 | "OpenTracing": "Debug" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples/netcoreapp3.1/OrdersApi/Controllers/OrdersController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net.Http; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.EntityFrameworkCore; 8 | using Newtonsoft.Json; 9 | using OpenTracing; 10 | using OrdersApi.DataStore; 11 | using Shared; 12 | 13 | namespace Samples.OrdersApi.Controllers 14 | { 15 | [Route("orders")] 16 | public class OrdersController : Controller 17 | { 18 | private readonly OrdersDbContext _dbContext; 19 | private readonly HttpClient _httpClient; 20 | private readonly ITracer _tracer; 21 | 22 | public OrdersController(OrdersDbContext dbContext, HttpClient httpClient, ITracer tracer) 23 | { 24 | _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext)); 25 | _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); 26 | _tracer = tracer ?? throw new ArgumentNullException(nameof(tracer)); 27 | } 28 | 29 | [HttpGet] 30 | public async Task Index() 31 | { 32 | var orders = await _dbContext.Orders.ToListAsync(); 33 | 34 | return Ok(orders.Select(x => new { x.OrderId }).ToList()); 35 | } 36 | 37 | [HttpPost] 38 | public async Task Index([FromBody] PlaceOrderCommand cmd) 39 | { 40 | var customer = await GetCustomer(cmd.CustomerId.Value); 41 | 42 | var order = new Order 43 | { 44 | CustomerId = cmd.CustomerId.Value, 45 | ItemNumber = cmd.ItemNumber, 46 | Quantity = cmd.Quantity 47 | }; 48 | 49 | _dbContext.Orders.Add(order); 50 | 51 | await _dbContext.SaveChangesAsync(); 52 | 53 | _tracer.ActiveSpan?.Log(new Dictionary { 54 | { "event", "OrderPlaced" }, 55 | { "orderId", order.OrderId }, 56 | { "customer", order.CustomerId }, 57 | { "customer_name", customer.Name }, 58 | { "item_number", order.ItemNumber }, 59 | { "quantity", order.Quantity } 60 | }); 61 | 62 | return Ok(); 63 | } 64 | 65 | private async Task GetCustomer(int customerId) 66 | { 67 | var request = new HttpRequestMessage 68 | { 69 | Method = HttpMethod.Get, 70 | RequestUri = new Uri(Constants.CustomersUrl + "customers/" + customerId) 71 | }; 72 | 73 | var response = await _httpClient.SendAsync(request); 74 | 75 | response.EnsureSuccessStatusCode(); 76 | 77 | var body = await response.Content.ReadAsStringAsync(); 78 | 79 | return JsonConvert.DeserializeObject(body); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /samples/netcoreapp3.1/OrdersApi/DataStore/Order.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace OrdersApi.DataStore 5 | { 6 | public class Order 7 | { 8 | [Key] 9 | public int OrderId { get; set; } 10 | 11 | public int CustomerId { get; set; } 12 | 13 | [Required, StringLength(10)] 14 | public string ItemNumber { get; set; } 15 | 16 | [Required, Range(1, 100)] 17 | public int Quantity { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /samples/netcoreapp3.1/OrdersApi/DataStore/OrdersDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | 3 | namespace OrdersApi.DataStore 4 | { 5 | public class OrdersDbContext : DbContext 6 | { 7 | public OrdersDbContext(DbContextOptions options) 8 | : base(options) 9 | { 10 | } 11 | 12 | public DbSet Orders { get; set; } 13 | 14 | public void Seed() 15 | { 16 | if (Database.EnsureCreated()) 17 | { 18 | Database.Migrate(); 19 | 20 | SaveChanges(); 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /samples/netcoreapp3.1/OrdersApi/OrdersApi.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /samples/netcoreapp3.1/OrdersApi/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Hosting; 4 | using Shared; 5 | 6 | namespace Samples.OrdersApi 7 | { 8 | public class Program 9 | { 10 | public static void Main(string[] args) 11 | { 12 | CreateHostBuilder(args).Build().Run(); 13 | } 14 | 15 | public static IHostBuilder CreateHostBuilder(string[] args) 16 | { 17 | return Host.CreateDefaultBuilder(args) 18 | .ConfigureWebHostDefaults(webBuilder => 19 | { 20 | webBuilder 21 | .UseStartup() 22 | .UseUrls(Constants.OrdersUrl); 23 | }) 24 | .ConfigureServices(services => 25 | { 26 | // Registers and starts Jaeger (see Shared.JaegerServiceCollectionExtensions) 27 | services.AddJaeger(); 28 | 29 | // Enables OpenTracing instrumentation for ASP.NET Core, CoreFx, EF Core 30 | services.AddOpenTracing(builder => 31 | { 32 | builder.ConfigureAspNetCore(options => 33 | { 34 | // We don't need any tracing data for our health endpoint. 35 | options.Hosting.IgnorePatterns.Add(ctx => ctx.Request.Path == "/health"); 36 | }); 37 | }); 38 | }); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /samples/netcoreapp3.1/OrdersApi/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "OrdersApi": { 4 | "commandName": "Project", 5 | "launchBrowser": false, 6 | "launchUrl": "http://localhost:5002", 7 | "environmentVariables": { 8 | "ASPNETCORE_ENVIRONMENT": "Development" 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /samples/netcoreapp3.1/OrdersApi/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using OrdersApi.DataStore; 7 | 8 | namespace Samples.OrdersApi 9 | { 10 | public class Startup 11 | { 12 | public void ConfigureServices(IServiceCollection services) 13 | { 14 | // Adds a SqlServer DB to show EFCore traces. 15 | services 16 | .AddDbContext(options => 17 | { 18 | options.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=Orders-netcoreapp31;Trusted_Connection=True;MultipleActiveResultSets=true"); 19 | }); 20 | 21 | services.AddSingleton(); 22 | 23 | services.AddMvc(); 24 | 25 | services.AddHealthChecks() 26 | .AddDbContextCheck(); 27 | } 28 | 29 | public void Configure(IApplicationBuilder app) 30 | { 31 | // Load some dummy data into the db. 32 | BootstrapDataStore(app.ApplicationServices); 33 | 34 | app.UseDeveloperExceptionPage(); 35 | 36 | app.UseRouting(); 37 | 38 | app.UseAuthentication(); 39 | app.UseAuthorization(); 40 | 41 | app.UseEndpoints(endpoints => 42 | { 43 | endpoints.MapDefaultControllerRoute(); 44 | endpoints.MapHealthChecks("/health"); 45 | }); 46 | } 47 | 48 | private void BootstrapDataStore(IServiceProvider serviceProvider) 49 | { 50 | using (var scope = serviceProvider.CreateScope()) 51 | { 52 | var dbContext = scope.ServiceProvider.GetRequiredService(); 53 | dbContext.Seed(); 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /samples/netcoreapp3.1/OrdersApi/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /samples/netcoreapp3.1/Shared/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace Shared 2 | { 3 | public class Constants 4 | { 5 | public const string FrontendUrl = "http://localhost:5000/"; 6 | 7 | public const string CustomersUrl = "http://localhost:5001/"; 8 | 9 | public const string OrdersUrl = "http://localhost:5002/"; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples/netcoreapp3.1/Shared/Customer.cs: -------------------------------------------------------------------------------- 1 | namespace Shared 2 | { 3 | public class Customer 4 | { 5 | public int CustomerId { get; set; } 6 | public string Name { get; set; } 7 | 8 | public Customer() 9 | { 10 | } 11 | 12 | public Customer(int customerId, string name) 13 | { 14 | CustomerId = customerId; 15 | Name = name; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /samples/netcoreapp3.1/Shared/JaegerServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using Jaeger; 4 | using Jaeger.Reporters; 5 | using Jaeger.Samplers; 6 | using Jaeger.Senders.Thrift; 7 | using Microsoft.Extensions.Logging; 8 | using OpenTracing; 9 | using OpenTracing.Contrib.NetCore.Configuration; 10 | using OpenTracing.Util; 11 | 12 | namespace Microsoft.Extensions.DependencyInjection 13 | { 14 | public static class JaegerServiceCollectionExtensions 15 | { 16 | private static readonly Uri _jaegerUri = new Uri("http://localhost:14268/api/traces"); 17 | 18 | public static IServiceCollection AddJaeger(this IServiceCollection services) 19 | { 20 | if (services == null) 21 | throw new ArgumentNullException(nameof(services)); 22 | 23 | services.AddSingleton(serviceProvider => 24 | { 25 | string serviceName = Assembly.GetEntryAssembly().GetName().Name; 26 | 27 | ILoggerFactory loggerFactory = serviceProvider.GetRequiredService(); 28 | 29 | ISampler sampler = new ConstSampler(sample: true); 30 | 31 | IReporter reporter = new RemoteReporter.Builder() 32 | .WithSender(new HttpSender.Builder(_jaegerUri.ToString()).Build()) 33 | .Build(); 34 | 35 | ITracer tracer = new Tracer.Builder(serviceName) 36 | .WithLoggerFactory(loggerFactory) 37 | .WithSampler(sampler) 38 | .WithReporter(reporter) 39 | .Build(); 40 | 41 | GlobalTracer.Register(tracer); 42 | 43 | return tracer; 44 | }); 45 | 46 | // Prevent endless loops when OpenTracing is tracking HTTP requests to Jaeger. 47 | services.Configure(options => 48 | { 49 | options.IgnorePatterns.Add(request => _jaegerUri.IsBaseOf(request.RequestUri)); 50 | }); 51 | 52 | return services; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /samples/netcoreapp3.1/Shared/PlaceOrderCommand.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Shared 4 | { 5 | public class PlaceOrderCommand 6 | { 7 | [Required] 8 | public int? CustomerId { get; set; } 9 | 10 | [Required, StringLength(10)] 11 | public string ItemNumber { get; set; } 12 | 13 | [Required, Range(1, 100)] 14 | public int Quantity { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/netcoreapp3.1/Shared/Shared.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /samples/netcoreapp3.1/TrafficGenerator/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace TrafficGenerator 5 | { 6 | class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IHostBuilder CreateHostBuilder(string[] args) => 14 | Host.CreateDefaultBuilder(args) 15 | .ConfigureServices(services => 16 | { 17 | // Registers and starts Jaeger (see Shared.JaegerServiceCollectionExtensions) 18 | services.AddJaeger(); 19 | 20 | services.AddOpenTracing(); 21 | 22 | services.AddHostedService(); 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /samples/netcoreapp3.1/TrafficGenerator/TrafficGenerator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /samples/netcoreapp3.1/TrafficGenerator/Worker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Microsoft.Extensions.Hosting; 6 | using Microsoft.Extensions.Logging; 7 | using Shared; 8 | 9 | namespace TrafficGenerator 10 | { 11 | public class Worker : BackgroundService 12 | { 13 | private readonly ILogger _logger; 14 | 15 | public Worker(ILogger logger) 16 | { 17 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 18 | } 19 | 20 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 21 | { 22 | try 23 | { 24 | HttpClient customersHttpClient = new HttpClient(); 25 | customersHttpClient.BaseAddress = new Uri(Constants.CustomersUrl); 26 | 27 | HttpClient ordersHttpClient = new HttpClient(); 28 | ordersHttpClient.BaseAddress = new Uri(Constants.OrdersUrl); 29 | 30 | 31 | while (!stoppingToken.IsCancellationRequested) 32 | { 33 | HttpResponseMessage ordershealthResponse = await ordersHttpClient.GetAsync("health"); 34 | _logger.LogInformation($"Health of 'orders'-endpoint: '{ordershealthResponse.StatusCode}'"); 35 | 36 | HttpResponseMessage customersHealthResponse = await customersHttpClient.GetAsync("health"); 37 | _logger.LogInformation($"Health of 'customers'-endpoint: '{customersHealthResponse.StatusCode}'"); 38 | 39 | _logger.LogInformation("Requesting customers"); 40 | 41 | HttpResponseMessage response = await customersHttpClient.GetAsync("customers"); 42 | 43 | _logger.LogInformation($"Response was '{response.StatusCode}'"); 44 | 45 | await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken); 46 | } 47 | } 48 | catch (TaskCanceledException) 49 | { 50 | /* Application should be stopped -> no-op */ 51 | } 52 | catch (Exception ex) 53 | { 54 | _logger.LogError(ex, "Unhandled exception"); 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /samples/netcoreapp3.1/TrafficGenerator/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Warning", 7 | "Microsoft.AspNetCore.Hosting": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | true 6 | Christian Weiss 7 | package-icon.png 8 | https://avatars0.githubusercontent.com/u/15482765 9 | https://github.com/opentracing-contrib/csharp-netcore 10 | Apache-2.0 11 | https://github.com/opentracing-contrib/csharp-netcore/releases/tag/v$(Version) 12 | 13 | $(NoWarn);CS1591 14 | true 15 | 16 | 17 | true 18 | true 19 | snupkg 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.NetCore/AspNetCore/AspNetCoreDiagnosticOptions.cs: -------------------------------------------------------------------------------- 1 | using OpenTracing.Contrib.NetCore.AspNetCore; 2 | 3 | namespace OpenTracing.Contrib.NetCore.Configuration 4 | { 5 | public class AspNetCoreDiagnosticOptions : DiagnosticOptions 6 | { 7 | public HostingOptions Hosting { get; } = new HostingOptions(); 8 | 9 | public AspNetCoreDiagnosticOptions() 10 | { 11 | // We create separate spans for MVC actions & results so we don't need these additional events by default. 12 | IgnoredEvents.Add("Microsoft.AspNetCore.Mvc.BeforeOnResourceExecuting"); 13 | IgnoredEvents.Add("Microsoft.AspNetCore.Mvc.BeforeOnActionExecution"); 14 | IgnoredEvents.Add("Microsoft.AspNetCore.Mvc.BeforeOnActionExecuting"); 15 | IgnoredEvents.Add("Microsoft.AspNetCore.Mvc.AfterOnActionExecuting"); 16 | IgnoredEvents.Add("Microsoft.AspNetCore.Mvc.BeforeActionMethod"); 17 | IgnoredEvents.Add("Microsoft.AspNetCore.Mvc.BeforeControllerActionMethod"); 18 | IgnoredEvents.Add("Microsoft.AspNetCore.Mvc.AfterControllerActionMethod"); 19 | IgnoredEvents.Add("Microsoft.AspNetCore.Mvc.AfterActionMethod"); 20 | IgnoredEvents.Add("Microsoft.AspNetCore.Mvc.BeforeOnActionExecuted"); 21 | IgnoredEvents.Add("Microsoft.AspNetCore.Mvc.AfterOnActionExecuted"); 22 | IgnoredEvents.Add("Microsoft.AspNetCore.Mvc.AfterOnActionExecution"); 23 | IgnoredEvents.Add("Microsoft.AspNetCore.Mvc.BeforeOnActionExecuted"); 24 | IgnoredEvents.Add("Microsoft.AspNetCore.Mvc.AfterOnActionExecuted"); 25 | IgnoredEvents.Add("Microsoft.AspNetCore.Mvc.AfterOnActionExecution"); 26 | IgnoredEvents.Add("Microsoft.AspNetCore.Mvc.BeforeOnResultExecuting"); 27 | IgnoredEvents.Add("Microsoft.AspNetCore.Mvc.AfterOnResultExecuting"); 28 | IgnoredEvents.Add("Microsoft.AspNetCore.Mvc.BeforeOnResultExecuted"); 29 | IgnoredEvents.Add("Microsoft.AspNetCore.Mvc.AfterOnResultExecuted"); 30 | IgnoredEvents.Add("Microsoft.AspNetCore.Mvc.BeforeOnResourceExecuted"); 31 | IgnoredEvents.Add("Microsoft.AspNetCore.Mvc.AfterOnResourceExecuted"); 32 | IgnoredEvents.Add("Microsoft.AspNetCore.Mvc.AfterOnResourceExecuting"); 33 | 34 | IgnoredEvents.Add("Microsoft.AspNetCore.Mvc.Razor.BeginInstrumentationContext"); 35 | IgnoredEvents.Add("Microsoft.AspNetCore.Mvc.Razor.EndInstrumentationContext"); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.NetCore/AspNetCore/HostingOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace OpenTracing.Contrib.NetCore.AspNetCore 6 | { 7 | public class HostingOptions 8 | { 9 | public const string DefaultComponent = "HttpIn"; 10 | 11 | // Variables are lazily instantiated to prevent the app from crashing if the required assemblies are not referenced. 12 | 13 | private string _componentName = DefaultComponent; 14 | private List> _ignorePatterns; 15 | private Func _operationNameResolver; 16 | 17 | 18 | /// 19 | /// Allows changing the "component" tag of created spans. 20 | /// 21 | public string ComponentName 22 | { 23 | get => _componentName; 24 | set => _componentName = value ?? throw new ArgumentNullException(nameof(ComponentName)); 25 | } 26 | 27 | /// 28 | /// A list of delegates that define whether or not a given request should be ignored. 29 | /// 30 | /// If any delegate in the list returns true, the request will be ignored. 31 | /// 32 | public List> IgnorePatterns 33 | { 34 | get 35 | { 36 | if (_ignorePatterns == null) 37 | { 38 | _ignorePatterns = new List>(); 39 | } 40 | return _ignorePatterns; 41 | } 42 | } 43 | 44 | /// 45 | /// A delegates that defines from which requests tracing headers are extracted. 46 | /// 47 | public Func ExtractEnabled { get; set; } 48 | 49 | /// 50 | /// A delegate that returns the OpenTracing "operation name" for the given request. 51 | /// 52 | public Func OperationNameResolver 53 | { 54 | get 55 | { 56 | if (_operationNameResolver == null) 57 | { 58 | _operationNameResolver = (httpContext) => "HTTP " + httpContext.Request.Method; 59 | } 60 | return _operationNameResolver; 61 | } 62 | set => _operationNameResolver = value ?? throw new ArgumentNullException(nameof(OperationNameResolver)); 63 | } 64 | 65 | /// 66 | /// Allows the modification of the created span to e.g. add further tags. 67 | /// 68 | public Action OnRequest { get; set; } 69 | 70 | /// 71 | /// Allows the modification of the created span when error occured to e.g. add further tags. 72 | /// 73 | public Action OnError { get; set; } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.NetCore/AspNetCore/RequestHeadersExtractAdapter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using Microsoft.AspNetCore.Http; 5 | using OpenTracing.Propagation; 6 | 7 | namespace OpenTracing.Contrib.NetCore.AspNetCore 8 | { 9 | internal sealed class RequestHeadersExtractAdapter : ITextMap 10 | { 11 | private readonly IHeaderDictionary _headers; 12 | 13 | public RequestHeadersExtractAdapter(IHeaderDictionary headers) 14 | { 15 | _headers = headers ?? throw new ArgumentNullException(nameof(headers)); 16 | } 17 | 18 | public void Set(string key, string value) 19 | { 20 | throw new NotSupportedException("This class should only be used with ITracer.Extract"); 21 | } 22 | 23 | public IEnumerator> GetEnumerator() 24 | { 25 | foreach (var kvp in _headers) 26 | { 27 | yield return new KeyValuePair(kvp.Key, kvp.Value); 28 | } 29 | } 30 | 31 | IEnumerator IEnumerable.GetEnumerator() 32 | { 33 | return GetEnumerator(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.NetCore/Configuration/DiagnosticOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace OpenTracing.Contrib.NetCore.Configuration 4 | { 5 | public abstract class DiagnosticOptions 6 | { 7 | /// 8 | /// Defines whether or not generic events from this DiagnostSource should be logged as events. 9 | /// 10 | public bool LogEvents { get; set; } = true; 11 | 12 | /// 13 | /// Defines specific event names that should NOT be logged as events. Set to `false` if you don't want any events to be logged. 14 | /// 15 | public HashSet IgnoredEvents { get; } = new HashSet(); 16 | 17 | /// 18 | /// Defines whether or not a span should be created if there is no parent span. 19 | /// 20 | public bool StartRootSpans { get; set; } = true; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.NetCore/Configuration/IOpenTracingBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.Extensions.DependencyInjection 2 | { 3 | /// 4 | /// An interface for configuring OpenTracing services. 5 | /// 6 | public interface IOpenTracingBuilder 7 | { 8 | /// 9 | /// Gets the where OpenTracing services are configured. 10 | /// 11 | IServiceCollection Services { get; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.NetCore/Configuration/OpenTracingBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.DependencyInjection; 3 | 4 | namespace OpenTracing.Contrib.NetCore.Configuration 5 | { 6 | internal class OpenTracingBuilder : IOpenTracingBuilder 7 | { 8 | public IServiceCollection Services { get; } 9 | 10 | public OpenTracingBuilder(IServiceCollection services) 11 | { 12 | Services = services ?? throw new ArgumentNullException(nameof(services)); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.NetCore/Configuration/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.DependencyInjection.Extensions; 3 | using Microsoft.Extensions.Hosting; 4 | using OpenTracing; 5 | using OpenTracing.Contrib.NetCore; 6 | using OpenTracing.Contrib.NetCore.Configuration; 7 | using OpenTracing.Contrib.NetCore.Internal; 8 | using OpenTracing.Util; 9 | 10 | namespace Microsoft.Extensions.DependencyInjection 11 | { 12 | public static class ServiceCollectionExtensions 13 | { 14 | /// 15 | /// Adds OpenTracing instrumentation for ASP.NET Core, CoreFx (BCL), Entity Framework Core. 16 | /// 17 | public static IServiceCollection AddOpenTracing(this IServiceCollection services, Action builder = null) 18 | { 19 | if (services == null) 20 | throw new ArgumentNullException(nameof(services)); 21 | 22 | return services.AddOpenTracingCoreServices(otBuilder => 23 | { 24 | otBuilder.AddLoggerProvider(); 25 | otBuilder.AddEntityFrameworkCore(); 26 | otBuilder.AddGenericDiagnostics(); 27 | otBuilder.AddHttpHandler(); 28 | otBuilder.AddMicrosoftSqlClient(); 29 | otBuilder.AddSystemSqlClient(); 30 | 31 | if (AssemblyExists("Microsoft.AspNetCore.Hosting")) 32 | { 33 | otBuilder.AddAspNetCore(); 34 | } 35 | 36 | builder?.Invoke(otBuilder); 37 | }); 38 | } 39 | 40 | /// 41 | /// Adds the core services required for OpenTracing without any actual instrumentations. 42 | /// 43 | public static IServiceCollection AddOpenTracingCoreServices(this IServiceCollection services, Action builder = null) 44 | { 45 | if (services == null) 46 | throw new ArgumentNullException(nameof(services)); 47 | 48 | services.TryAddSingleton(GlobalTracer.Instance); 49 | services.TryAddSingleton(); 50 | 51 | services.TryAddSingleton(); 52 | services.TryAddEnumerable(ServiceDescriptor.Singleton()); 53 | 54 | var builderInstance = new OpenTracingBuilder(services); 55 | 56 | builder?.Invoke(builderInstance); 57 | 58 | return services; 59 | } 60 | 61 | private static bool AssemblyExists(string assemblyName) 62 | { 63 | var assemblies = AppDomain.CurrentDomain.GetAssemblies(); 64 | foreach (var assembly in assemblies) 65 | { 66 | if (assembly.FullName.StartsWith(assemblyName)) 67 | return true; 68 | } 69 | return false; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.NetCore/EntityFrameworkCore/EntityFrameworkCoreDiagnosticOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.EntityFrameworkCore.Diagnostics; 4 | 5 | namespace OpenTracing.Contrib.NetCore.Configuration 6 | { 7 | public class EntityFrameworkCoreDiagnosticOptions : DiagnosticOptions 8 | { 9 | // NOTE: Everything here that references any EFCore types MUST NOT be initialized in the constructor as that would throw on applications that don't reference EFCore. 10 | 11 | public const string DefaultComponent = "EFCore"; 12 | 13 | private string _componentName = DefaultComponent; 14 | private List> _ignorePatterns; 15 | private Func _operationNameResolver; 16 | 17 | public EntityFrameworkCoreDiagnosticOptions() 18 | { 19 | IgnoredEvents.Add("Microsoft.EntityFrameworkCore.ChangeTracking.StartedTracking"); 20 | IgnoredEvents.Add("Microsoft.EntityFrameworkCore.ChangeTracking.DetectChangesStarting"); 21 | IgnoredEvents.Add("Microsoft.EntityFrameworkCore.ChangeTracking.DetectChangesCompleted"); 22 | IgnoredEvents.Add("Microsoft.EntityFrameworkCore.ChangeTracking.ForeignKeyChangeDetected"); 23 | IgnoredEvents.Add("Microsoft.EntityFrameworkCore.ChangeTracking.StateChanged"); 24 | IgnoredEvents.Add("Microsoft.EntityFrameworkCore.ChangeTracking.ValueGenerated"); 25 | IgnoredEvents.Add("Microsoft.EntityFrameworkCore.Database.Command.CommandCreating"); 26 | IgnoredEvents.Add("Microsoft.EntityFrameworkCore.Database.Command.CommandCreated"); 27 | IgnoredEvents.Add("Microsoft.EntityFrameworkCore.Database.Command.DataReaderDisposing"); 28 | IgnoredEvents.Add("Microsoft.EntityFrameworkCore.Database.Connection.ConnectionOpening"); 29 | IgnoredEvents.Add("Microsoft.EntityFrameworkCore.Database.Connection.ConnectionOpened"); 30 | IgnoredEvents.Add("Microsoft.EntityFrameworkCore.Database.Connection.ConnectionClosing"); 31 | IgnoredEvents.Add("Microsoft.EntityFrameworkCore.Database.Connection.ConnectionClosed"); 32 | IgnoredEvents.Add("Microsoft.EntityFrameworkCore.Database.Transaction.TransactionStarting"); 33 | IgnoredEvents.Add("Microsoft.EntityFrameworkCore.Database.Transaction.TransactionStarted"); 34 | IgnoredEvents.Add("Microsoft.EntityFrameworkCore.Database.Transaction.TransactionCommitting"); 35 | IgnoredEvents.Add("Microsoft.EntityFrameworkCore.Database.Transaction.TransactionCommitted"); 36 | IgnoredEvents.Add("Microsoft.EntityFrameworkCore.Database.Transaction.TransactionDisposed"); 37 | IgnoredEvents.Add("Microsoft.EntityFrameworkCore.Infrastructure.ContextDisposed"); 38 | } 39 | 40 | /// 41 | /// A list of delegates that define whether or not a given EF Core command should be ignored. 42 | /// 43 | /// If any delegate in the list returns true, the EF Core command will be ignored. 44 | /// 45 | public List> IgnorePatterns => _ignorePatterns ??= new List>(); 46 | 47 | /// 48 | /// Allows changing the "component" tag of created spans. 49 | /// 50 | public string ComponentName 51 | { 52 | get => _componentName; 53 | set => _componentName = value ?? throw new ArgumentNullException(nameof(ComponentName)); 54 | } 55 | 56 | /// 57 | /// A delegate that returns the OpenTracing "operation name" for the given command. 58 | /// 59 | public Func OperationNameResolver 60 | { 61 | get 62 | { 63 | if (_operationNameResolver == null) 64 | { 65 | // Default value may not be set in the constructor because this would fail 66 | // if the target application does not reference EFCore. 67 | _operationNameResolver = (data) => "DB " + data.ExecuteMethod.ToString(); 68 | } 69 | return _operationNameResolver; 70 | } 71 | set => _operationNameResolver = value ?? throw new ArgumentNullException(nameof(OperationNameResolver)); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.NetCore/GenericListeners/GenericDiagnosticOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace OpenTracing.Contrib.NetCore.Configuration 4 | { 5 | public sealed class GenericDiagnosticOptions 6 | { 7 | public HashSet IgnoredListenerNames { get; } = new HashSet(); 8 | 9 | public Dictionary> IgnoredEvents { get; } = new Dictionary>(); 10 | 11 | public void IgnoreEvent(string diagnosticListenerName, string eventName) 12 | { 13 | if (diagnosticListenerName == null || eventName == null) 14 | return; 15 | 16 | HashSet ignoredListenerEvents; 17 | 18 | if (!IgnoredEvents.TryGetValue(diagnosticListenerName, out ignoredListenerEvents)) 19 | { 20 | ignoredListenerEvents = new HashSet(); 21 | IgnoredEvents.Add(diagnosticListenerName, ignoredListenerEvents); 22 | } 23 | 24 | ignoredListenerEvents.Add(eventName); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.NetCore/GenericListeners/GenericDiagnostics.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using Microsoft.Extensions.Logging; 5 | using Microsoft.Extensions.Options; 6 | using OpenTracing.Contrib.NetCore.Configuration; 7 | using OpenTracing.Contrib.NetCore.Internal; 8 | 9 | namespace OpenTracing.Contrib.NetCore.GenericListeners 10 | { 11 | /// 12 | /// A subscriber that logs ALL events to . 13 | /// 14 | internal sealed class GenericDiagnostics : DiagnosticObserver 15 | { 16 | private readonly GenericDiagnosticOptions _options; 17 | 18 | public GenericDiagnostics(ILoggerFactory loggerFactory, ITracer tracer, IOptions options) 19 | : base(loggerFactory, tracer) 20 | { 21 | _options = options?.Value ?? throw new ArgumentNullException(nameof(options)); 22 | } 23 | 24 | public override IDisposable SubscribeIfMatch(DiagnosticListener diagnosticListener) 25 | { 26 | if (_options.IgnoredListenerNames.Contains(diagnosticListener.Name)) 27 | { 28 | return null; 29 | } 30 | 31 | _options.IgnoredEvents.TryGetValue(diagnosticListener.Name, out var ignoredListenerEvents); 32 | 33 | return new GenericDiagnosticsSubscription(this, diagnosticListener, ignoredListenerEvents); 34 | } 35 | 36 | private class GenericDiagnosticsSubscription : IObserver>, IDisposable 37 | { 38 | private readonly GenericDiagnostics _subscriber; 39 | private readonly string _listenerName; 40 | private readonly HashSet _ignoredEvents; 41 | private readonly GenericEventProcessor _genericEventProcessor; 42 | 43 | private readonly IDisposable _subscription; 44 | 45 | 46 | public GenericDiagnosticsSubscription(GenericDiagnostics subscriber, DiagnosticListener diagnosticListener, 47 | HashSet ignoredEvents) 48 | { 49 | _subscriber = subscriber; 50 | _ignoredEvents = ignoredEvents; 51 | _listenerName = diagnosticListener.Name; 52 | 53 | _genericEventProcessor = new GenericEventProcessor(_listenerName, _subscriber.Tracer, subscriber.Logger); 54 | 55 | _subscription = diagnosticListener.Subscribe(this, IsEnabled); 56 | } 57 | 58 | public void Dispose() 59 | { 60 | _subscription.Dispose(); 61 | } 62 | 63 | private bool IsEnabled(string eventName) 64 | { 65 | if (_ignoredEvents != null && _ignoredEvents.Contains(eventName)) 66 | { 67 | if (_subscriber.IsLogLevelTraceEnabled) 68 | { 69 | _subscriber.Logger.LogTrace("Ignoring event '{ListenerName}/{Event}'", _listenerName, eventName); 70 | } 71 | 72 | return false; 73 | } 74 | 75 | return true; 76 | } 77 | 78 | public void OnCompleted() 79 | { 80 | } 81 | 82 | public void OnError(Exception error) 83 | { 84 | } 85 | 86 | public void OnNext(KeyValuePair value) 87 | { 88 | string eventName = value.Key; 89 | object untypedArg = value.Value; 90 | 91 | try 92 | { 93 | // We have to check this twice because EVERY subscriber is called 94 | // if ANY subscriber returns IsEnabled=true. 95 | if (!IsEnabled(eventName)) 96 | return; 97 | 98 | _genericEventProcessor?.ProcessEvent(eventName, untypedArg); 99 | } 100 | catch (Exception ex) 101 | { 102 | _subscriber.Logger.LogWarning(ex, "Event-Exception: {ListenerName}/{Event}", _listenerName, value.Key); 103 | } 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.NetCore/HttpHandler/HttpHandlerDiagnosticOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.Http; 4 | 5 | namespace OpenTracing.Contrib.NetCore.Configuration 6 | { 7 | public class HttpHandlerDiagnosticOptions : DiagnosticOptions 8 | { 9 | public const string PropertyIgnore = "ot-ignore"; 10 | 11 | public const string DefaultComponent = "HttpOut"; 12 | 13 | private string _componentName; 14 | private Func _operationNameResolver; 15 | 16 | /// 17 | /// Allows changing the "component" tag of created spans. 18 | /// 19 | public string ComponentName 20 | { 21 | get => _componentName; 22 | set => _componentName = value ?? throw new ArgumentNullException(nameof(ComponentName)); 23 | } 24 | 25 | /// 26 | /// A list of delegates that define whether or not a given request should be ignored. 27 | /// 28 | /// If any delegate in the list returns true, the request will be ignored. 29 | /// 30 | public List> IgnorePatterns { get; } = new List>(); 31 | 32 | /// 33 | /// A delegates that defines on what requests tracing headers are propagated. 34 | /// 35 | public Func InjectEnabled { get; set; } 36 | 37 | /// 38 | /// A delegate that returns the OpenTracing "operation name" for the given request. 39 | /// 40 | public Func OperationNameResolver 41 | { 42 | get => _operationNameResolver; 43 | set => _operationNameResolver = value ?? throw new ArgumentNullException(nameof(OperationNameResolver)); 44 | } 45 | 46 | /// 47 | /// Allows the modification of the created span to e.g. add further tags. 48 | /// 49 | public Action OnRequest { get; set; } 50 | 51 | /// 52 | /// Allows the modification of the created span when error occured to e.g. add further tags. 53 | /// 54 | public Action OnError { get; set; } 55 | 56 | public HttpHandlerDiagnosticOptions() 57 | { 58 | // Default settings 59 | 60 | ComponentName = DefaultComponent; 61 | 62 | IgnorePatterns.Add((request) => 63 | { 64 | IDictionary requestOptions; 65 | 66 | #if NETCOREAPP3_1 67 | requestOptions = request.Properties; 68 | #else 69 | requestOptions = request.Options; 70 | #endif 71 | 72 | return requestOptions.ContainsKey(PropertyIgnore); 73 | }); 74 | 75 | OperationNameResolver = (request) => 76 | { 77 | return "HTTP " + request.Method.Method; 78 | }; 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.NetCore/HttpHandler/HttpHeadersInjectAdapter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Net.Http.Headers; 5 | using OpenTracing.Propagation; 6 | 7 | namespace OpenTracing.Contrib.NetCore.HttpHandler 8 | { 9 | internal sealed class HttpHeadersInjectAdapter : ITextMap 10 | { 11 | private readonly HttpHeaders _headers; 12 | 13 | public HttpHeadersInjectAdapter(HttpHeaders headers) 14 | { 15 | _headers = headers ?? throw new ArgumentNullException(nameof(headers)); 16 | } 17 | 18 | public void Set(string key, string value) 19 | { 20 | if (_headers.Contains(key)) 21 | { 22 | _headers.Remove(key); 23 | } 24 | 25 | _headers.Add(key, value); 26 | } 27 | 28 | public IEnumerator> GetEnumerator() 29 | { 30 | throw new NotSupportedException("This class should only be used with ITracer.Inject"); 31 | } 32 | 33 | IEnumerator IEnumerable.GetEnumerator() 34 | { 35 | return GetEnumerator(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.NetCore/InstrumentationService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.Extensions.Hosting; 5 | using OpenTracing.Contrib.NetCore.Internal; 6 | 7 | namespace OpenTracing.Contrib.NetCore 8 | { 9 | /// 10 | /// Starts and stops all OpenTracing instrumentation components. 11 | /// 12 | internal class InstrumentationService : IHostedService 13 | { 14 | private readonly DiagnosticManager _diagnosticsManager; 15 | 16 | public InstrumentationService(DiagnosticManager diagnosticManager) 17 | { 18 | _diagnosticsManager = diagnosticManager ?? throw new ArgumentNullException(nameof(diagnosticManager)); 19 | } 20 | 21 | public Task StartAsync(CancellationToken cancellationToken) 22 | { 23 | _diagnosticsManager.Start(); 24 | 25 | return Task.CompletedTask; 26 | } 27 | 28 | public Task StopAsync(CancellationToken cancellationToken) 29 | { 30 | _diagnosticsManager.Stop(); 31 | 32 | return Task.CompletedTask; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.NetCore/Internal/DiagnosticEventObserver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using Microsoft.Extensions.Logging; 5 | using OpenTracing.Contrib.NetCore.Configuration; 6 | 7 | namespace OpenTracing.Contrib.NetCore.Internal 8 | { 9 | /// 10 | /// Base class that allows handling events from a single . 11 | /// 12 | internal abstract class DiagnosticEventObserver 13 | : DiagnosticObserver, IObserver> 14 | { 15 | private readonly DiagnosticOptions _options; 16 | private readonly GenericEventProcessor _genericEventProcessor; 17 | 18 | protected DiagnosticEventObserver(ILoggerFactory loggerFactory, ITracer tracer, DiagnosticOptions options) 19 | : base(loggerFactory, tracer) 20 | { 21 | _options = options; 22 | 23 | if (options.LogEvents) 24 | { 25 | _genericEventProcessor = new GenericEventProcessor(GetListenerName(), Tracer, Logger); 26 | } 27 | } 28 | 29 | public override IDisposable SubscribeIfMatch(DiagnosticListener diagnosticListener) 30 | { 31 | if (diagnosticListener.Name == GetListenerName()) 32 | { 33 | return diagnosticListener.Subscribe(this, IsEnabled); 34 | } 35 | 36 | return null; 37 | } 38 | 39 | void IObserver>.OnCompleted() 40 | { 41 | } 42 | 43 | void IObserver>.OnError(Exception error) 44 | { 45 | } 46 | 47 | void IObserver>.OnNext(KeyValuePair value) 48 | { 49 | try 50 | { 51 | if (IsEnabled(value.Key)) 52 | { 53 | HandleEvent(value.Key, value.Value); 54 | } 55 | } 56 | catch (Exception ex) 57 | { 58 | Logger.LogWarning(ex, "Event-Exception: {Event}", value.Key); 59 | } 60 | } 61 | 62 | /// 63 | /// The name of the that should be instrumented. 64 | /// 65 | protected abstract string GetListenerName(); 66 | 67 | protected virtual bool IsSupportedEvent(string eventName) => true; 68 | 69 | protected abstract IEnumerable HandledEventNames(); 70 | 71 | private bool IsEnabled(string eventName) 72 | { 73 | if (!IsSupportedEvent(eventName)) 74 | return false; 75 | 76 | foreach (var handledEventName in HandledEventNames()) 77 | { 78 | if (handledEventName == eventName) 79 | return true; 80 | } 81 | 82 | if (!_options.LogEvents || _options.IgnoredEvents.Contains(eventName)) 83 | return false; 84 | 85 | return true; 86 | } 87 | 88 | protected abstract void HandleEvent(string eventName, object untypedArg); 89 | 90 | protected void HandleUnknownEvent(string eventName, object untypedArg, IEnumerable> tags = null) 91 | { 92 | _genericEventProcessor?.ProcessEvent(eventName, untypedArg, tags); 93 | } 94 | 95 | protected void DisposeActiveScope(bool isScopeRequired, Exception exception = null) 96 | { 97 | IScope scope = Tracer.ScopeManager.Active; 98 | 99 | if (scope != null) 100 | { 101 | if (exception != null) 102 | { 103 | scope.Span.SetException(exception); 104 | } 105 | 106 | scope.Dispose(); 107 | } 108 | else if (isScopeRequired) 109 | { 110 | Logger.LogWarning("Scope not found"); 111 | } 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.NetCore/Internal/DiagnosticManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using Microsoft.Extensions.Logging; 6 | using Microsoft.Extensions.Options; 7 | 8 | namespace OpenTracing.Contrib.NetCore.Internal 9 | { 10 | /// 11 | /// Subscribes to and forwards events to individual instances. 12 | /// 13 | internal sealed class DiagnosticManager : IObserver, IDisposable 14 | { 15 | private readonly ILogger _logger; 16 | private readonly ITracer _tracer; 17 | private readonly IEnumerable _diagnosticSubscribers; 18 | private readonly DiagnosticManagerOptions _options; 19 | 20 | private readonly List _subscriptions = new List(); 21 | private IDisposable _allListenersSubscription; 22 | 23 | public bool IsRunning => _allListenersSubscription != null; 24 | 25 | public DiagnosticManager( 26 | ILoggerFactory loggerFactory, 27 | ITracer tracer, 28 | IEnumerable diagnosticSubscribers, 29 | IOptions options) 30 | { 31 | if (loggerFactory == null) 32 | throw new ArgumentNullException(nameof(loggerFactory)); 33 | 34 | if (diagnosticSubscribers == null) 35 | throw new ArgumentNullException(nameof(diagnosticSubscribers)); 36 | 37 | _tracer = tracer ?? throw new ArgumentNullException(nameof(tracer)); 38 | _options = options?.Value ?? throw new ArgumentNullException(nameof(options)); 39 | 40 | _logger = loggerFactory.CreateLogger(); 41 | 42 | _diagnosticSubscribers = diagnosticSubscribers; 43 | } 44 | 45 | public void Start() 46 | { 47 | if (_allListenersSubscription == null) 48 | { 49 | if (_tracer.IsNoopTracer() && !_options.StartInstrumentationForNoopTracer) 50 | { 51 | _logger.LogWarning("Instrumentation has not been started because no tracer was registered."); 52 | } 53 | else 54 | { 55 | _logger.LogTrace("Starting AllListeners subscription"); 56 | _allListenersSubscription = DiagnosticListener.AllListeners.Subscribe(this); 57 | } 58 | } 59 | } 60 | 61 | void IObserver.OnCompleted() 62 | { 63 | } 64 | 65 | void IObserver.OnError(Exception error) 66 | { 67 | } 68 | 69 | void IObserver.OnNext(DiagnosticListener listener) 70 | { 71 | foreach (var subscriber in _diagnosticSubscribers) 72 | { 73 | IDisposable subscription = subscriber.SubscribeIfMatch(listener); 74 | if (subscription != null) 75 | { 76 | _logger.LogTrace($"Subscriber '{subscriber.GetType().Name}' returned subscription for '{listener.Name}'"); 77 | _subscriptions.Add(subscription); 78 | } 79 | } 80 | } 81 | 82 | public void Stop() 83 | { 84 | if (_allListenersSubscription != null) 85 | { 86 | _logger.LogTrace("Stopping AllListeners subscription"); 87 | 88 | _allListenersSubscription.Dispose(); 89 | _allListenersSubscription = null; 90 | 91 | foreach (var subscription in _subscriptions) 92 | { 93 | subscription.Dispose(); 94 | } 95 | 96 | _subscriptions.Clear(); 97 | } 98 | } 99 | 100 | public void Dispose() 101 | { 102 | Stop(); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.NetCore/Internal/DiagnosticManagerOptions.cs: -------------------------------------------------------------------------------- 1 | namespace OpenTracing.Contrib.NetCore.Internal 2 | { 3 | public class DiagnosticManagerOptions 4 | { 5 | public bool StartInstrumentationForNoopTracer { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.NetCore/Internal/DiagnosticObserver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using Microsoft.Extensions.Logging; 4 | 5 | namespace OpenTracing.Contrib.NetCore.Internal 6 | { 7 | internal abstract class DiagnosticObserver 8 | { 9 | protected ILogger Logger { get; } 10 | 11 | protected ITracer Tracer { get; } 12 | 13 | protected bool IsLogLevelTraceEnabled { get; } 14 | 15 | protected DiagnosticObserver(ILoggerFactory loggerFactory, ITracer tracer) 16 | { 17 | if (loggerFactory == null) 18 | throw new ArgumentNullException(nameof(loggerFactory)); 19 | 20 | if (tracer == null) 21 | throw new ArgumentNullException(nameof(tracer)); 22 | 23 | Logger = loggerFactory.CreateLogger(GetType()); 24 | Tracer = tracer; 25 | 26 | IsLogLevelTraceEnabled = Logger.IsEnabled(LogLevel.Trace); 27 | } 28 | 29 | public abstract IDisposable SubscribeIfMatch(DiagnosticListener diagnosticListener); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.NetCore/Internal/GenericEventProcessor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using Microsoft.Extensions.Logging; 5 | using OpenTracing.Tag; 6 | 7 | namespace OpenTracing.Contrib.NetCore.Internal 8 | { 9 | internal class GenericEventProcessor 10 | { 11 | private readonly string _listenerName; 12 | private readonly ITracer _tracer; 13 | private readonly ILogger _logger; 14 | private readonly bool _isLogLevelTraceEnabled; 15 | 16 | public GenericEventProcessor(string listenerName, ITracer tracer, ILogger logger) 17 | { 18 | _listenerName = listenerName ?? throw new ArgumentNullException(nameof(listenerName)); 19 | _tracer = tracer ?? throw new ArgumentNullException(nameof(tracer)); 20 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 21 | 22 | _isLogLevelTraceEnabled = _logger.IsEnabled(LogLevel.Trace); 23 | } 24 | 25 | public void ProcessEvent(string eventName, object untypedArg, IEnumerable> tags = null) 26 | { 27 | Activity activity = Activity.Current; 28 | 29 | if (activity != null && eventName.EndsWith(".Start", StringComparison.Ordinal)) 30 | { 31 | HandleActivityStart(eventName, activity, untypedArg, tags); 32 | } 33 | else if (activity != null && eventName.EndsWith(".Stop", StringComparison.Ordinal)) 34 | { 35 | HandleActivityStop(eventName, activity); 36 | } 37 | else 38 | { 39 | HandleRegularEvent(eventName, untypedArg, tags); 40 | } 41 | } 42 | 43 | private void HandleActivityStart(string eventName, Activity activity, object untypedArg, IEnumerable> tags) 44 | { 45 | ISpanBuilder spanBuilder = _tracer.BuildSpan(activity.OperationName) 46 | .WithTag(Tags.Component, _listenerName); 47 | 48 | foreach (var tag in activity.Tags) 49 | { 50 | spanBuilder.WithTag(tag.Key, tag.Value); 51 | } 52 | 53 | if (tags != null) 54 | { 55 | foreach (var tag in tags) 56 | { 57 | spanBuilder.WithTag(tag.Key, tag.Value); 58 | } 59 | } 60 | 61 | spanBuilder.StartActive(); 62 | } 63 | 64 | private void HandleActivityStop(string eventName, Activity activity) 65 | { 66 | IScope scope = _tracer.ScopeManager.Active; 67 | if (scope != null) 68 | { 69 | scope.Dispose(); 70 | } 71 | else 72 | { 73 | _logger.LogWarning("No scope found. Event: {ListenerName}/{Event}", _listenerName, eventName); 74 | } 75 | } 76 | 77 | private void HandleRegularEvent(string eventName, object untypedArg, IEnumerable> tags) 78 | { 79 | var span = _tracer.ActiveSpan; 80 | 81 | if (span != null) 82 | { 83 | span.Log(GetLogFields(eventName, untypedArg, tags)); 84 | } 85 | else if (_isLogLevelTraceEnabled) 86 | { 87 | _logger.LogTrace("No ActiveSpan. Event: {ListenerName}/{Event}", _listenerName, eventName); 88 | } 89 | } 90 | 91 | private Dictionary GetLogFields(string eventName, object arg, IEnumerable> tags) 92 | { 93 | var fields = new Dictionary 94 | { 95 | { LogFields.Event, eventName }, 96 | { Tags.Component.Key, _listenerName } 97 | }; 98 | 99 | if (tags != null) 100 | { 101 | foreach (var tag in tags) 102 | { 103 | fields[tag.Key] = tag.Value; 104 | } 105 | } 106 | 107 | // TODO improve the hell out of this... :) 108 | 109 | if (arg != null) 110 | { 111 | Type argType = arg.GetType(); 112 | 113 | if (argType.IsPrimitive) 114 | { 115 | fields.Add("arg", arg); 116 | } 117 | else if (argType.Namespace == null) 118 | { 119 | // Anonymous types usually contain complex objects so their output is not really useful. 120 | // Ignoring them for now. 121 | } 122 | else 123 | { 124 | fields.Add("arg", arg.ToString()); 125 | 126 | if (_isLogLevelTraceEnabled) 127 | { 128 | _logger.LogTrace("Can not extract value for argument type '{Type}'. Using ToString()", argType); 129 | } 130 | } 131 | } 132 | 133 | return fields; 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.NetCore/Internal/GlobalTracerAccessor.cs: -------------------------------------------------------------------------------- 1 | using OpenTracing.Util; 2 | 3 | namespace OpenTracing.Contrib.NetCore.Internal 4 | { 5 | public class GlobalTracerAccessor : IGlobalTracerAccessor 6 | { 7 | public ITracer GetGlobalTracer() 8 | { 9 | return GlobalTracer.Instance; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.NetCore/Internal/IGlobalTracerAccessor.cs: -------------------------------------------------------------------------------- 1 | namespace OpenTracing.Contrib.NetCore.Internal 2 | { 3 | /// 4 | /// Helper interface which allows unit tests to mock the . 5 | /// 6 | public interface IGlobalTracerAccessor 7 | { 8 | ITracer GetGlobalTracer(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.NetCore/Internal/PropertyFetcher.cs: -------------------------------------------------------------------------------- 1 | // From https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceEventSource.cs 2 | 3 | using System; 4 | using System.Linq; 5 | using System.Reflection; 6 | 7 | namespace OpenTracing.Contrib.NetCore.Internal 8 | { 9 | internal class PropertyFetcher 10 | { 11 | private readonly string _propertyName; 12 | private Type _expectedType; 13 | private PropertyFetch _fetchForExpectedType; 14 | 15 | /// 16 | /// Make a new PropertyFetcher for a property named 'propertyName'. 17 | /// 18 | public PropertyFetcher(string propertyName) 19 | { 20 | _propertyName = propertyName; 21 | } 22 | 23 | /// 24 | /// Given an object fetch the property that this PropertySpec represents. 25 | /// 26 | public object Fetch(object obj) 27 | { 28 | Type objType = obj.GetType(); 29 | if (objType != _expectedType) 30 | { 31 | TypeInfo typeInfo = objType.GetTypeInfo(); 32 | var propertyInfo = typeInfo.DeclaredProperties.FirstOrDefault(p => string.Equals(p.Name, _propertyName, StringComparison.InvariantCultureIgnoreCase)); 33 | _fetchForExpectedType = PropertyFetch.FetcherForProperty(propertyInfo); 34 | _expectedType = objType; 35 | } 36 | return _fetchForExpectedType.Fetch(obj); 37 | } 38 | 39 | 40 | /// 41 | /// PropertyFetch is a helper class. It takes a PropertyInfo and then knows how 42 | /// to efficiently fetch that property from a .NET object (See Fetch method). 43 | /// It hides some slightly complex generic code. 44 | /// 45 | private class PropertyFetch 46 | { 47 | /// 48 | /// Create a property fetcher from a .NET Reflection PropertyInfo class that 49 | /// represents a property of a particular type. 50 | /// 51 | public static PropertyFetch FetcherForProperty(PropertyInfo propertyInfo) 52 | { 53 | if (propertyInfo == null) 54 | return new PropertyFetch(); // returns null on any fetch. 55 | 56 | Type typedPropertyFetcher = typeof(TypedFetchProperty<,>); 57 | Type instantiatedTypedPropertyFetcher = typedPropertyFetcher.GetTypeInfo().MakeGenericType( 58 | propertyInfo.DeclaringType, propertyInfo.PropertyType); 59 | 60 | return (PropertyFetch)Activator.CreateInstance(instantiatedTypedPropertyFetcher, propertyInfo); 61 | } 62 | 63 | /// 64 | /// Given an object, fetch the property that this propertyFech represents. 65 | /// 66 | public virtual object Fetch(object obj) 67 | { 68 | return null; 69 | } 70 | 71 | private class TypedFetchProperty : PropertyFetch 72 | { 73 | public TypedFetchProperty(PropertyInfo property) 74 | { 75 | _propertyFetch = (Func)property.GetMethod.CreateDelegate(typeof(Func)); 76 | } 77 | public override object Fetch(object obj) 78 | { 79 | return _propertyFetch((TObject)obj); 80 | } 81 | private readonly Func _propertyFetch; 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.NetCore/Internal/SpanExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using OpenTracing.Tag; 4 | 5 | namespace OpenTracing.Contrib.NetCore.Internal 6 | { 7 | internal static class SpanExtensions 8 | { 9 | /// 10 | /// Sets the tag and adds information about the 11 | /// to the given . 12 | /// 13 | public static void SetException(this ISpan span, Exception exception) 14 | { 15 | if (span == null || exception == null) 16 | return; 17 | 18 | span.SetTag(Tags.Error, true); 19 | 20 | span.Log(new Dictionary(3) 21 | { 22 | { LogFields.Event, Tags.Error.Key }, 23 | { LogFields.ErrorKind, exception.GetType().Name }, 24 | { LogFields.ErrorObject, exception } 25 | }); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.NetCore/Internal/TracerExtensions.cs: -------------------------------------------------------------------------------- 1 | using OpenTracing.Noop; 2 | using OpenTracing.Util; 3 | 4 | namespace OpenTracing.Contrib.NetCore.Internal 5 | { 6 | internal static class TracerExtensions 7 | { 8 | public static bool IsNoopTracer(this ITracer tracer) 9 | { 10 | if (tracer is NoopTracer) 11 | return true; 12 | 13 | // There's no way to check the underlying tracer on the instance so we have to check the static method. 14 | if (tracer is GlobalTracer && !GlobalTracer.IsRegistered()) 15 | return true; 16 | 17 | return false; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.NetCore/Logging/OpenTracingLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.Extensions.Logging; 4 | using OpenTracing.Contrib.NetCore.Internal; 5 | using OpenTracing.Noop; 6 | using OpenTracing.Util; 7 | 8 | namespace OpenTracing.Contrib.NetCore.Logging 9 | { 10 | internal class OpenTracingLogger : ILogger 11 | { 12 | private const string OriginalFormatPropertyName = "{OriginalFormat}"; 13 | 14 | private readonly string _categoryName; 15 | private readonly IGlobalTracerAccessor _globalTracerAccessor; 16 | 17 | public OpenTracingLogger(IGlobalTracerAccessor globalTracerAccessor, string categoryName) 18 | { 19 | _globalTracerAccessor = globalTracerAccessor; 20 | _categoryName = categoryName; 21 | } 22 | 23 | public IDisposable BeginScope(TState state) 24 | { 25 | return NoopDisposable.Instance; 26 | } 27 | 28 | public bool IsEnabled(LogLevel logLevel) 29 | { 30 | // Filtering should be done via the general Logging filtering feature. 31 | ITracer tracer = _globalTracerAccessor.GetGlobalTracer(); 32 | return !( 33 | (tracer is NoopTracer) || 34 | (tracer is GlobalTracer && !GlobalTracer.IsRegistered())); 35 | } 36 | 37 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) 38 | { 39 | if (formatter == null) 40 | { 41 | // This throws an Exception e.g. in Microsoft's DebugLogger but we don't want the app to crash if the logger has an issue. 42 | return; 43 | } 44 | 45 | ITracer tracer = _globalTracerAccessor.GetGlobalTracer(); 46 | ISpan span = tracer.ActiveSpan; 47 | 48 | if (span == null) 49 | { 50 | // Creating a new span for a log message seems brutal so we ignore messages if we can't attach it to an active span. 51 | return; 52 | } 53 | 54 | if (!IsEnabled(logLevel)) 55 | { 56 | return; 57 | } 58 | 59 | var fields = new Dictionary 60 | { 61 | { "component", _categoryName }, 62 | { "level", logLevel.ToString() } 63 | }; 64 | 65 | try 66 | { 67 | if (eventId.Id != 0) 68 | { 69 | fields["eventId"] = eventId.Id; 70 | } 71 | 72 | try 73 | { 74 | // This throws if the argument count (message format vs. actual args) doesn't match. 75 | // e.g. LogInformation("Foo {Arg1} {Arg2}", arg1); 76 | // We want to preserve as much as possible from the original log message so we just continue without this information. 77 | string message = formatter(state, exception); 78 | fields[LogFields.Message] = message; 79 | } 80 | catch (Exception) 81 | { 82 | /* no-op */ 83 | } 84 | 85 | if (exception != null) 86 | { 87 | fields[LogFields.ErrorKind] = exception.GetType().FullName; 88 | fields[LogFields.ErrorObject] = exception; 89 | } 90 | 91 | bool eventAdded = false; 92 | 93 | var structure = state as IEnumerable>; 94 | if (structure != null) 95 | { 96 | try 97 | { 98 | // The enumerator throws if the argument count (message format vs. actual args) doesn't match. 99 | // We want to preserve as much as possible from the original log message so we just ignore 100 | // this error and take as many properties as possible. 101 | foreach (var property in structure) 102 | { 103 | if (string.Equals(property.Key, OriginalFormatPropertyName, StringComparison.Ordinal) 104 | && property.Value is string messageTemplateString) 105 | { 106 | fields[LogFields.Event] = messageTemplateString; 107 | eventAdded = true; 108 | } 109 | else 110 | { 111 | fields[property.Key] = property.Value; 112 | } 113 | } 114 | } 115 | catch (IndexOutOfRangeException) 116 | { 117 | /* no-op */ 118 | } 119 | } 120 | 121 | if (!eventAdded) 122 | { 123 | fields[LogFields.Event] = "log"; 124 | } 125 | } 126 | catch (Exception logException) 127 | { 128 | fields["opentracing.contrib.netcore.error"] = logException.ToString(); 129 | } 130 | 131 | span.Log(fields); 132 | } 133 | 134 | private class NoopDisposable : IDisposable 135 | { 136 | public static NoopDisposable Instance = new NoopDisposable(); 137 | 138 | public void Dispose() 139 | { 140 | } 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.NetCore/Logging/OpenTracingLoggerProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | using OpenTracing.Contrib.NetCore.Internal; 4 | 5 | namespace OpenTracing.Contrib.NetCore.Logging 6 | { 7 | /// 8 | /// The provider for the . 9 | /// 10 | [ProviderAlias("OpenTracing")] 11 | internal class OpenTracingLoggerProvider : ILoggerProvider 12 | { 13 | private readonly IGlobalTracerAccessor _globalTracerAccessor; 14 | 15 | public OpenTracingLoggerProvider(IGlobalTracerAccessor globalTracerAccessor) 16 | { 17 | _globalTracerAccessor = globalTracerAccessor ?? throw new ArgumentNullException(nameof(globalTracerAccessor)); 18 | } 19 | 20 | /// 21 | public ILogger CreateLogger(string categoryName) 22 | { 23 | return new OpenTracingLogger(_globalTracerAccessor, categoryName); 24 | } 25 | 26 | public void Dispose() 27 | { 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.NetCore/MicrosoftSqlClient/MicrosoftSqlClientDiagnosticOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.Data.SqlClient; 5 | 6 | namespace OpenTracing.Contrib.NetCore.Configuration 7 | { 8 | public class MicrosoftSqlClientDiagnosticOptions : DiagnosticOptions 9 | { 10 | public static class EventNames 11 | { 12 | // https://github.com/dotnet/SqlClient/blob/master/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlClientDiagnosticListenerExtensions.cs 13 | 14 | private const string SqlClientPrefix = "Microsoft.Data.SqlClient."; 15 | 16 | public const string WriteCommandBefore = SqlClientPrefix + nameof(WriteCommandBefore); 17 | public const string WriteCommandAfter = SqlClientPrefix + nameof(WriteCommandAfter); 18 | public const string WriteCommandError = SqlClientPrefix + nameof(WriteCommandError); 19 | 20 | public const string WriteConnectionOpenBefore = SqlClientPrefix + nameof(WriteConnectionOpenBefore); 21 | public const string WriteConnectionOpenAfter = SqlClientPrefix + nameof(WriteConnectionOpenAfter); 22 | public const string WriteConnectionOpenError = SqlClientPrefix + nameof(WriteConnectionOpenError); 23 | 24 | public const string WriteConnectionCloseBefore = SqlClientPrefix + nameof(WriteConnectionCloseBefore); 25 | public const string WriteConnectionCloseAfter = SqlClientPrefix + nameof(WriteConnectionCloseAfter); 26 | public const string WriteConnectionCloseError = SqlClientPrefix + nameof(WriteConnectionCloseError); 27 | 28 | public const string WriteTransactionCommitBefore = SqlClientPrefix + nameof(WriteTransactionCommitBefore); 29 | public const string WriteTransactionCommitAfter = SqlClientPrefix + nameof(WriteTransactionCommitAfter); 30 | public const string WriteTransactionCommitError = SqlClientPrefix + nameof(WriteTransactionCommitError); 31 | 32 | public const string WriteTransactionRollbackBefore = SqlClientPrefix + nameof(WriteTransactionRollbackBefore); 33 | public const string WriteTransactionRollbackAfter = SqlClientPrefix + nameof(WriteTransactionRollbackAfter); 34 | public const string WriteTransactionRollbackError = SqlClientPrefix + nameof(WriteTransactionRollbackError); 35 | } 36 | 37 | public const string DefaultComponent = "SqlClient"; 38 | public const string SqlClientPrefix = "sqlClient "; 39 | 40 | private string _componentName = DefaultComponent; 41 | private List> _ignorePatterns; 42 | private Func _operationNameResolver; 43 | 44 | /// 45 | /// A list of delegates that define whether or not a given SQL command should be ignored. 46 | /// 47 | /// If any delegate in the list returns true, the SQL command will be ignored. 48 | /// 49 | public List> IgnorePatterns => _ignorePatterns ??= new List>(); 50 | 51 | /// 52 | /// Allows changing the "component" tag of created spans. 53 | /// 54 | public string ComponentName 55 | { 56 | get => _componentName; 57 | set => _componentName = value ?? throw new ArgumentNullException(nameof(ComponentName)); 58 | } 59 | 60 | /// 61 | /// A delegate that returns the OpenTracing "operation name" for the given command. 62 | /// 63 | public Func OperationNameResolver 64 | { 65 | get 66 | { 67 | if (_operationNameResolver == null) 68 | { 69 | // Default value may not be set in the constructor because this would fail 70 | // if the target application does not reference SqlClient. 71 | _operationNameResolver = (cmd) => 72 | { 73 | var commandType = cmd.CommandText?.Split(' '); 74 | return $"{SqlClientPrefix}{commandType?.FirstOrDefault()}"; 75 | }; 76 | } 77 | return _operationNameResolver; 78 | } 79 | set => _operationNameResolver = value ?? throw new ArgumentNullException(nameof(OperationNameResolver)); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.NetCore/MicrosoftSqlClient/MicrosoftSqlClientDiagnostics.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using Microsoft.Data.SqlClient; 5 | using Microsoft.Extensions.Logging; 6 | using Microsoft.Extensions.Options; 7 | using OpenTracing.Contrib.NetCore.Configuration; 8 | using OpenTracing.Contrib.NetCore.Internal; 9 | using OpenTracing.Tag; 10 | 11 | namespace OpenTracing.Contrib.NetCore.MicrosoftSqlClient 12 | { 13 | internal sealed class MicrosoftSqlClientDiagnostics : DiagnosticEventObserver 14 | { 15 | public const string DiagnosticListenerName = "SqlClientDiagnosticListener"; 16 | 17 | private static readonly PropertyFetcher _activityCommand_RequestFetcher = new PropertyFetcher("Command"); 18 | private static readonly PropertyFetcher _exception_ExceptionFetcher = new PropertyFetcher("Exception"); 19 | 20 | private readonly MicrosoftSqlClientDiagnosticOptions _options; 21 | private readonly ConcurrentDictionary _spanStorage; 22 | 23 | public MicrosoftSqlClientDiagnostics(ILoggerFactory loggerFactory, ITracer tracer, IOptions options) 24 | : base(loggerFactory, tracer, options?.Value) 25 | { 26 | _options = options?.Value ?? throw new ArgumentNullException(nameof(options)); 27 | _spanStorage = new ConcurrentDictionary(); 28 | } 29 | 30 | protected override string GetListenerName() => DiagnosticListenerName; 31 | 32 | /// 33 | /// Both diagnostic listeners for System.Data.SqlClient and Microsoft.Data.SqlClient use the same listener name, 34 | /// so we need to make sure this observer gets the correct events. 35 | /// 36 | protected override bool IsSupportedEvent(string eventName) => eventName.StartsWith("Microsoft."); 37 | 38 | protected override IEnumerable HandledEventNames() 39 | { 40 | yield return MicrosoftSqlClientDiagnosticOptions.EventNames.WriteCommandBefore; 41 | yield return MicrosoftSqlClientDiagnosticOptions.EventNames.WriteCommandError; 42 | yield return MicrosoftSqlClientDiagnosticOptions.EventNames.WriteCommandAfter; 43 | } 44 | 45 | protected override void HandleEvent(string eventName, object untypedArg) 46 | { 47 | switch (eventName) 48 | { 49 | case MicrosoftSqlClientDiagnosticOptions.EventNames.WriteCommandBefore: 50 | { 51 | var cmd = (SqlCommand)_activityCommand_RequestFetcher.Fetch(untypedArg); 52 | 53 | var activeSpan = Tracer.ActiveSpan; 54 | 55 | if (activeSpan == null && !_options.StartRootSpans) 56 | { 57 | if (IsLogLevelTraceEnabled) 58 | { 59 | Logger.LogTrace("Ignoring SQL command due to missing parent span"); 60 | } 61 | return; 62 | } 63 | 64 | if (IgnoreEvent(cmd)) 65 | { 66 | if (IsLogLevelTraceEnabled) 67 | { 68 | Logger.LogTrace("Ignoring SQL command due to IgnorePatterns"); 69 | } 70 | return; 71 | } 72 | 73 | string operationName = _options.OperationNameResolver(cmd); 74 | 75 | var span = Tracer.BuildSpan(operationName) 76 | .AsChildOf(activeSpan) 77 | .WithTag(Tags.SpanKind, Tags.SpanKindClient) 78 | .WithTag(Tags.Component, _options.ComponentName) 79 | .WithTag(Tags.DbInstance, cmd.Connection.Database) 80 | .WithTag(Tags.DbStatement, cmd.CommandText) 81 | .Start(); 82 | 83 | _spanStorage.TryAdd(cmd, span); 84 | } 85 | break; 86 | 87 | case MicrosoftSqlClientDiagnosticOptions.EventNames.WriteCommandError: 88 | { 89 | var cmd = (SqlCommand)_activityCommand_RequestFetcher.Fetch(untypedArg); 90 | var ex = (Exception)_exception_ExceptionFetcher.Fetch(untypedArg); 91 | 92 | if (_spanStorage.TryRemove(cmd, out var span)) 93 | { 94 | span.SetException(ex); 95 | span.Finish(); 96 | } 97 | } 98 | break; 99 | 100 | case MicrosoftSqlClientDiagnosticOptions.EventNames.WriteCommandAfter: 101 | { 102 | var cmd = (SqlCommand)_activityCommand_RequestFetcher.Fetch(untypedArg); 103 | 104 | if (_spanStorage.TryRemove(cmd, out var span)) 105 | { 106 | span.Finish(); 107 | } 108 | } 109 | break; 110 | 111 | default: 112 | HandleUnknownEvent(eventName, untypedArg); 113 | break; 114 | } 115 | } 116 | 117 | private bool IgnoreEvent(SqlCommand sqlCommand) 118 | { 119 | foreach (Func ignore in _options.IgnorePatterns) 120 | { 121 | if (ignore(sqlCommand)) 122 | return true; 123 | } 124 | 125 | return false; 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.NetCore/OpenTracing.Contrib.NetCore.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1;net6.0;net7.0 5 | Adds OpenTracing instrumentation for .NET Core apps that use the `Microsoft.Extensions.*` stack. 6 | Instrumented components: HttpClient calls, ASP.NET Core, Entity Framework Core and any other library that uses DiagnosticSource events. 7 | opentracing;distributed-tracing;tracing;netcore 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 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 | 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 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.NetCore/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("OpenTracing.Contrib.NetCore.Tests, PublicKey=" + 4 | "00240000048000009400000006020000002400005253413100040000010001005d933a901e4687" + 5 | "a5fad68c57b11f12b925922968cb9b7c4ef5744b4e3c7d7535095fb13a87169a4dde0ef547a035" + 6 | "0089ae94e192dfeddfa8272b12a76cd42bd36d8b7003bfff49bc825c1a9d510b3c37549efac581" + 7 | "0194d6cfdac8c4dc2f465e4c893a32b65a2f2d55faa3d24d6e68d861a7ccdec74d80ccf03b4489" + 8 | "f51328ba")] 9 | 10 | [assembly: InternalsVisibleTo("OpenTracing.Contrib.NetCore.Benchmarks, PublicKey=" + 11 | "00240000048000009400000006020000002400005253413100040000010001005d933a901e4687" + 12 | "a5fad68c57b11f12b925922968cb9b7c4ef5744b4e3c7d7535095fb13a87169a4dde0ef547a035" + 13 | "0089ae94e192dfeddfa8272b12a76cd42bd36d8b7003bfff49bc825c1a9d510b3c37549efac581" + 14 | "0194d6cfdac8c4dc2f465e4c893a32b65a2f2d55faa3d24d6e68d861a7ccdec74d80ccf03b4489" + 15 | "f51328ba")] 16 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.NetCore/SystemSqlClient/SqlClientDiagnosticOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.SqlClient; 4 | using System.Linq; 5 | 6 | namespace OpenTracing.Contrib.NetCore.Configuration 7 | { 8 | public class SqlClientDiagnosticOptions : DiagnosticOptions 9 | { 10 | public const string DefaultComponent = "SqlClient"; 11 | public const string SqlClientPrefix = "sqlClient "; 12 | 13 | private string _componentName = DefaultComponent; 14 | private List> _ignorePatterns; 15 | private Func _operationNameResolver; 16 | 17 | /// 18 | /// A list of delegates that define whether or not a given SQL command should be ignored. 19 | /// 20 | /// If any delegate in the list returns true, the SQL command will be ignored. 21 | /// 22 | public List> IgnorePatterns => _ignorePatterns ??= new List>(); 23 | 24 | /// 25 | /// Allows changing the "component" tag of created spans. 26 | /// 27 | public string ComponentName 28 | { 29 | get => _componentName; 30 | set => _componentName = value ?? throw new ArgumentNullException(nameof(ComponentName)); 31 | } 32 | 33 | /// 34 | /// A delegate that returns the OpenTracing "operation name" for the given command. 35 | /// 36 | public Func OperationNameResolver 37 | { 38 | get 39 | { 40 | if (_operationNameResolver == null) 41 | { 42 | // Default value may not be set in the constructor because this would fail 43 | // if the target application does not reference SqlClient. 44 | _operationNameResolver = (cmd) => 45 | { 46 | var commandType = cmd.CommandText?.Split(' '); 47 | return $"{SqlClientPrefix}{commandType?.FirstOrDefault()}"; 48 | }; 49 | } 50 | return _operationNameResolver; 51 | } 52 | set => _operationNameResolver = value ?? throw new ArgumentNullException(nameof(OperationNameResolver)); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.NetCore/SystemSqlClient/SqlClientDiagnostics.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Data.SqlClient; 5 | using Microsoft.Extensions.Logging; 6 | using Microsoft.Extensions.Options; 7 | using OpenTracing.Contrib.NetCore.Configuration; 8 | using OpenTracing.Contrib.NetCore.Internal; 9 | using OpenTracing.Tag; 10 | 11 | namespace OpenTracing.Contrib.NetCore.SystemSqlClient 12 | { 13 | internal sealed class SqlClientDiagnostics : DiagnosticEventObserver 14 | { 15 | public const string DiagnosticListenerName = "SqlClientDiagnosticListener"; 16 | 17 | private static readonly PropertyFetcher _writeCommandBefore_CommandFetcher = new PropertyFetcher("Command"); 18 | private static readonly PropertyFetcher _writeCommandError_CommandFetcher = new PropertyFetcher("Command"); 19 | private static readonly PropertyFetcher _writeCommandAfter_CommandFetcher = new PropertyFetcher("Command"); 20 | private static readonly PropertyFetcher _exception_ExceptionFetcher = new PropertyFetcher("Exception"); 21 | 22 | private readonly SqlClientDiagnosticOptions _options; 23 | private readonly ConcurrentDictionary _spanStorage; 24 | 25 | public SqlClientDiagnostics(ILoggerFactory loggerFactory, ITracer tracer, IOptions options) 26 | : base(loggerFactory, tracer, options?.Value) 27 | { 28 | _options = options?.Value ?? throw new ArgumentNullException(nameof(options)); 29 | _spanStorage = new ConcurrentDictionary(); 30 | } 31 | 32 | protected override string GetListenerName() => DiagnosticListenerName; 33 | 34 | /// 35 | /// Both diagnostic listeners for System.Data.SqlClient and Microsoft.Data.SqlClient use the same listener name, 36 | /// so we need to make sure this observer gets the correct events. 37 | /// 38 | protected override bool IsSupportedEvent(string eventName) => eventName.StartsWith("System."); 39 | 40 | protected override IEnumerable HandledEventNames() 41 | { 42 | yield return "System.Data.SqlClient.WriteCommandBefore"; 43 | yield return "System.Data.SqlClient.WriteCommandError"; 44 | yield return "System.Data.SqlClient.WriteCommandAfter"; 45 | } 46 | 47 | protected override void HandleEvent(string eventName, object untypedArg) 48 | { 49 | switch (eventName) 50 | { 51 | case "System.Data.SqlClient.WriteCommandBefore": 52 | { 53 | var cmd = (SqlCommand)_writeCommandBefore_CommandFetcher.Fetch(untypedArg); 54 | 55 | var activeSpan = Tracer.ActiveSpan; 56 | 57 | if (activeSpan == null && !_options.StartRootSpans) 58 | { 59 | if (IsLogLevelTraceEnabled) 60 | { 61 | Logger.LogTrace("Ignoring SQL command due to missing parent span"); 62 | } 63 | return; 64 | } 65 | 66 | if (IgnoreEvent(cmd)) 67 | { 68 | if (IsLogLevelTraceEnabled) 69 | { 70 | Logger.LogTrace("Ignoring SQL command due to IgnorePatterns"); 71 | } 72 | return; 73 | } 74 | 75 | string operationName = _options.OperationNameResolver(cmd); 76 | 77 | var span = Tracer.BuildSpan(operationName) 78 | .AsChildOf(activeSpan) 79 | .WithTag(Tags.SpanKind, Tags.SpanKindClient) 80 | .WithTag(Tags.Component, _options.ComponentName) 81 | .WithTag(Tags.DbInstance, cmd.Connection.Database) 82 | .WithTag(Tags.DbStatement, cmd.CommandText) 83 | .Start(); 84 | 85 | _spanStorage.TryAdd(cmd, span); 86 | } 87 | break; 88 | 89 | case "System.Data.SqlClient.WriteCommandError": 90 | { 91 | var cmd = (SqlCommand)_writeCommandError_CommandFetcher.Fetch(untypedArg); 92 | var ex = (Exception)_exception_ExceptionFetcher.Fetch(untypedArg); 93 | 94 | if (_spanStorage.TryRemove(cmd, out var span)) 95 | { 96 | span.SetException(ex); 97 | span.Finish(); 98 | } 99 | } 100 | break; 101 | 102 | case "System.Data.SqlClient.WriteCommandAfter": 103 | { 104 | var cmd = (SqlCommand)_writeCommandAfter_CommandFetcher.Fetch(untypedArg); 105 | 106 | if (_spanStorage.TryRemove(cmd, out var span)) 107 | { 108 | span.Finish(); 109 | } 110 | } 111 | break; 112 | 113 | default: 114 | HandleUnknownEvent(eventName, untypedArg); 115 | break; 116 | } 117 | } 118 | 119 | private bool IgnoreEvent(SqlCommand sqlCommand) 120 | { 121 | foreach (Func ignore in _options.IgnorePatterns) 122 | { 123 | if (ignore(sqlCommand)) 124 | return true; 125 | } 126 | 127 | return false; 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /test/OpenTracing.Contrib.NetCore.Tests/Internal/DiagnosticManagerTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.Extensions.Logging.Abstractions; 3 | using Microsoft.Extensions.Options; 4 | using OpenTracing.Contrib.NetCore.Internal; 5 | using OpenTracing.Mock; 6 | using OpenTracing.Noop; 7 | using OpenTracing.Util; 8 | using Xunit; 9 | 10 | namespace OpenTracing.Contrib.NetCore.Tests.Internal 11 | { 12 | public class DiagnosticManagerTest 13 | { 14 | [Fact] 15 | public void Does_not_Start_if_Tracer_is_NoopTracer() 16 | { 17 | var loggerFactory = new NullLoggerFactory(); 18 | var tracer = NoopTracerFactory.Create(); 19 | var diagnosticSubscribers = new List(); 20 | var options = Options.Create(new DiagnosticManagerOptions()); 21 | 22 | using (DiagnosticManager diagnosticManager = new DiagnosticManager(loggerFactory, tracer, diagnosticSubscribers, options)) 23 | { 24 | Assert.False(diagnosticManager.IsRunning); 25 | 26 | diagnosticManager.Start(); 27 | 28 | Assert.False(diagnosticManager.IsRunning); 29 | } 30 | } 31 | 32 | [Fact] 33 | public void Does_not_Start_if_Tracer_is_GlobalTracer_with_NoopTracer() 34 | { 35 | var loggerFactory = new NullLoggerFactory(); 36 | var tracer = GlobalTracer.Instance; 37 | var diagnosticSubscribers = new List(); 38 | var options = Options.Create(new DiagnosticManagerOptions()); 39 | 40 | using (DiagnosticManager diagnosticManager = new DiagnosticManager(loggerFactory, tracer, diagnosticSubscribers, options)) 41 | { 42 | Assert.False(diagnosticManager.IsRunning); 43 | 44 | diagnosticManager.Start(); 45 | 46 | Assert.False(diagnosticManager.IsRunning); 47 | } 48 | } 49 | 50 | [Fact] 51 | public void Start_if_valid_Tracer() 52 | { 53 | var loggerFactory = new NullLoggerFactory(); 54 | var tracer = new MockTracer(); 55 | var diagnosticSubscribers = new List(); 56 | var options = Options.Create(new DiagnosticManagerOptions()); 57 | 58 | using (DiagnosticManager diagnosticManager = new DiagnosticManager(loggerFactory, tracer, diagnosticSubscribers, options)) 59 | { 60 | Assert.False(diagnosticManager.IsRunning); 61 | 62 | diagnosticManager.Start(); 63 | 64 | Assert.True(diagnosticManager.IsRunning); 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /test/OpenTracing.Contrib.NetCore.Tests/Internal/PropertyFetcherTest.cs: -------------------------------------------------------------------------------- 1 | using OpenTracing.Contrib.NetCore.Internal; 2 | using Xunit; 3 | 4 | namespace OpenTracing.Contrib.NetCore.Tests.Internal 5 | { 6 | public class PropertyFetcherTest 7 | { 8 | public class TestClass 9 | { 10 | public string TestProperty { get; set; } 11 | } 12 | 13 | [Fact] 14 | public void Fetch_NameNotFound_NullReturned() 15 | { 16 | var obj = new TestClass { TestProperty = "TestValue" }; 17 | 18 | var sut = new PropertyFetcher("DifferentProperty"); 19 | 20 | var result = sut.Fetch(obj); 21 | 22 | Assert.Null(result); 23 | } 24 | 25 | [Fact] 26 | public void Fetch_NameFound_ValueReturned() 27 | { 28 | var obj = new TestClass { TestProperty = "TestValue" }; 29 | 30 | var sut = new PropertyFetcher("TestProperty"); 31 | 32 | var result = sut.Fetch(obj); 33 | 34 | Assert.Equal("TestValue", result); 35 | } 36 | 37 | [Fact] 38 | public void Fetch_NameFoundDifferentCasing_ValueReturned() 39 | { 40 | var obj = new TestClass { TestProperty = "TestValue" }; 41 | 42 | var sut = new PropertyFetcher("testproperty"); 43 | 44 | var result = sut.Fetch(obj); 45 | 46 | Assert.Equal("TestValue", result); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /test/OpenTracing.Contrib.NetCore.Tests/Logging/LoggingDependencyInjectionTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Logging; 4 | using OpenTracing.Propagation; 5 | using Xunit; 6 | 7 | namespace OpenTracing.Contrib.Netcore.Tests.Logging 8 | { 9 | public class LoggingDependencyInjectionTest 10 | { 11 | [Fact] 12 | public void Resolving_tracer_that_needs_ILoggerFactory_succeeds() 13 | { 14 | // https://github.com/opentracing-contrib/csharp-netcore/issues/14 15 | 16 | var serviceProvider = new ServiceCollection() 17 | .AddLogging() 18 | .AddOpenTracingCoreServices(ot => 19 | { 20 | ot.AddLoggerProvider(); 21 | ot.Services.AddSingleton(); 22 | }) 23 | .BuildServiceProvider(); 24 | 25 | var tracer = serviceProvider.GetRequiredService(); 26 | Assert.IsType(tracer); 27 | } 28 | 29 | private class TracerWithLoggerFactory : ITracer 30 | { 31 | public TracerWithLoggerFactory(ILoggerFactory loggerFactory) 32 | { 33 | } 34 | 35 | public IScopeManager ScopeManager => throw new NotSupportedException(); 36 | 37 | public ISpan ActiveSpan => throw new NotSupportedException(); 38 | 39 | public ISpanBuilder BuildSpan(string operationName) 40 | { 41 | throw new NotSupportedException(); 42 | } 43 | 44 | public ISpanContext Extract(IFormat format, TCarrier carrier) 45 | { 46 | throw new NotSupportedException(); 47 | } 48 | 49 | public void Inject(ISpanContext spanContext, IFormat format, TCarrier carrier) 50 | { 51 | throw new NotSupportedException(); 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/OpenTracing.Contrib.NetCore.Tests/OpenTracing.Contrib.NetCore.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1;net6.0;net7.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /test/OpenTracing.Contrib.NetCore.Tests/XunitLogging/XunitLoggerFactoryExtensions.cs: -------------------------------------------------------------------------------- 1 | // From https://github.com/aspnet/Logging/blob/dev/src/Microsoft.Extensions.Logging.Testing/XunitLoggerFactoryExtensions.cs 2 | 3 | using Microsoft.Extensions.DependencyInjection; 4 | using OpenTracing.Contrib.NetCore.Tests.XunitLogging; 5 | using Xunit.Abstractions; 6 | 7 | namespace Microsoft.Extensions.Logging 8 | { 9 | public static class XunitLoggerFactoryExtensions 10 | { 11 | public static ILoggingBuilder AddXunit(this ILoggingBuilder builder, ITestOutputHelper output) 12 | { 13 | builder.Services.AddSingleton(new XunitLoggerProvider(output)); 14 | return builder; 15 | } 16 | 17 | public static ILoggingBuilder AddXunit(this ILoggingBuilder builder, ITestOutputHelper output, LogLevel minLevel) 18 | { 19 | builder.Services.AddSingleton(new XunitLoggerProvider(output, minLevel)); 20 | return builder; 21 | } 22 | 23 | public static ILoggerFactory AddXunit(this ILoggerFactory loggerFactory, ITestOutputHelper output) 24 | { 25 | loggerFactory.AddProvider(new XunitLoggerProvider(output)); 26 | return loggerFactory; 27 | } 28 | 29 | public static ILoggerFactory AddXunit(this ILoggerFactory loggerFactory, ITestOutputHelper output, LogLevel minLevel) 30 | { 31 | loggerFactory.AddProvider(new XunitLoggerProvider(output, minLevel)); 32 | return loggerFactory; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/OpenTracing.Contrib.NetCore.Tests/XunitLogging/XunitLoggerProvider.cs: -------------------------------------------------------------------------------- 1 | // From https://github.com/aspnet/Logging/blob/dev/src/Microsoft.Extensions.Logging.Testing/XunitLoggerProvider.cs 2 | 3 | using System; 4 | using System.Linq; 5 | using System.Text; 6 | using Microsoft.Extensions.Logging; 7 | using Xunit.Abstractions; 8 | 9 | namespace OpenTracing.Contrib.NetCore.Tests.XunitLogging 10 | { 11 | public class XunitLoggerProvider : ILoggerProvider 12 | { 13 | private readonly ITestOutputHelper _output; 14 | private readonly LogLevel _minLevel; 15 | 16 | public XunitLoggerProvider(ITestOutputHelper output) 17 | : this(output, LogLevel.Trace) 18 | { 19 | } 20 | 21 | public XunitLoggerProvider(ITestOutputHelper output, LogLevel minLevel) 22 | { 23 | _output = output; 24 | _minLevel = minLevel; 25 | } 26 | 27 | public ILogger CreateLogger(string categoryName) 28 | { 29 | return new XunitLogger(_output, categoryName, _minLevel); 30 | } 31 | 32 | public void Dispose() 33 | { 34 | } 35 | } 36 | 37 | public class XunitLogger : ILogger 38 | { 39 | private static readonly string[] NewLineChars = new[] { Environment.NewLine }; 40 | private readonly string _category; 41 | private readonly LogLevel _minLogLevel; 42 | private readonly ITestOutputHelper _output; 43 | 44 | public XunitLogger(ITestOutputHelper output, string category, LogLevel minLogLevel) 45 | { 46 | _minLogLevel = minLogLevel; 47 | _category = category; 48 | _output = output; 49 | } 50 | 51 | public void Log( 52 | LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) 53 | { 54 | if (!IsEnabled(logLevel)) 55 | { 56 | return; 57 | } 58 | 59 | // Buffer the message into a single string in order to avoid shearing the message when running across multiple threads. 60 | var messageBuilder = new StringBuilder(); 61 | 62 | var firstLinePrefix = $"| {_category} {logLevel}: "; 63 | var lines = formatter(state, exception).Split(NewLineChars, StringSplitOptions.RemoveEmptyEntries); 64 | messageBuilder.AppendLine(firstLinePrefix + lines.First()); 65 | 66 | var additionalLinePrefix = "|" + new string(' ', firstLinePrefix.Length - 1); 67 | foreach (var line in lines.Skip(1)) 68 | { 69 | messageBuilder.AppendLine(additionalLinePrefix + line); 70 | } 71 | 72 | if (exception != null) 73 | { 74 | lines = exception.ToString().Split(NewLineChars, StringSplitOptions.RemoveEmptyEntries); 75 | additionalLinePrefix = "| "; 76 | foreach (var line in lines) 77 | { 78 | messageBuilder.AppendLine(additionalLinePrefix + line); 79 | } 80 | } 81 | 82 | // Remove the last line-break, because ITestOutputHelper only has WriteLine. 83 | var message = messageBuilder.ToString(); 84 | if (message.EndsWith(Environment.NewLine)) 85 | { 86 | message = message.Substring(0, message.Length - Environment.NewLine.Length); 87 | } 88 | 89 | try 90 | { 91 | _output.WriteLine(message); 92 | } 93 | catch (Exception) 94 | { 95 | // We could fail because we're on a background thread and our captured ITestOutputHelper is 96 | // busted (if the test "completed" before the background thread fired). 97 | // So, ignore this. There isn't really anything we can do but hope the 98 | // caller has additional loggers registered 99 | } 100 | } 101 | 102 | public bool IsEnabled(LogLevel logLevel) 103 | => logLevel >= _minLogLevel; 104 | 105 | public IDisposable BeginScope(TState state) 106 | => new NullScope(); 107 | 108 | private class NullScope : IDisposable 109 | { 110 | public void Dispose() 111 | { 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /version.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 16 | 99.99.99 17 | 18 | 19 | loc$([System.DateTime]::UtcNow.ToString('yyyyMMddHHmm')) 20 | 21 | 22 | ci$([System.Int32]::Parse($(APPVEYOR_BUILD_NUMBER)).ToString('D4')) 23 | 24 | 25 | $(APPVEYOR_REPO_TAG_NAME.TrimStart('v')) 26 | 27 | 28 | --------------------------------------------------------------------------------