();
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 |
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