diff --git a/.editorconfig b/.editorconfig index 9cd7b7c6..e2ad36a5 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,8 +8,6 @@ root = true # Use 4 spaces as indentation [*] insert_final_newline = true -indent_style = space -indent_size = 4 trim_trailing_whitespace = true [*.cs] @@ -17,5 +15,3 @@ trim_trailing_whitespace = true dotnet_diagnostic.IDE0073.severity = warning file_header_template = Copyright (c) TotalSoft.\nThis source code is licensed under the MIT license. -[{*.csproj, *.fsproj, *.props, *.xml}] -indent_size = 2 diff --git a/NBB.sln b/NBB.sln index 435efd58..ab050e36 100644 --- a/NBB.sln +++ b/NBB.sln @@ -216,7 +216,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NBB.EventStore.InMemory.Tes EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NBB.Application.DataContracts.Schema", "src\Application\NBB.Application.DataContracts.Schema\NBB.Application.DataContracts.Schema.csproj", "{B7F02268-98F8-4C73-9B3A-41250835F3FD}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NBB.Messaging.OpenTracing", "src\Messaging\NBB.Messaging.OpenTracing\NBB.Messaging.OpenTracing.csproj", "{B5C4D462-E7AF-4146-9618-75FF242E148C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NBB.Messaging.OpenTelemetry", "src\Messaging\NBB.Messaging.OpenTelemetry\NBB.Messaging.OpenTelemetry.csproj", "{B5C4D462-E7AF-4146-9618-75FF242E148C}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{F57779EB-9107-427D-A65C-5AA262C348E1}" ProjectSection(SolutionItems) = preProject @@ -311,7 +311,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProcessManagerSample", "sam EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{EF374BA2-61FC-41FB-8229-5187DCBAA63A}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NBB.Tools.Serilog.OpenTracingSink", "src\Tools\Serilog\NBB.Tools.Serilog.OpenTracingSink\NBB.Tools.Serilog.OpenTracingSink.csproj", "{A5826A9E-7E96-4A52-8A06-2B6DC9855954}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NBB.Tools.Serilog.OpenTelemetryTracingSink", "src\Tools\Serilog\NBB.Tools.Serilog.OpenTelemetryTracingSink\NBB.Tools.Serilog.OpenTelemetryTracingSink.csproj", "{A5826A9E-7E96-4A52-8A06-2B6DC9855954}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NBB.EventStore.AdoNet.MultiTenancy", "src\EventStore\NBB.EventStore.AdoNet.Multitenancy\NBB.EventStore.AdoNet.MultiTenancy.csproj", "{2FEDB78D-E355-4449-A9D3-1C3079E38FD9}" EndProject diff --git a/dependencies.props b/dependencies.props index 74a03152..69ebe2f4 100644 --- a/dependencies.props +++ b/dependencies.props @@ -16,17 +16,24 @@ 7.0.0 7.0.0 4.7.0 + 1.4.0-rc.1 + 1.4.0-rc.1 + 1.4.0-rc.1 + 1.0.0-rc9.10 + 1.0.0-beta.3 + 1.0.0-rc9.10 + 1.1.0-beta.2 + 1.4.0-rc.1 + 1.4.0-rc.1 7.2.2 2.10.0 3.1.0 4.0.1 5.6.1 4.1.0 - 12.0.0 - 0.12.1 + 12.0.0 10.6.6 4.17.0 - 0.8.0 1.0.3 6.15.0 diff --git a/samples/MicroServices/NBB.Contracts/NBB.Contracts.Api/Controllers/ContractsController.cs b/samples/MicroServices/NBB.Contracts/NBB.Contracts.Api/Controllers/ContractsController.cs index efe4552a..ea84134d 100644 --- a/samples/MicroServices/NBB.Contracts/NBB.Contracts.Api/Controllers/ContractsController.cs +++ b/samples/MicroServices/NBB.Contracts/NBB.Contracts.Api/Controllers/ContractsController.cs @@ -1,74 +1,78 @@ // Copyright (c) TotalSoft. // This source code is licensed under the MIT license. -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; using NBB.Contracts.PublishedLanguage; -using NBB.Contracts.ReadModel; -using NBB.Messaging.Abstractions; -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace NBB.Contracts.Api.Controllers -{ - [Route("api/[controller]")] - public class ContractsController : Controller - { - private readonly IQueryable _contractReadModelQuery; - private readonly IMessageBusPublisher _messageBusPublisher; - - public ContractsController(IMessageBusPublisher messageBusPublisher, IQueryable contractReadModelQuery) - { - _messageBusPublisher = messageBusPublisher; - _contractReadModelQuery = contractReadModelQuery; - } - - - // GET api/contracts - [HttpGet] - public async Task Get() - { - var query = await _contractReadModelQuery.ToListAsync(); - return Ok(query.ToList()); - } - - // GET api/contracts/7327223E-22EA-48DC-BC44-FFF6AB3B9489 - [HttpGet("{id}")] - public async Task Get(Guid id) - { - //var contract = await _contractReadModelRepository.GetFirstOrDefaultAsync(x=> x.ContractId == id, "ContractLines"); - var contract = await _contractReadModelQuery - .Include(x=> x.ContractLines) - .SingleOrDefaultAsync(x => x.ContractId == id, CancellationToken.None); - - if (contract != null) - return Ok(contract); - - return NotFound(); - } - - // POST api/contracts - [HttpPost] - public Task Post([FromBody]CreateContract command, CancellationToken cancellationToken) - { - return _messageBusPublisher.PublishAsync(command, cancellationToken); - } - - // POST api/contracts/7327223E-22EA-48DC-BC44-FFF6AB3B9489/lines - [HttpPost("{id}/lines")] - public Task Post([FromBody]AddContractLine command, CancellationToken cancellationToken) - { - return _messageBusPublisher.PublishAsync(command, cancellationToken); - } - - // POST api/contracts/7327223E-22EA-48DC-BC44-FFF6AB3B9489/validate - [HttpPost("{id}/validate")] - public Task Post([FromBody]ValidateContract command, CancellationToken cancellationToken) - { - return _messageBusPublisher.PublishAsync(command, cancellationToken); - } - - } -} +using NBB.Contracts.ReadModel; +using NBB.Messaging.Abstractions; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace NBB.Contracts.Api.Controllers +{ + [Route("api/[controller]")] + public class ContractsController : Controller + { + private readonly IQueryable _contractReadModelQuery; + private readonly ILogger _logger; + private readonly IMessageBusPublisher _messageBusPublisher; + + public ContractsController(IMessageBusPublisher messageBusPublisher, IQueryable contractReadModelQuery, ILogger logger) + { + _messageBusPublisher = messageBusPublisher; + _contractReadModelQuery = contractReadModelQuery; + _logger = logger; + } + + + // GET api/contracts + [HttpGet] + public async Task Get() + { + var query = await _contractReadModelQuery.ToListAsync(); + return Ok(query.ToList()); + } + + // GET api/contracts/7327223E-22EA-48DC-BC44-FFF6AB3B9489 + [HttpGet("{id}")] + public async Task Get(Guid id) + { + //var contract = await _contractReadModelRepository.GetFirstOrDefaultAsync(x=> x.ContractId == id, "ContractLines"); + var contract = await _contractReadModelQuery + .Include(x=> x.ContractLines) + .SingleOrDefaultAsync(x => x.ContractId == id, CancellationToken.None); + + if (contract != null) + return Ok(contract); + + return NotFound(); + } + + // POST api/contracts + [HttpPost] + public Task Post([FromBody]CreateContract command, CancellationToken cancellationToken) + { + return _messageBusPublisher.PublishAsync(command, cancellationToken); + } + + // POST api/contracts/7327223E-22EA-48DC-BC44-FFF6AB3B9489/lines + [HttpPost("{id}/lines")] + public Task Post([FromBody]AddContractLine command, CancellationToken cancellationToken) + { + return _messageBusPublisher.PublishAsync(command, cancellationToken); + } + + // POST api/contracts/7327223E-22EA-48DC-BC44-FFF6AB3B9489/validate + [HttpPost("{id}/validate")] + public Task Post([FromBody]ValidateContract command, CancellationToken cancellationToken) + { + _logger.LogInformation("Validating contract"); + return _messageBusPublisher.PublishAsync(command, cancellationToken); + } + + } +} diff --git a/samples/MicroServices/NBB.Contracts/NBB.Contracts.Api/Dockerfile b/samples/MicroServices/NBB.Contracts/NBB.Contracts.Api/Dockerfile index 18cdf68e..17d1fa3b 100644 --- a/samples/MicroServices/NBB.Contracts/NBB.Contracts.Api/Dockerfile +++ b/samples/MicroServices/NBB.Contracts/NBB.Contracts.Api/Dockerfile @@ -24,7 +24,7 @@ COPY ["src/Messaging/NBB.Messaging.Nats/NBB.Messaging.Nats.csproj", "src/Messagi COPY ["src/Correlation/NBB.Correlation.AspNet/NBB.Correlation.AspNet.csproj", "src/Correlation/NBB.Correlation.AspNet/"] COPY ["samples/MicroServices/NBB.Contracts/NBB.Contracts.PublishedLanguage/NBB.Contracts.PublishedLanguage.csproj", "samples/MicroServices/NBB.Contracts/NBB.Contracts.PublishedLanguage/"] COPY ["src/Messaging/NBB.Messaging.BackwardCompatibility/NBB.Messaging.BackwardCompatibility.csproj", "src/Messaging/NBB.Messaging.BackwardCompatibility/"] -COPY ["src/Messaging/NBB.Messaging.OpenTracing/NBB.Messaging.OpenTracing.csproj", "src/Messaging/NBB.Messaging.OpenTracing/"] +COPY ["src/Messaging/NBB.Messaging.OpenTelemetry/NBB.Messaging.OpenTelemetry.csproj", "src/Messaging/NBB.Messaging.OpenTelemetry/"] COPY ["samples/MicroServices/NBB.Contracts/NBB.Contracts.ReadModel.Data/NBB.Contracts.ReadModel.Data.csproj", "samples/MicroServices/NBB.Contracts/NBB.Contracts.ReadModel.Data/"] COPY ["samples/MicroServices/NBB.Contracts/NBB.Contracts.ReadModel/NBB.Contracts.ReadModel.csproj", "samples/MicroServices/NBB.Contracts/NBB.Contracts.ReadModel/"] COPY ["src/Data/NBB.Data.EntityFramework/NBB.Data.EntityFramework.csproj", "src/Data/NBB.Data.EntityFramework/"] diff --git a/samples/MicroServices/NBB.Contracts/NBB.Contracts.Api/NBB.Contracts.Api.csproj b/samples/MicroServices/NBB.Contracts/NBB.Contracts.Api/NBB.Contracts.Api.csproj index 09d54712..8b70415f 100644 --- a/samples/MicroServices/NBB.Contracts/NBB.Contracts.Api/NBB.Contracts.Api.csproj +++ b/samples/MicroServices/NBB.Contracts/NBB.Contracts.Api/NBB.Contracts.Api.csproj @@ -20,18 +20,29 @@ + + + + + + + + + + + + - - - + - - + + + diff --git a/samples/MicroServices/NBB.Contracts/NBB.Contracts.Api/Program.cs b/samples/MicroServices/NBB.Contracts/NBB.Contracts.Api/Program.cs index 735b8e8d..a6a2e4c5 100644 --- a/samples/MicroServices/NBB.Contracts/NBB.Contracts.Api/Program.cs +++ b/samples/MicroServices/NBB.Contracts/NBB.Contracts.Api/Program.cs @@ -3,6 +3,9 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; +using NBB.Correlation.Serilog; +using NBB.Tools.Serilog.OpenTelemetryTracingSink; +using Serilog; namespace NBB.Contracts.Api { @@ -15,6 +18,15 @@ public static void Main(string[] args) public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) + .UseSerilog((context, services, logConfig) => + { + logConfig + .ReadFrom.Configuration(context.Configuration) + .Enrich.FromLogContext() + .Enrich.With() + .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3} {TenantCode:u}] {Message:lj}{NewLine}{Exception}") + .WriteTo.OpenTelemetryTracing(); + }) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); diff --git a/samples/MicroServices/NBB.Contracts/NBB.Contracts.Api/Startup.cs b/samples/MicroServices/NBB.Contracts/NBB.Contracts.Api/Startup.cs index 4ad3fef4..85f2239d 100644 --- a/samples/MicroServices/NBB.Contracts/NBB.Contracts.Api/Startup.cs +++ b/samples/MicroServices/NBB.Contracts/NBB.Contracts.Api/Startup.cs @@ -1,10 +1,6 @@ // Copyright (c) TotalSoft. // This source code is licensed under the MIT license. -using Jaeger; -using Jaeger.Reporters; -using Jaeger.Samplers; -using Jaeger.Senders.Thrift; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; @@ -12,15 +8,16 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; using Microsoft.OpenApi.Models; using NBB.Contracts.ReadModel.Data; using NBB.Correlation.AspNet; -using NBB.Messaging.Abstractions; -using NBB.Messaging.OpenTracing.Publisher; -using OpenTracing; -using OpenTracing.Noop; -using OpenTracing.Util; +using NBB.Messaging.OpenTelemetry; +using OpenTelemetry; +using OpenTelemetry.Exporter; +using OpenTelemetry.Extensions.Propagators; +using OpenTelemetry.Metrics; +using OpenTelemetry.Resources; +using OpenTelemetry.Trace; using System; using System.Reflection; @@ -39,10 +36,7 @@ public Startup(IConfiguration configuration) public void ConfigureServices(IServiceCollection services) { services.AddMvc(); - services.AddSwaggerGen(c => - { - c.SwaggerDoc("v1", new OpenApiInfo { Title = "Contracts API", Version = "v1" }); - }); + services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "Contracts API", Version = "v1" }); }); services.AddSingleton(Configuration); services.TryAddSingleton(); @@ -70,39 +64,37 @@ public void ConfigureServices(IServiceCollection services) services.AddContractsReadModelDataAccess(); - services.Decorate(); + var assembly = Assembly.GetExecutingAssembly().GetName(); + void configureResource(ResourceBuilder r) => + r.AddService(assembly.Name, serviceVersion: assembly.Version?.ToString(), serviceInstanceId: Environment.MachineName); - // OpenTracing - //services.AddOpenTracingCoreServices(builder => builder - // .AddAspNetCore() - // .AddHttpHandler() - // .AddGenericDiagnostics(x => x.IgnoredListenerNames.Add("Grpc.Net.Client")) - // .AddLoggerProvider() - // .AddMicrosoftSqlClient()); + if (Configuration.GetValue("OpenTelemetry:TracingEnabled")) + { + Sdk.SetDefaultTextMapPropagator(new JaegerPropagator()); + + services.AddOpenTelemetryTracing(builder => builder + .ConfigureResource(configureResource) + .SetSampler(new AlwaysOnSampler()) + .AddMessageBusInstrumentation() + .AddHttpClientInstrumentation() + .AddAspNetCoreInstrumentation() + .AddEntityFrameworkCoreInstrumentation(options => options.SetDbStatementForText = true) + .AddJaegerExporter() + ); + services.Configure(Configuration.GetSection("OpenTelemetry:Jaeger")); + } - services.AddSingleton(serviceProvider => + if (Configuration.GetValue("OpenTelemetry:MetricsEnabled")) { - if (!Configuration.GetValue("OpenTracing:Jaeger:IsEnabled")) + services.AddOpenTelemetryMetrics(options => { - return NoopTracerFactory.Create(); - } - - string serviceName = Assembly.GetEntryAssembly().GetName().Name; - - ILoggerFactory loggerFactory = serviceProvider.GetRequiredService(); - - ITracer tracer = new Tracer.Builder(serviceName) - .WithLoggerFactory(loggerFactory) - .WithSampler(new ConstSampler(true)) - .WithReporter(new RemoteReporter.Builder() - .WithSender(new HttpSender(Configuration.GetValue("OpenTracing:Jaeger:CollectorUrl"))) - .Build()) - .Build(); - - GlobalTracer.Register(tracer); - - return tracer; - }); + options.ConfigureResource(configureResource) + .AddRuntimeInstrumentation() + .AddHttpClientInstrumentation() + .AddAspNetCoreInstrumentation() + .AddPrometheusExporter(); + }); + } } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -118,11 +110,11 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) } app.UseRouting(); - app.UseEndpoints(endpoints => - { - endpoints.MapControllers(); - }); + if (Configuration.GetValue("OpenTelemetry:MetricsEnabled")) + app.UseOpenTelemetryPrometheusScrapingEndpoint(); + + app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } } } diff --git a/samples/MicroServices/NBB.Contracts/NBB.Contracts.Api/appsettings.json b/samples/MicroServices/NBB.Contracts/NBB.Contracts.Api/appsettings.json index 9074c8ff..ea961e5f 100644 --- a/samples/MicroServices/NBB.Contracts/NBB.Contracts.Api/appsettings.json +++ b/samples/MicroServices/NBB.Contracts/NBB.Contracts.Api/appsettings.json @@ -1,35 +1,41 @@ -{ - "Logging": { - "IncludeScopes": false, - "LogLevel": { - "Default": "Information" - } - }, - "ConnectionStrings": { - "DefaultConnection": "Server=YOUR_SERVER;Database=NBB_Contracts;User Id=YOUR_USER;Password=YOUR_PASSWORD;MultipleActiveResultSets=true" - }, - "Messaging": { - "Env": "DEV", - "Source1": "Contracts.Api", - "TopicResolutionCompatibility": "NBB_5", // NBB_4 - "Transport": "NATS", // NATS Rusi - "Kafka": { - "bootstrap_servers": "YOUR_KAFKA_URL" - }, - "Nats": { - "natsUrl": "YOUR_NATS_URL", - "cluster": "faas-cluster", - "clientId": "NBB_Samples" - }, - "Rusi": { - "RusiPort": 50003, - "PubsubName": "natsstreaming-pubsub" - } - }, - "OpenTracing": { - "Jaeger": { - "IsEnabled": "true", - "Collectorurl": "YOUR_COLLECTOR_URL" - } - } -} +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting": "Information" + } + }, + "ConnectionStrings": { + "DefaultConnection": "Server=YOUR_SERVER;Database=NBB_Contracts;User Id=YOUR_USER;Password=YOUR_PASSWORD;MultipleActiveResultSets=true" + }, + "Messaging": { + "Env": "DEV", + "Source": "Contracts.Api", + "TopicResolutionCompatibility": "NBB_5", // NBB_4 + "Transport": "NATS", // NATS Rusi + "Kafka": { + "bootstrap_servers": "YOUR_KAFKA_URL" + }, + "Nats": { + "natsUrl": "YOUR_NATS_URL", + "cluster": "faas-cluster", + "clientId": "NBB_Samples" + }, + "Rusi": { + "RusiPort": 50003, + "PubsubName": "natsstreaming-pubsub" + } + }, + "OpenTelemetry": { + "MetricsEnabled": true, + "TracingEnabled": true, + "Jaeger": { + //"AgentHost": "localhost", + //"AgentPort": 6831, + //"Protocol": "UdpCompactThrift", + "Endpoint": "YOUR_COLLECTOR_URL", + "Protocol": "HttpBinaryThrift" + } + } +} diff --git a/samples/MicroServices/NBB.Contracts/NBB.Contracts.Application/CommandHandlers/ContractCommandHandlers.cs b/samples/MicroServices/NBB.Contracts/NBB.Contracts.Application/CommandHandlers/ContractCommandHandlers.cs index 52b662b9..18b2af11 100644 --- a/samples/MicroServices/NBB.Contracts/NBB.Contracts.Application/CommandHandlers/ContractCommandHandlers.cs +++ b/samples/MicroServices/NBB.Contracts/NBB.Contracts.Application/CommandHandlers/ContractCommandHandlers.cs @@ -1,51 +1,60 @@ // Copyright (c) TotalSoft. // This source code is licensed under the MIT license. -using MediatR; -using NBB.Contracts.Domain.ContractAggregate; +using MediatR; +using Microsoft.Extensions.Logging; +using NBB.Contracts.Domain.ContractAggregate; using NBB.Contracts.PublishedLanguage; -using NBB.Data.Abstractions; -using System.Threading; -using System.Threading.Tasks; - -namespace NBB.Contracts.Application.CommandHandlers -{ - public class ContractCommandHandlers : - IRequestHandler, - IRequestHandler, - IRequestHandler - { - private readonly IEventSourcedRepository _repository; - - public ContractCommandHandlers(IEventSourcedRepository repository) - { - this._repository = repository; - } - - public async Task Handle(CreateContract command, CancellationToken cancellationToken) - { - var contract = new Contract(command.ClientId); - await _repository.SaveAsync(contract, cancellationToken); - - return Unit.Value; - } - - public async Task Handle(AddContractLine command, CancellationToken cancellationToken) - { - var contract = await _repository.GetByIdAsync(command.ContractId, cancellationToken); - contract.AddContractLine(command.Product, command.Price, command.Quantity); - await _repository.SaveAsync(contract, cancellationToken); - - return Unit.Value; - } - - public async Task Handle(ValidateContract command, CancellationToken cancellationToken) - { - var contract = await _repository.GetByIdAsync(command.ContractId, cancellationToken); - contract.Validate(); - await _repository.SaveAsync(contract, cancellationToken); - - return Unit.Value; - } - } -} +using NBB.Data.Abstractions; +using System.Threading; +using System.Threading.Tasks; + +namespace NBB.Contracts.Application.CommandHandlers +{ + public class ContractCommandHandlers : + IRequestHandler, + IRequestHandler, + IRequestHandler + { + private readonly IEventSourcedRepository _repository; + private readonly ContractDomainMetrics _domainMetrics; + private readonly ILogger _logger; + + public ContractCommandHandlers(IEventSourcedRepository repository, ContractDomainMetrics domainMetrics, ILogger logger) + { + this._repository = repository; + _domainMetrics = domainMetrics; + _logger = logger; + } + + public async Task Handle(CreateContract command, CancellationToken cancellationToken) + { + var contract = new Contract(command.ClientId); + await _repository.SaveAsync(contract, cancellationToken); + _domainMetrics.ContractCreated(); + + return Unit.Value; + } + + public async Task Handle(AddContractLine command, CancellationToken cancellationToken) + { + var contract = await _repository.GetByIdAsync(command.ContractId, cancellationToken); + contract.AddContractLine(command.Product, command.Price, command.Quantity); + await _repository.SaveAsync(contract, cancellationToken); + + return Unit.Value; + } + + public async Task Handle(ValidateContract command, CancellationToken cancellationToken) + { + _logger.LogInformation("Validating contract"); + + var contract = await _repository.GetByIdAsync(command.ContractId, cancellationToken); + contract.Validate(); + await _repository.SaveAsync(contract, cancellationToken); + _domainMetrics.ContractValidated(); + + return Unit.Value; + } + } +} diff --git a/samples/MicroServices/NBB.Contracts/NBB.Contracts.Application/ContractDomainMetrics.cs b/samples/MicroServices/NBB.Contracts/NBB.Contracts.Application/ContractDomainMetrics.cs new file mode 100644 index 00000000..cb1a10fa --- /dev/null +++ b/samples/MicroServices/NBB.Contracts/NBB.Contracts.Application/ContractDomainMetrics.cs @@ -0,0 +1,44 @@ +// Copyright (c) TotalSoft. +// This source code is licensed under the MIT license. + +using System; +using System.Diagnostics.Metrics; +using System.Reflection; + +namespace NBB.Contracts.Application +{ + public class ContractDomainMetrics : IDisposable + { + private static readonly AssemblyName AssemblyName = Assembly.GetCallingAssembly().GetName(); + public static readonly string InstrumentationName = AssemblyName.Name; + private readonly Counter _contracts; + private readonly Counter _validatedContracts; + private readonly Meter _meter; + + public ContractDomainMetrics() + { + _meter = new Meter(AssemblyName.Name); + _contracts = _meter.CreateCounter("nbb.contracts.created.count"); + _validatedContracts = _meter.CreateCounter("nbb.contracts.validated.count"); + + _contracts.Add(1); + _contracts.Add(1); + + } + + public void ContractCreated() + { + _contracts.Add(1); + } + public void ContractValidated() + { + _validatedContracts.Add(1); + } + + /// + public void Dispose() + { + _meter?.Dispose(); + } + } +} diff --git a/samples/MicroServices/NBB.Contracts/NBB.Contracts.Application/DomainEventHandlers/ContractDomainEventHandlers.cs b/samples/MicroServices/NBB.Contracts/NBB.Contracts.Application/DomainEventHandlers/ContractDomainEventHandlers.cs index 3cd3fc12..cb92ca6a 100644 --- a/samples/MicroServices/NBB.Contracts/NBB.Contracts.Application/DomainEventHandlers/ContractDomainEventHandlers.cs +++ b/samples/MicroServices/NBB.Contracts/NBB.Contracts.Application/DomainEventHandlers/ContractDomainEventHandlers.cs @@ -1,28 +1,28 @@ // Copyright (c) TotalSoft. // This source code is licensed under the MIT license. -using System.Threading; -using System.Threading.Tasks; -using MediatR; -using NBB.Contracts.Domain.ContractAggregate; -using NBB.Messaging.Abstractions; - -namespace NBB.Contracts.Application.DomainEventHandlers -{ - public class ContractDomainEventHandlers : - INotificationHandler - { - private readonly IMessageBusPublisher _messageBusPublisher; - - public ContractDomainEventHandlers(IMessageBusPublisher messageBusPublisher) - { - _messageBusPublisher = messageBusPublisher; - } - - public Task Handle(ContractValidated domainEvent, CancellationToken cancellationToken) - { - return _messageBusPublisher.PublishAsync( - new PublishedLanguage.ContractValidated(domainEvent.ContractId, domainEvent.ClientId, domainEvent.Amount), cancellationToken); - } - } -} +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using NBB.Contracts.Domain.ContractAggregate; +using NBB.Messaging.Abstractions; + +namespace NBB.Contracts.Application.DomainEventHandlers +{ + public class ContractDomainEventHandlers : + INotificationHandler + { + private readonly IMessageBusPublisher _messageBusPublisher; + + public ContractDomainEventHandlers(IMessageBusPublisher messageBusPublisher) + { + _messageBusPublisher = messageBusPublisher; + } + + public Task Handle(ContractValidated domainEvent, CancellationToken cancellationToken) + { + return _messageBusPublisher.PublishAsync( + new PublishedLanguage.ContractValidated(domainEvent.ContractId, domainEvent.ClientId, domainEvent.Amount), cancellationToken); + } + } +} diff --git a/samples/MicroServices/NBB.Contracts/NBB.Contracts.Domain/ContractAggregate/Contract.cs b/samples/MicroServices/NBB.Contracts/NBB.Contracts.Domain/ContractAggregate/Contract.cs index e3d1e15d..1da70258 100644 --- a/samples/MicroServices/NBB.Contracts/NBB.Contracts.Domain/ContractAggregate/Contract.cs +++ b/samples/MicroServices/NBB.Contracts/NBB.Contracts.Domain/ContractAggregate/Contract.cs @@ -1,111 +1,111 @@ // Copyright (c) TotalSoft. // This source code is licensed under the MIT license. -using System; -using System.Collections.Generic; -using System.Linq; -using NBB.Contracts.Domain.ContractAggregate.Snapshots; -using NBB.Domain; - -namespace NBB.Contracts.Domain.ContractAggregate -{ - public class Contract : EventSourcedAggregateRoot - { - public Guid ContractId { get; private set; } - public decimal Amount { get; private set; } - - public Guid ClientId { get; private set; } - - public List ContractLines { get; private set; } = new List(); - - public bool IsValidated { get; private set; } - - - //needed 4 repository should be private - public Contract() - { - } - - public Contract(Guid clientId) - { - Emit(new ContractCreated(Guid.NewGuid(), clientId)); - } - - public override Guid GetIdentityValue() => ContractId; - - - public void AddContractLine(string product, decimal price, int quantity) - { - if (IsValidated) - throw new Exception("Contract is validated"); - - Emit(new ContractLineAdded(Guid.NewGuid(), this.ContractId, product, price, quantity)); - Emit(new ContractAmountUpdated(this.ContractId, this.Amount + price * quantity)); - } - - public void Validate() - { - if (IsValidated) - throw new Exception("Contract already validated"); - Emit(new ContractValidated(this.ContractId, this.ClientId, this.Amount)); - } - - protected override void SetMemento(ContractSnapshot memento) - { - Amount = memento.Amount; - ClientId = memento.ClientId; - ContractId = memento.ContractId; - IsValidated = memento.IsValidated; - ContractLines = memento.ContractLines.Select(x => - new ContractLine( - new Product(x.Product.Name, x.Product.Price), - x.Quantity, x.ContractId)) - .ToList(); - } - - protected override ContractSnapshot CreateMemento() - { - return new ContractSnapshot() - { - Amount = Amount, - ClientId = ClientId, - ContractId = ContractId, - IsValidated = IsValidated, - ContractLines = ContractLines.Select(x => - new ContractSnapshot.ContractLine - { - ContractId = x.ContractId, - ContractLineId = x.ContractLineId, - Quantity = x.Quantity, - Product = new ContractSnapshot.Product - { - Name = x.Product.Name, - Price = x.Product.Price - } - }).ToList() - }; - } - - private void Apply(ContractCreated e) - { - this.ContractId = e.ContractId; - this.ClientId = e.ClientId; - } - - private void Apply(ContractAmountUpdated e) - { - this.Amount = e.NewAmount; - } - - private void Apply(ContractLineAdded e) - { - var contractLine = new ContractLine(new Product(e.Product, e.Price), e.Quantity, this.ContractId); - this.ContractLines.Add(contractLine); - } - - private void Apply(ContractValidated e) - { - this.IsValidated = true; - } - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using System.Linq; +using NBB.Contracts.Domain.ContractAggregate.Snapshots; +using NBB.Domain; + +namespace NBB.Contracts.Domain.ContractAggregate +{ + public class Contract : EventSourcedAggregateRoot + { + public Guid ContractId { get; private set; } + public decimal Amount { get; private set; } + + public Guid ClientId { get; private set; } + + public List ContractLines { get; private set; } = new List(); + + public bool IsValidated { get; private set; } + + + //needed 4 repository should be private + public Contract() + { + } + + public Contract(Guid clientId) + { + Emit(new ContractCreated(Guid.NewGuid(), clientId)); + } + + public override Guid GetIdentityValue() => ContractId; + + + public void AddContractLine(string product, decimal price, int quantity) + { + if (IsValidated) + throw new Exception("Contract is validated"); + + Emit(new ContractLineAdded(Guid.NewGuid(), this.ContractId, product, price, quantity)); + Emit(new ContractAmountUpdated(this.ContractId, this.Amount + price * quantity)); + } + + public void Validate() + { + if (IsValidated) + throw new Exception("Contract already validated"); + Emit(new ContractValidated(this.ContractId, this.ClientId, this.Amount)); + } + + protected override void SetMemento(ContractSnapshot memento) + { + Amount = memento.Amount; + ClientId = memento.ClientId; + ContractId = memento.ContractId; + IsValidated = memento.IsValidated; + ContractLines = memento.ContractLines.Select(x => + new ContractLine( + new Product(x.Product.Name, x.Product.Price), + x.Quantity, x.ContractId)) + .ToList(); + } + + protected override ContractSnapshot CreateMemento() + { + return new ContractSnapshot() + { + Amount = Amount, + ClientId = ClientId, + ContractId = ContractId, + IsValidated = IsValidated, + ContractLines = ContractLines.Select(x => + new ContractSnapshot.ContractLine + { + ContractId = x.ContractId, + ContractLineId = x.ContractLineId, + Quantity = x.Quantity, + Product = new ContractSnapshot.Product + { + Name = x.Product.Name, + Price = x.Product.Price + } + }).ToList() + }; + } + + private void Apply(ContractCreated e) + { + this.ContractId = e.ContractId; + this.ClientId = e.ClientId; + } + + private void Apply(ContractAmountUpdated e) + { + this.Amount = e.NewAmount; + } + + private void Apply(ContractLineAdded e) + { + var contractLine = new ContractLine(new Product(e.Product, e.Price), e.Quantity, this.ContractId); + this.ContractLines.Add(contractLine); + } + + private void Apply(ContractValidated e) + { + this.IsValidated = true; + } + } +} diff --git a/samples/MicroServices/NBB.Contracts/NBB.Contracts.Worker/Dockerfile b/samples/MicroServices/NBB.Contracts/NBB.Contracts.Worker/Dockerfile index dc0da797..b1758e7f 100644 --- a/samples/MicroServices/NBB.Contracts/NBB.Contracts.Worker/Dockerfile +++ b/samples/MicroServices/NBB.Contracts/NBB.Contracts.Worker/Dockerfile @@ -38,7 +38,7 @@ COPY ["src/Messaging/NBB.Messaging.Nats/NBB.Messaging.Nats.csproj", "src/Messagi COPY ["src/EventStore/NBB.EventStore.AdoNet/NBB.EventStore.AdoNet.csproj", "src/EventStore/NBB.EventStore.AdoNet/"] COPY ["src/Correlation/NBB.Correlation.Serilog/NBB.Correlation.Serilog.csproj", "src/Correlation/NBB.Correlation.Serilog/"] COPY ["src/Messaging/NBB.Messaging.BackwardCompatibility/NBB.Messaging.BackwardCompatibility.csproj", "src/Messaging/NBB.Messaging.BackwardCompatibility/"] -COPY ["src/Messaging/NBB.Messaging.OpenTracing/NBB.Messaging.OpenTracing.csproj", "src/Messaging/NBB.Messaging.OpenTracing/"] +COPY ["src/Messaging/NBB.Messaging.OpenTelemetry/NBB.Messaging.OpenTelemetry.csproj", "src/Messaging/NBB.Messaging.OpenTelemetry/"] COPY ["samples/MicroServices/NBB.Contracts/NBB.Contracts.ReadModel.Data/NBB.Contracts.ReadModel.Data.csproj", "samples/MicroServices/NBB.Contracts/NBB.Contracts.ReadModel.Data/"] COPY ["src/Data/NBB.Data.EntityFramework/NBB.Data.EntityFramework.csproj", "src/Data/NBB.Data.EntityFramework/"] RUN dotnet restore "samples/MicroServices/NBB.Contracts/NBB.Contracts.Worker/NBB.Contracts.Worker.csproj" diff --git a/samples/MicroServices/NBB.Contracts/NBB.Contracts.Worker/NBB.Contracts.Worker.csproj b/samples/MicroServices/NBB.Contracts/NBB.Contracts.Worker/NBB.Contracts.Worker.csproj index 5920e781..12dd8bfe 100644 --- a/samples/MicroServices/NBB.Contracts/NBB.Contracts.Worker/NBB.Contracts.Worker.csproj +++ b/samples/MicroServices/NBB.Contracts/NBB.Contracts.Worker/NBB.Contracts.Worker.csproj @@ -1,62 +1,70 @@  - - SAK - SAK - SAK - SAK - - - - Exe - net7.0 - NBB_Contracts_6a73f87d-2175-4be0-9a42-31cb73bc8e10 - Linux - ..\..\..\.. - - - - 1701;1702;1705;NU1701 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Always - - - Always - - - + + SAK + SAK + SAK + SAK + + + + Exe + net7.0 + NBB_Contracts_6a73f87d-2175-4be0-9a42-31cb73bc8e10 + Linux + ..\..\..\.. + + + + 1701;1702;1705;NU1701 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Always + + + Always + + + diff --git a/samples/MicroServices/NBB.Contracts/NBB.Contracts.Worker/Program.cs b/samples/MicroServices/NBB.Contracts/NBB.Contracts.Worker/Program.cs index 5deb1550..4800da05 100644 --- a/samples/MicroServices/NBB.Contracts/NBB.Contracts.Worker/Program.cs +++ b/samples/MicroServices/NBB.Contracts/NBB.Contracts.Worker/Program.cs @@ -1,32 +1,30 @@ // Copyright (c) TotalSoft. // This source code is licensed under the MIT license. -using Jaeger; -using Jaeger.Reporters; -using Jaeger.Samplers; -using Jaeger.Senders.Thrift; using MediatR; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; +using NBB.Contracts.Application; using NBB.Contracts.Application.CommandHandlers; using NBB.Contracts.ReadModel.Data; using NBB.Contracts.WriteModel.Data; using NBB.Correlation.Serilog; using NBB.Domain; -using NBB.Messaging.Abstractions; using NBB.Messaging.Host; -using NBB.Messaging.OpenTracing.Publisher; -using OpenTracing; -using OpenTracing.Noop; -using OpenTracing.Util; +using NBB.Messaging.OpenTelemetry; +using OpenTelemetry; +using OpenTelemetry.Exporter; +using OpenTelemetry.Extensions.Propagators; +using OpenTelemetry.Metrics; +using OpenTelemetry.Resources; +using OpenTelemetry.Trace; using Serilog; -using Serilog.Events; -using Serilog.Sinks.MSSqlServer; using System; -using System.Reflection; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection.Extensions; +using System.Reflection; +using NBB.Tools.Serilog.OpenTelemetryTracingSink; namespace NBB.Contracts.Worker { @@ -36,23 +34,14 @@ public static async Task Main(string[] args) { var builder = Host .CreateDefaultBuilder(args) - .ConfigureLogging((hostingContext, loggingBuilder) => + .UseSerilog((context, services, logConfig) => { - var env = hostingContext.HostingEnvironment.IsDevelopment(); - var connectionString = hostingContext.Configuration.GetConnectionString("Logs"); - - Log.Logger = new LoggerConfiguration() - .MinimumLevel.Debug() - .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) + logConfig + .ReadFrom.Configuration(context.Configuration) .Enrich.FromLogContext() .Enrich.With() - .WriteTo.MSSqlServer(connectionString, - new MSSqlServerSinkOptions { TableName = "Logs", AutoCreateSqlTable = true }) - .CreateLogger(); - - loggingBuilder.AddSerilog(dispose: true); - loggingBuilder.AddFilter("Microsoft", logLevel => logLevel >= LogLevel.Warning); - loggingBuilder.AddConsole(); + .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3} {TenantCode:u}] {Message:lj}{NewLine}{Exception}") + .WriteTo.OpenTelemetryTracing(); }) .ConfigureServices((hostingContext, services) => { @@ -89,43 +78,52 @@ public static async Task Main(string[] args) b.UseAdoNetEventRepository(o => o.FromConfiguration()); }); - services.Decorate(); services.AddMessagingHost(hostingContext.Configuration, hostBuilder => hostBuilder.UseStartup()); - // OpenTracing - //services.AddOpenTracingCoreServices(builder => builder.AddGenericDiagnostics().AddMicrosoftSqlClient()); + var assembly = Assembly.GetExecutingAssembly().GetName(); + void configureResource(ResourceBuilder r) => + r.AddService(assembly.Name, serviceVersion: assembly.Version?.ToString(), serviceInstanceId: Environment.MachineName); + if (hostingContext.Configuration.GetValue("OpenTelemetry:TracingEnabled")) + { + Sdk.SetDefaultTextMapPropagator(new JaegerPropagator()); + + services.AddOpenTelemetryTracing(builder => builder + .ConfigureResource(configureResource) + .SetSampler(new AlwaysOnSampler()) + .AddMessageBusInstrumentation() + .AddEntityFrameworkCoreInstrumentation(options => options.SetDbStatementForText = true) + .AddJaegerExporter() + ); + services.Configure(hostingContext.Configuration.GetSection("OpenTelemetry:Jaeger")); + } - services.AddSingleton(serviceProvider => + if (hostingContext.Configuration.GetValue("OpenTelemetry:MetricsEnabled")) { - if (!hostingContext.Configuration.GetValue("OpenTracing:Jaeger:IsEnabled")) + services.AddOpenTelemetryMetrics(options => { - return NoopTracerFactory.Create(); - } - - string serviceName = Assembly.GetEntryAssembly().GetName().Name; - - ILoggerFactory loggerFactory = serviceProvider.GetRequiredService(); - - ITracer tracer = new Tracer.Builder(serviceName) - .WithLoggerFactory(loggerFactory) - .WithSampler(new ConstSampler(true)) - .WithReporter(new RemoteReporter.Builder() - .WithSender(new HttpSender(hostingContext.Configuration.GetValue("OpenTracing:Jaeger:CollectorUrl"))) - .Build()) - .Build(); - - GlobalTracer.Register(tracer); - - return tracer; - }); - + options.ConfigureResource(configureResource) + .AddRuntimeInstrumentation() + .AddPrometheusHttpListener(); + AddContractMetrics(options); + }); + } + else + { + services.TryAddSingleton(); + } }); var host = builder.Build(); await host.RunAsync(); } + + public static MeterProviderBuilder AddContractMetrics(MeterProviderBuilder builder) + { + builder.AddMeter(ContractDomainMetrics.InstrumentationName); + return builder.AddInstrumentation(); + } } class MessagingHostStartup : IMessagingHostStartup @@ -141,7 +139,6 @@ public Task Configure(IMessagingHostConfigurationBuilder hostConfigurationBuilde .UsePipeline(pipelineBuilder => pipelineBuilder .UseCorrelationMiddleware() .UseExceptionHandlingMiddleware() - .UseOpenTracingMiddleware() .UseDefaultResiliencyMiddleware() .UseMediatRMiddleware() ); diff --git a/samples/MicroServices/NBB.Contracts/NBB.Contracts.Worker/appsettings.json b/samples/MicroServices/NBB.Contracts/NBB.Contracts.Worker/appsettings.json index 86600956..3d4ad970 100644 --- a/samples/MicroServices/NBB.Contracts/NBB.Contracts.Worker/appsettings.json +++ b/samples/MicroServices/NBB.Contracts/NBB.Contracts.Worker/appsettings.json @@ -1,47 +1,49 @@ { - "ConnectionStrings": { - "DefaultConnection": "Server=YOUR_SERVER;Database=NBB_Contracts;User Id=YOUR_USER;Password=YOUR_PASSWORD;MultipleActiveResultSets=true", - "Logs": "Server=YOUR_SERVER_URL;Database=NBB_Logs;User Id=YOUR_USER_NAME;Password=YOUR_PASSWORD;MultipleActiveResultSets=true" + "ConnectionStrings": { + "DefaultConnection": "Server=YOUR_SERVER;Database=NBB_Contracts;User Id=YOUR_USER;Password=YOUR_PASSWORD;MultipleActiveResultSets=true", + "Logs": "Server=YOUR_SERVER_URL;Database=NBB_Logs;User Id=YOUR_USER_NAME;Password=YOUR_PASSWORD;MultipleActiveResultSets=true" + }, + "Messaging": { + "Env": "DEV", + "Source": "Contracts.Worker", + "Transport": "NATS", // NATS Rusi + "TopicResolutionCompatibility": "NBB_5", // NBB_4 + "Host": { + "TransportErrorStrategy": "Retry", + "StartRetryCount": 10 }, - "Messaging": { - "Env": "DEV", - "Source": "Contracts.Worker", - "Transport": "NATS", // NATS Rusi - "TopicResolutionCompatibility": "NBB_5", // NBB_4 - "Host": { - "TransportErrorStrategy": "Retry", - "StartRetryCount": 10 - }, - "Kafka": { - "bootstrap_servers": "YOUR_KAFKA_URL", - "group_id": "NBB.Contracts.Worker" - }, - "Nats": { - "natsUrl": "YOUR_NATS_URL", - "cluster": "faas-cluster", - "clientId": "NBB_Samples", - "qGroup": "NBB.Contracts.Worker", - "durableName": "durable" - }, - "Rusi": { - "RusiPort": 50003, - "PubsubName": "natsstreaming-pubsub" - } + "Kafka": { + "bootstrap_servers": "YOUR_KAFKA_URL", + "group_id": "NBB.Contracts.Worker" }, - "EventStore": { - "NBB": { - "ConnectionString": "Server=YOUR_SERVER;Database=NBB_Contracts;User Id=YOUR_USER;Password=YOUR_PASSWORD;MultipleActiveResultSets=true" - }, - "GetEventStore": { - }, - "SqlStreamStore": { - "ConnectionString": "Server=YOUR_SERVER;Database=NBB_Contracts;User Id=YOUR_USER;Password=YOUR_PASSWORD;MultipleActiveResultSets=true" - } + "Nats": { + "natsUrl": "YOUR_NATS_URL", + "cluster": "faas-cluster", + "clientId": "NBB_Samples", + "qGroup": "NBB.Contracts.Worker", + "durableName": "durable" }, - "OpenTracing": { - "Jaeger": { - "IsEnabled": "True", - "Collectorurl": "YOUR_COLLECTOR_URL" - } + "Rusi": { + "RusiPort": 50003, + "PubsubName": "natsstreaming-pubsub" } + }, + "EventStore": { + "NBB": { + "ConnectionString": "Server=YOUR_SERVER;Database=NBB_Contracts;User Id=YOUR_USER;Password=YOUR_PASSWORD;MultipleActiveResultSets=true" + }, + "GetEventStore": { + }, + "SqlStreamStore": { + "ConnectionString": "Server=YOUR_SERVER;Database=NBB_Contracts;User Id=YOUR_USER;Password=YOUR_PASSWORD;MultipleActiveResultSets=true" + } + }, + "OpenTelemetry": { + "MetricsEnabled": true, + "TracingEnabled": true, + "Jaeger": { + "Endpoint": "YOUR_COLLECTOR_URL", + "Protocol": "HttpBinaryThrift" + } + } } diff --git a/samples/MicroServices/NBB.Payments/NBB.Payments.Worker/appsettings.json b/samples/MicroServices/NBB.Payments/NBB.Payments.Worker/appsettings.json index 3c46a950..0fae0770 100644 --- a/samples/MicroServices/NBB.Payments/NBB.Payments.Worker/appsettings.json +++ b/samples/MicroServices/NBB.Payments/NBB.Payments.Worker/appsettings.json @@ -7,7 +7,7 @@ "Env": "DEV", "Source": "Payments.Worker", "Kafka": { - "bootstrap_servers": "kube-worker1", + "bootstrap_servers": "YOUR_KAFKA_URL", "group_id": "NBB.Payments.Worker" }, "Nats": { diff --git a/samples/MultiTenancy/NBB.Todo.Api/NBB.Todo.Api.csproj b/samples/MultiTenancy/NBB.Todo.Api/NBB.Todo.Api.csproj index 9e029aae..4d4374fe 100644 --- a/samples/MultiTenancy/NBB.Todo.Api/NBB.Todo.Api.csproj +++ b/samples/MultiTenancy/NBB.Todo.Api/NBB.Todo.Api.csproj @@ -12,7 +12,15 @@ - + + + + + + + + + @@ -21,9 +29,11 @@ + + diff --git a/samples/MultiTenancy/NBB.Todo.Api/Program.cs b/samples/MultiTenancy/NBB.Todo.Api/Program.cs index 79f1a862..462a1ba2 100644 --- a/samples/MultiTenancy/NBB.Todo.Api/Program.cs +++ b/samples/MultiTenancy/NBB.Todo.Api/Program.cs @@ -7,6 +7,7 @@ using NBB.Correlation.Serilog; using Microsoft.Extensions.DependencyInjection; using NBB.Tools.Serilog.Enrichers.TenantId; +using NBB.Tools.Serilog.OpenTelemetryTracingSink; namespace NBB.Todo.Api { @@ -28,8 +29,9 @@ public static IHostBuilder CreateHostBuilder(string[] args) => .ReadFrom.Configuration(context.Configuration) .Enrich.FromLogContext() .Enrich.With() - .Enrich.With(services.GetRequiredService()); - logConfig.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3} {TenantCode:u}] {Message:lj}{NewLine}{Exception}"); + .Enrich.With(services.GetRequiredService()) + .WriteTo.OpenTelemetryTracing() + .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3} {TenantCode:u}] {Message:lj}{NewLine}{Exception}"); }) .ConfigureWebHostDefaults(webBuilder => { diff --git a/samples/MultiTenancy/NBB.Todo.Api/Startup.cs b/samples/MultiTenancy/NBB.Todo.Api/Startup.cs index 64295b60..135c90a7 100644 --- a/samples/MultiTenancy/NBB.Todo.Api/Startup.cs +++ b/samples/MultiTenancy/NBB.Todo.Api/Startup.cs @@ -11,11 +11,19 @@ using Microsoft.Extensions.Hosting; using NBB.Correlation; using NBB.Correlation.AspNet; +using NBB.Messaging.OpenTelemetry; using NBB.MultiTenancy.Abstractions.Repositories; using NBB.MultiTenancy.AspNet; using NBB.Todos.Data; using NBB.Tools.Serilog.Enrichers.TenantId; +using OpenTelemetry; +using OpenTelemetry.Exporter; +using OpenTelemetry.Extensions.Propagators; +using OpenTelemetry.Metrics; +using OpenTelemetry.Resources; +using OpenTelemetry.Trace; using System; +using System.Reflection; using ProblemDetailsOptions = Hellang.Middleware.ProblemDetails.ProblemDetailsOptions; namespace NBB.Todo.Api @@ -53,6 +61,38 @@ public void ConfigureServices(IServiceCollection services) services.AddProblemDetails(config => ConfigureProblemDetails(config)); + var assembly = Assembly.GetExecutingAssembly().GetName(); + void configureResource(ResourceBuilder r) => + r.AddService(assembly.Name, serviceVersion: assembly.Version?.ToString(), serviceInstanceId: Environment.MachineName); + + if (Configuration.GetValue("OpenTelemetry:TracingEnabled")) + { + Sdk.SetDefaultTextMapPropagator(new JaegerPropagator()); + + services.AddOpenTelemetryTracing(builder => builder + .ConfigureResource(configureResource) + .SetSampler(new AlwaysOnSampler()) + .AddHttpClientInstrumentation() + .AddAspNetCoreInstrumentation() + .AddMessageBusInstrumentation() + .AddEntityFrameworkCoreInstrumentation(options => options.SetDbStatementForText = true) + .AddJaegerExporter() + ); + services.Configure(Configuration.GetSection("OpenTelemetry:Jaeger")); + } + + if (Configuration.GetValue("OpenTelemetry:MetricsEnabled")) + { + services.AddOpenTelemetryMetrics(options => + { + options.ConfigureResource(configureResource) + .AddRuntimeInstrumentation() + .AddHttpClientInstrumentation() + .AddAspNetCoreInstrumentation() + .AddPrometheusExporter(); + }); + } + } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/samples/MultiTenancy/NBB.Todo.Api/appsettings.json b/samples/MultiTenancy/NBB.Todo.Api/appsettings.json index 065c6b40..5ba8d6dc 100644 --- a/samples/MultiTenancy/NBB.Todo.Api/appsettings.json +++ b/samples/MultiTenancy/NBB.Todo.Api/appsettings.json @@ -1,79 +1,87 @@ { - "ConnectionStrings": { - "DefaultConnection": "Server=YOUR_SERVER;Database=NBB_Invoices;User Id=YOUR_USER;Password=YOUR_PASSWORD;MultipleActiveResultSets=true", - "Log_Database": "Server=YOUR_SERVER;Database=NBB_Invoices;User Id=YOUR_USER;Password=YOUR_PASSWORD;MultipleActiveResultSets=true" + "ConnectionStrings": { + "DefaultConnection": "Server=YOUR_SERVER;Database=NBB_Invoices;User Id=YOUR_USER;Password=YOUR_PASSWORD;MultipleActiveResultSets=true", + "Log_Database": "Server=YOUR_SERVER;Database=NBB_Invoices;User Id=YOUR_USER;Password=YOUR_PASSWORD;MultipleActiveResultSets=true" + }, + "Serilog": { + "Properties": { + "ServiceName": "NBB.Todo.Api" }, - "Serilog": { - "Properties": { - "ServiceName": "NBB.Todo.Api" - }, - "MinimumLevel": { - "Default": "Debug", - "Override": { - "Microsoft": "Information", - "OpenTracing": "Warning", - "System.Net": "Warning" - } - }, - "WriteTo": [ - { - "Name": "MSSqlServer", - "Args": { - "connectionString": "Log_Database", - "sinkOptionsSection": { - "tableName": "__Logs", - "schemaName": "dbo", - "autoCreateSqlTable": true, - "batchPostingLimit": 5000 - }, - //"restrictedToMinimumLevel": "Information", - "columnOptionsSection": { - "addStandardColumns": [ "LogEvent" ], - "removeStandardColumns": [ "MessageTemplate", "Properties" ], - "additionalColumns": [ - { - "ColumnName": "ServiceName", - "DataType": "varchar", - "DataLength": 200 - }, - { - "ColumnName": "TenantId", - "DataType": "UniqueIdentifier" - }, - { - "ColumnName": "CorrelationId", - "DataType": "UniqueIdentifier" - } - ] - } - } - } - ] + "MinimumLevel": { + "Default": "Debug", + "Override": { + "Microsoft": "Information", + "OpenTelemetry": "Warning", + "System.Net": "Warning" + } }, - - "Messaging": { - "Env": "DEV", - "Source": "Todo.Api", - "Nats": { - "natsUrl": "YOUR_NATS_URL", - "cluster": "faas-cluster", - "clientId": "NBB_Samples" + "WriteTo": [ + { + "Name": "MSSqlServer", + "Args": { + "connectionString": "Log_Database", + "sinkOptionsSection": { + "tableName": "__Logs", + "schemaName": "dbo", + "autoCreateSqlTable": true, + "batchPostingLimit": 5000 + }, + //"restrictedToMinimumLevel": "Information", + "columnOptionsSection": { + "addStandardColumns": [ "LogEvent" ], + "removeStandardColumns": [ "MessageTemplate", "Properties" ], + "additionalColumns": [ + { + "ColumnName": "ServiceName", + "DataType": "varchar", + "DataLength": 200 + }, + { + "ColumnName": "TenantId", + "DataType": "UniqueIdentifier" + }, + { + "ColumnName": "CorrelationId", + "DataType": "UniqueIdentifier" + } + ] + } } + } + ] + }, + + "Messaging": { + "Env": "DEV", + "Source": "Todo.Api", + "Nats": { + "natsUrl": "YOUR_NATS_URL", + "cluster": "faas-cluster", + "clientId": "NBB_Samples" + } + }, + "MultiTenancy": { + "TenancyType": "MultiTenant", // "MultiTenant" "MonoTenant" + "Defaults": { + "ConnectionStrings": { + "DefaultConnection": "Server=YOUR_SERVER;Database=NBB_Invoices;User Id=YOUR_USER;Password=YOUR_PASSWORD;MultipleActiveResultSets=true" + } }, - "MultiTenancy": { - "TenancyType": "MultiTenant", // "MultiTenant" "MonoTenant" - "Defaults": { - "ConnectionStrings": { - "DefaultConnection": "Server=YOUR_SERVER;Database=NBB_Invoices;User Id=YOUR_USER;Password=YOUR_PASSWORD;MultipleActiveResultSets=true" - } - }, - "Tenants": { - "tenant1": { - "TenantId": "f7bfa571-4067-4167-a4c5-dafb71ccdcf7" - }, - "tenant2": { - "TenantId": "a7bfa571-4067-4167-a4c5-dafb71ccdcf7" - } - } + "Tenants": { + "tenant1": { + "TenantId": "f7bfa571-4067-4167-a4c5-dafb71ccdcf7" + }, + "tenant2": { + "TenantId": "a7bfa571-4067-4167-a4c5-dafb71ccdcf7" + } + } + }, + "OpenTelemetry": { + "MetricsEnabled": true, + "TracingEnabled": true, + "Jaeger": { + "Endpoint": "YOUR_COLLECTOR_URL", + "Protocol": "HttpBinaryThrift" } + } } diff --git a/samples/MultiTenancy/NBB.Todo.Worker/NBB.Todo.Worker.csproj b/samples/MultiTenancy/NBB.Todo.Worker/NBB.Todo.Worker.csproj index 294ef933..52600b6f 100644 --- a/samples/MultiTenancy/NBB.Todo.Worker/NBB.Todo.Worker.csproj +++ b/samples/MultiTenancy/NBB.Todo.Worker/NBB.Todo.Worker.csproj @@ -25,6 +25,13 @@ + + + + + + + @@ -34,11 +41,11 @@ - + - + diff --git a/samples/MultiTenancy/NBB.Todo.Worker/Program.cs b/samples/MultiTenancy/NBB.Todo.Worker/Program.cs index bc4f1995..8bbee54b 100644 --- a/samples/MultiTenancy/NBB.Todo.Worker/Program.cs +++ b/samples/MultiTenancy/NBB.Todo.Worker/Program.cs @@ -12,11 +12,19 @@ using NBB.Todo.Worker.Application; using NBB.Messaging.Host; using NBB.Messaging.MultiTenancy; +using NBB.Messaging.OpenTelemetry; using NBB.MultiTenancy.Abstractions.Repositories; -using Serilog.Events; -using Microsoft.Extensions.Logging; using NBB.Correlation.Serilog; using NBB.Tools.Serilog.Enrichers.TenantId; +using Microsoft.Extensions.Configuration; +using OpenTelemetry; +using OpenTelemetry.Extensions.Propagators; +using OpenTelemetry.Exporter; +using OpenTelemetry.Resources; +using System.Reflection; +using OpenTelemetry.Trace; +using OpenTelemetry.Metrics; +using NBB.Tools.Serilog.OpenTelemetryTracingSink; namespace NBB.Todo.Worker { @@ -55,6 +63,7 @@ public static IHost BuildConsoleHost(string[] args) => .Enrich.FromLogContext() .Enrich.With() .Enrich.With(services.GetRequiredService()) + .WriteTo.OpenTelemetryTracing() .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3} {TenantCode:u}] {Message:lj}{NewLine}{Exception}"); }) .UseConsoleLifetime() @@ -96,6 +105,37 @@ private static void ConfigureServices(HostBuilderContext hostingContext, IServic .AddTenantRepository(); services.AddSingleton(); + + + var assembly = Assembly.GetExecutingAssembly().GetName(); + void configureResource(ResourceBuilder r) => + r.AddService(assembly.Name, serviceVersion: assembly.Version?.ToString(), serviceInstanceId: Environment.MachineName); + + + if (hostingContext.Configuration.GetValue("OpenTelemetry:TracingEnabled")) + { + Sdk.SetDefaultTextMapPropagator(new JaegerPropagator()); + + services.AddOpenTelemetryTracing(builder => builder + .ConfigureResource(configureResource) + .SetSampler(new AlwaysOnSampler()) + .AddMessageBusInstrumentation() + .AddEntityFrameworkCoreInstrumentation(options => options.SetDbStatementForText = true) + .AddJaegerExporter() + ); + services.Configure(hostingContext.Configuration.GetSection("OpenTelemetry:Jaeger")); + } + + + if (hostingContext.Configuration.GetValue("OpenTelemetry:MetricsEnabled")) + { + services.AddOpenTelemetryMetrics(options => + { + options.ConfigureResource(configureResource) + .AddRuntimeInstrumentation() + .AddPrometheusHttpListener(); + }); + } } } } diff --git a/samples/MultiTenancy/NBB.Todo.Worker/appsettings.json b/samples/MultiTenancy/NBB.Todo.Worker/appsettings.json index fa662978..f48d4608 100644 --- a/samples/MultiTenancy/NBB.Todo.Worker/appsettings.json +++ b/samples/MultiTenancy/NBB.Todo.Worker/appsettings.json @@ -1,80 +1,88 @@ { - "ConnectionStrings": { - "DefaultConnection": "Server=YOUR_SERVER;Database=NBB_Invoices;User Id=YOUR_USER;Password=YOUR_PASSWORD;MultipleActiveResultSets=true", - "Log_Database": "Server=YOUR_SERVER;Database=NBB_Invoices;User Id=YOUR_USER;Password=YOUR_PASSWORD;MultipleActiveResultSets=true" + "ConnectionStrings": { + "DefaultConnection": "Server=YOUR_SERVER;Database=NBB_Invoices;User Id=YOUR_USER;Password=YOUR_PASSWORD;MultipleActiveResultSets=true", + "Log_Database": "Server=YOUR_SERVER;Database=NBB_Invoices;User Id=YOUR_USER;Password=YOUR_PASSWORD;MultipleActiveResultSets=true" + }, + "Serilog": { + "Properties": { + "ServiceName": "NBB.Todo.Worker" }, - "Serilog": { - "Properties": { - "ServiceName": "NBB.Todo.Worker" - }, - "MinimumLevel": { - "Default": "Debug", - "Override": { - "Microsoft": "Information", - "OpenTracing": "Warning", - "System.Net": "Warning" - } - }, - "WriteTo": [ - { - "Name": "MSSqlServer", - "Args": { - "connectionString": "Log_Database", - "sinkOptionsSection": { - "tableName": "__Logs", - "schemaName": "dbo", - "autoCreateSqlTable": true, - "batchPostingLimit": 5000 - }, - //"restrictedToMinimumLevel": "Information", - "columnOptionsSection": { - "addStandardColumns": [ "LogEvent" ], - "removeStandardColumns": [ "MessageTemplate", "Properties" ], - "additionalColumns": [ - { - "ColumnName": "ServiceName", - "DataType": "varchar", - "DataLength": 200 - }, - { - "ColumnName": "TenantId", - "DataType": "UniqueIdentifier" - }, - { - "ColumnName": "CorrelationId", - "DataType": "UniqueIdentifier" - } - ] - } - } - } - ] + "MinimumLevel": { + "Default": "Debug", + "Override": { + "Microsoft": "Information", + "OpenTelemetry": "Warning", + "System.Net": "Warning" + } }, - "Messaging": { - "Env": "DEV", - "Source": "NBB.Todo.Worker", - "Nats": { - "natsUrl": "YOUR_NATS_URL", - "cluster": "faas-cluster", - "clientId": "NBB.Todo.Worker", - "qGroup": "NBB.Todo.Worker", - "durableName": "durable" + "WriteTo": [ + { + "Name": "MSSqlServer", + "Args": { + "connectionString": "Log_Database", + "sinkOptionsSection": { + "tableName": "__Logs", + "schemaName": "dbo", + "autoCreateSqlTable": true, + "batchPostingLimit": 5000 + }, + //"restrictedToMinimumLevel": "Information", + "columnOptionsSection": { + "addStandardColumns": [ "LogEvent" ], + "removeStandardColumns": [ "MessageTemplate", "Properties" ], + "additionalColumns": [ + { + "ColumnName": "ServiceName", + "DataType": "varchar", + "DataLength": 200 + }, + { + "ColumnName": "TenantId", + "DataType": "UniqueIdentifier" + }, + { + "ColumnName": "CorrelationId", + "DataType": "UniqueIdentifier" + } + ] + } } + } + ] + }, + "Messaging": { + "Env": "DEV", + "Source": "NBB.Todo.Worker", + "Nats": { + "natsUrl": "YOUR_NATS_URL", + "cluster": "faas-cluster", + "clientId": "NBB.Todo.Worker", + "qGroup": "NBB.Todo.Worker", + "durableName": "durable" + } + }, + "MultiTenancy": { + "TenancyType": "MultiTenant", // "MultiTenant" "MonoTenant", + "Defaults": { + "ConnectionStrings": { + "DefaultConnection": "Server=YOUR_SERVER;Database=NBB_Invoices;User Id=YOUR_USER;Password=YOUR_PASSWORD;MultipleActiveResultSets=true" + } }, - "MultiTenancy": { - "TenancyType": "MultiTenant", // "MultiTenant" "MonoTenant", - "Defaults": { - "ConnectionStrings": { - "DefaultConnection": "Server=YOUR_SERVER;Database=NBB_Invoices;User Id=YOUR_USER;Password=YOUR_PASSWORD;MultipleActiveResultSets=true" - } - }, - "Tenants": { - "tenant1": { - "TenantId": "f7bfa571-4067-4167-a4c5-dafb71ccdcf7" - }, - "tenant2": { - "TenantId": "a7bfa571-4067-4167-a4c5-dafb71ccdcf7" - } - } + "Tenants": { + "tenant1": { + "TenantId": "f7bfa571-4067-4167-a4c5-dafb71ccdcf7" + }, + "tenant2": { + "TenantId": "a7bfa571-4067-4167-a4c5-dafb71ccdcf7" + } + } + }, + "OpenTelemetry": { + "MetricsEnabled": true, + "TracingEnabled": true, + "Jaeger": { + "Endpoint": "YOUR_COLLECTOR_URL", + "Protocol": "HttpBinaryThrift" } + } } diff --git a/samples/Orchestration/ProcessManagerSample/ProcessManagerSample.csproj b/samples/Orchestration/ProcessManagerSample/ProcessManagerSample.csproj index a3c09a86..1565d354 100644 --- a/samples/Orchestration/ProcessManagerSample/ProcessManagerSample.csproj +++ b/samples/Orchestration/ProcessManagerSample/ProcessManagerSample.csproj @@ -31,7 +31,6 @@ - diff --git a/samples/Orchestration/ProcessManagerSample/Startup.cs b/samples/Orchestration/ProcessManagerSample/Startup.cs index c4838b71..bc412a4a 100644 --- a/samples/Orchestration/ProcessManagerSample/Startup.cs +++ b/samples/Orchestration/ProcessManagerSample/Startup.cs @@ -43,7 +43,6 @@ public static void ConfigureServicesDelegate(HostBuilderContext context, IServic .UsePipeline(builder => builder .UseCorrelationMiddleware() .UseExceptionHandlingMiddleware() - //.UseOpenTracingMiddleware() .UseDefaultResiliencyMiddleware() .UseMiddleware() .UseMediatRMiddleware()) diff --git a/src/Core/NBB.Core.Abstractions/ActivityExtensions.cs b/src/Core/NBB.Core.Abstractions/ActivityExtensions.cs new file mode 100644 index 00000000..8402808b --- /dev/null +++ b/src/Core/NBB.Core.Abstractions/ActivityExtensions.cs @@ -0,0 +1,182 @@ +// Copyright (c) TotalSoft. +// This source code is licensed under the MIT license. + +using System; +using System.Diagnostics; +using System.Globalization; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace NBB.Core.Abstractions +{ + public static class ActivityExtensions + { + /// + /// Sets the status of activity execution. + /// Activity class in .NET does not support 'Status'. + /// This extension provides a workaround to store Status as special tags with key name of otel.status_code and otel.status_description. + /// Read more about SetStatus here https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#set-status. + /// + /// Activity instance. + /// Activity execution status. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SetStatus(this Activity activity, Status status) + { + Debug.Assert(activity != null, "Activity should not be null"); + + activity.SetTag(SpanAttributeConstants.StatusCodeKey, StatusHelper.GetTagValueForStatusCode(status.StatusCode)); + activity.SetTag(SpanAttributeConstants.StatusDescriptionKey, status.Description); + } + + /// + /// Adds an activity event containing information from the specified exception. + /// + /// Activity instance. + /// Exception to be recorded. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SetException(this Activity activity, Exception ex) + { + activity?.SetException(ex, default); + } + + /// + /// Adds an activity event containing information from the specified exception and additional tags. + /// + /// Activity instance. + /// Exception to be recorded. + /// Additional tags to record on the event. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SetException(this Activity activity, Exception ex, in TagList tags) + { + if (ex == null || activity == null) + { + return; + } + + var tagsCollection = new ActivityTagsCollection + { + { SemanticConventions.AttributeExceptionType, ex.GetType().FullName }, + { SemanticConventions.AttributeExceptionStacktrace, ex.ToInvariantString() }, + }; + + if (!string.IsNullOrWhiteSpace(ex.Message)) + { + tagsCollection.Add(SemanticConventions.AttributeExceptionMessage, ex.Message); + } + + foreach (var tag in tags) + { + tagsCollection[tag.Key] = tag.Value; + } + + activity.AddEvent(new ActivityEvent(SemanticConventions.AttributeExceptionEventName, default, tagsCollection)); + } + } + + + /// + /// Canonical result code of span execution. + /// + public enum StatusCode + { + /// + /// The default status. + /// + Unset = 0, + + /// + /// The operation completed successfully. + /// + Ok = 1, + + /// + /// The operation contains an error. + /// + Error = 2, + } + + /// + /// Defines well-known span attribute keys. + /// + internal static class SpanAttributeConstants + { + public const string StatusCodeKey = "otel.status_code"; + public const string StatusDescriptionKey = "otel.status_description"; + public const string DatabaseStatementTypeKey = "db.statement_type"; + } + + /// + /// Span execution status. + /// + public readonly record struct Status(StatusCode StatusCode, string Description = null) + { + /// + /// The operation completed successfully. + /// + public static readonly Status Ok = new(StatusCode.Ok); + + /// + /// The default status. + /// + public static readonly Status Unset = new(StatusCode.Unset); + + /// + /// The operation contains an error. + /// + public static readonly Status Error = new(StatusCode.Error); + } + internal static class StatusHelper + { + public const string UnsetStatusCodeTagValue = "UNSET"; + public const string OkStatusCodeTagValue = "OK"; + public const string ErrorStatusCodeTagValue = "ERROR"; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string GetTagValueForStatusCode(StatusCode statusCode) + { + return statusCode switch + { + /* + * Note: Order here does matter for perf. Unset is + * first because assumption is most spans will be + * Unset, then Error. Ok is not set by the SDK. + */ + StatusCode.Unset => UnsetStatusCodeTagValue, + StatusCode.Error => ErrorStatusCodeTagValue, + StatusCode.Ok => OkStatusCodeTagValue, + _ => null, + }; + } + } + + internal static class SemanticConventions { + public const string AttributeExceptionEventName = "exception"; + public const string AttributeExceptionType = "exception.type"; + public const string AttributeExceptionMessage = "exception.message"; + public const string AttributeExceptionStacktrace = "exception.stacktrace"; + } + + internal static class ExceptionExtensions + { + /// + /// Returns a culture-independent string representation of the given object, + /// appropriate for diagnostics tracing. + /// + /// Exception to convert to string. + /// Exception as string with no culture. + public static string ToInvariantString(this Exception exception) + { + var originalUICulture = Thread.CurrentThread.CurrentUICulture; + + try + { + Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; + return exception.ToString(); + } + finally + { + Thread.CurrentThread.CurrentUICulture = originalUICulture; + } + } + } +} diff --git a/src/Correlation/README.md b/src/Correlation/README.md index 750f7d18..3fa95d28 100644 --- a/src/Correlation/README.md +++ b/src/Correlation/README.md @@ -23,7 +23,7 @@ There is a dedicated messaging header **nbb-correlationID** interpreted by: * [`message bus publisher`](../Messaging/NBB.Messaging.Abstractions#publish) - the publisher automatically adds the current correlation ID to the envelope headers * [`messaging host`](../Messaging/NBB.Messaging.Host#readme) - there is a built-in [`correlation middleware `](../Messaging/NBB.Messaging.Host#built-in-correlation-middleware) that reads the messaging header and sets the current correlation ID -### Open Tracing integration +### Open Telemetry integration A tag named **nbb.correlation_id** is added to the messaging publisher and subscriber spans. -For details see [`NBB.Messaging.OpenTracing`](./../Messaging/NBB.Messaging.OpenTracing#readme) \ No newline at end of file +For details see [`NBB.Messaging.OpenTelemetry`](./../Messaging/NBB.Messaging.OpenTelemetry#readme) diff --git a/src/Messaging/NBB.Messaging.Abstractions/MessageBus.cs b/src/Messaging/NBB.Messaging.Abstractions/MessageBus.cs index bb5f09c5..7babeb37 100644 --- a/src/Messaging/NBB.Messaging.Abstractions/MessageBus.cs +++ b/src/Messaging/NBB.Messaging.Abstractions/MessageBus.cs @@ -50,4 +50,4 @@ Task HandleMessage(MessagingEnvelope msg) return await tcs.Task; } } -} \ No newline at end of file +} diff --git a/src/Messaging/NBB.Messaging.OpenTracing/MessagingTags.cs b/src/Messaging/NBB.Messaging.Abstractions/TracingTags.cs similarity index 54% rename from src/Messaging/NBB.Messaging.OpenTracing/MessagingTags.cs rename to src/Messaging/NBB.Messaging.Abstractions/TracingTags.cs index 5a38fea6..cc9d1f72 100644 --- a/src/Messaging/NBB.Messaging.OpenTracing/MessagingTags.cs +++ b/src/Messaging/NBB.Messaging.Abstractions/TracingTags.cs @@ -1,12 +1,19 @@ // Copyright (c) TotalSoft. // This source code is licensed under the MIT license. -namespace NBB.Messaging.OpenTracing -{ - public static class MessagingTags - { - public const string ComponentMessaging = "NBB.Messaging"; - public const string CorrelationId = "nbb.correlation_id"; - public const string MessagingEnvelopeHeaderSpanTagPrefix = "messaging_header."; - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NBB.Messaging.Abstractions +{ + public static class TracingTags + { + public const string ComponentMessaging = "NBB.Messaging"; + public const string CorrelationId = "nbb.correlation_id"; + public const string TenantId = "nbb.tenant_id"; + public const string MessagingEnvelopeHeaderSpanTagPrefix = "messaging_header."; + } +} diff --git a/src/Messaging/NBB.Messaging.Host/MessagingPipeline/CorrelationMiddleware.cs b/src/Messaging/NBB.Messaging.Host/MessagingPipeline/CorrelationMiddleware.cs index ba21559f..f214c32b 100644 --- a/src/Messaging/NBB.Messaging.Host/MessagingPipeline/CorrelationMiddleware.cs +++ b/src/Messaging/NBB.Messaging.Host/MessagingPipeline/CorrelationMiddleware.cs @@ -2,11 +2,12 @@ // This source code is licensed under the MIT license. using NBB.Core.Pipeline; -using NBB.Correlation; using NBB.Messaging.Abstractions; using System; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; +using CorrelationManager = NBB.Correlation.CorrelationManager; // ReSharper disable once CheckNamespace namespace NBB.Messaging.Host @@ -21,6 +22,8 @@ public async Task Invoke(MessagingContext context, CancellationToken cancellatio { using (CorrelationManager.NewCorrelationId(context.MessagingEnvelope.GetCorrelationId())) { + Activity.Current?.SetTag(TracingTags.CorrelationId, CorrelationManager.GetCorrelationId()?.ToString()); + await next(); } } diff --git a/src/Messaging/NBB.Messaging.Host/MessagingPipeline/ExceptionHandlingMiddleware.cs b/src/Messaging/NBB.Messaging.Host/MessagingPipeline/ExceptionHandlingMiddleware.cs index 2d53c922..543fa95b 100644 --- a/src/Messaging/NBB.Messaging.Host/MessagingPipeline/ExceptionHandlingMiddleware.cs +++ b/src/Messaging/NBB.Messaging.Host/MessagingPipeline/ExceptionHandlingMiddleware.cs @@ -48,6 +48,9 @@ public async Task Invoke(MessagingContext context, CancellationToken cancellatio "An unhandled exception has occurred while processing message of type {MessageType}.", context.MessagingEnvelope.Payload.GetType().GetPrettyName()); + Activity.Current?.SetException(ex); + Activity.Current?.SetStatus(Status.Error); + _deadLetterQueue.Push(context.MessagingEnvelope, context.TopicName, ex); } finally diff --git a/src/Messaging/NBB.Messaging.Host/README.md b/src/Messaging/NBB.Messaging.Host/README.md index 0305bc0a..7710c778 100644 --- a/src/Messaging/NBB.Messaging.Host/README.md +++ b/src/Messaging/NBB.Messaging.Host/README.md @@ -138,7 +138,6 @@ services.AddMessagingHost( .UsePipeline(builder => builder .UseCorrelationMiddleware() .UseExceptionHandlingMiddleware() - .UseOpenTracing() .UseDefaultResiliencyMiddleware() .UseMiddleware()); })); @@ -353,16 +352,6 @@ Includes the following resiliency policies for incoming messages: ``` Tipically configured last in the pipeline, it acts as a message dispatcher (broker) that delivers messages to MediatR handlers -#### built-in OpenTracing middleware - -```csharp -.UsePipeline(pipelineBuilder => pipelineBuilder.UseOpenTracing()) -``` - -Typically configured early in the pipeline, it creates an OpenTracing span for incoming message processing and correlates it with the publisher span. - -To use it you must reference the *NBB.Messaging.OpenTracing* package - #### built-in Multi Tenant middleware ```csharp diff --git a/src/Messaging/NBB.Messaging.MultiTenancy/MultiTenancyMessageBusPublisherDecorator.cs b/src/Messaging/NBB.Messaging.MultiTenancy/MultiTenancyMessageBusPublisherDecorator.cs index 27d0756b..779eb651 100644 --- a/src/Messaging/NBB.Messaging.MultiTenancy/MultiTenancyMessageBusPublisherDecorator.cs +++ b/src/Messaging/NBB.Messaging.MultiTenancy/MultiTenancyMessageBusPublisherDecorator.cs @@ -1,6 +1,7 @@ // Copyright (c) TotalSoft. // This source code is licensed under the MIT license. +using System.Diagnostics; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -42,6 +43,8 @@ public async Task PublishAsync(T message, MessagingPublisherOptions options = void NewCustomizer(MessagingEnvelope outgoingEnvelope) { + Activity.Current?.SetTag(TracingTags.TenantId, tenantId); + outgoingEnvelope.SetHeader(MessagingHeaders.TenantId, tenantId.ToString()); options.EnvelopeCustomizer?.Invoke(outgoingEnvelope); } diff --git a/src/Messaging/NBB.Messaging.MultiTenancy/TenantMiddleware.cs b/src/Messaging/NBB.Messaging.MultiTenancy/TenantMiddleware.cs index 27c0ba03..5f4614ab 100644 --- a/src/Messaging/NBB.Messaging.MultiTenancy/TenantMiddleware.cs +++ b/src/Messaging/NBB.Messaging.MultiTenancy/TenantMiddleware.cs @@ -12,6 +12,7 @@ using NBB.MultiTenancy.Abstractions.Repositories; using NBB.MultiTenancy.Identification.Services; using NBB.MultiTenancy.Abstractions; +using System.Diagnostics; namespace NBB.Messaging.MultiTenancy { @@ -55,6 +56,8 @@ public async Task Invoke(MessagingContext context, CancellationToken cancellatio _tenantContextAccessor.TenantContext = new TenantContext(tenant); + Activity.Current?.SetTag(TracingTags.TenantId, tenantId); + await next(); } } diff --git a/src/Messaging/NBB.Messaging.OpenTelemetry/MessagingActivitySource.cs b/src/Messaging/NBB.Messaging.OpenTelemetry/MessagingActivitySource.cs new file mode 100644 index 00000000..34f87eb1 --- /dev/null +++ b/src/Messaging/NBB.Messaging.OpenTelemetry/MessagingActivitySource.cs @@ -0,0 +1,22 @@ +// Copyright (c) TotalSoft. +// This source code is licensed under the MIT license. + +using NBB.Messaging.OpenTelemetry.Publisher; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace NBB.Messaging.OpenTelemetry +{ + public static class MessagingActivitySource + { + private static readonly AssemblyName assemblyName = typeof(OpenTelemetryPublisherDecorator).Assembly.GetName(); + private static readonly ActivitySource activitySource = new(assemblyName.Name, assemblyName.Version.ToString()); + + public static ActivitySource Current => activitySource; + } +} diff --git a/src/Messaging/NBB.Messaging.OpenTracing/NBB.Messaging.OpenTracing.csproj b/src/Messaging/NBB.Messaging.OpenTelemetry/NBB.Messaging.OpenTelemetry.csproj similarity index 55% rename from src/Messaging/NBB.Messaging.OpenTracing/NBB.Messaging.OpenTracing.csproj rename to src/Messaging/NBB.Messaging.OpenTelemetry/NBB.Messaging.OpenTelemetry.csproj index df67de05..4a08e84e 100644 --- a/src/Messaging/NBB.Messaging.OpenTracing/NBB.Messaging.OpenTracing.csproj +++ b/src/Messaging/NBB.Messaging.OpenTelemetry/NBB.Messaging.OpenTelemetry.csproj @@ -1,17 +1,20 @@ - - - - net7.0 - - - - - - - - - - - - - + + + + net7.0 + + + + + + + + + + + + + + + + diff --git a/src/Messaging/NBB.Messaging.OpenTelemetry/Publisher/OpenTelemetryPublisherDecorator.cs b/src/Messaging/NBB.Messaging.OpenTelemetry/Publisher/OpenTelemetryPublisherDecorator.cs new file mode 100644 index 00000000..d7093c7c --- /dev/null +++ b/src/Messaging/NBB.Messaging.OpenTelemetry/Publisher/OpenTelemetryPublisherDecorator.cs @@ -0,0 +1,71 @@ +// Copyright (c) TotalSoft. +// This source code is licensed under the MIT license. + +using NBB.Messaging.Abstractions; +using OpenTelemetry; +using OpenTelemetry.Trace; +using OpenTelemetry.Context.Propagation; +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using System.Reflection; +using NBB.Core.Abstractions; + +namespace NBB.Messaging.OpenTelemetry.Publisher +{ + public class OpenTelemetryPublisherDecorator : IMessageBusPublisher + { + private readonly IMessageBusPublisher _inner; + private readonly ITopicRegistry _topicRegistry; + + private static readonly ActivitySource activitySource = MessagingActivitySource.Current; + private static readonly TextMapPropagator propagator = Propagators.DefaultTextMapPropagator; + + public OpenTelemetryPublisherDecorator(IMessageBusPublisher inner, ITopicRegistry topicRegistry) + { + _inner = inner; + _topicRegistry = topicRegistry; + } + + public async Task PublishAsync(T message, MessagingPublisherOptions options = null, + CancellationToken cancellationToken = default) + { + options ??= MessagingPublisherOptions.Default; + + void NewCustomizer(MessagingEnvelope outgoingEnvelope) + { + + if (Activity.Current != null) + { + var contextToInject = Activity.Current.Context; + propagator.Inject(new PropagationContext(contextToInject, Baggage.Current), outgoingEnvelope.Headers, (headers, key, value) => headers[key] = value); + } + + options.EnvelopeCustomizer?.Invoke(outgoingEnvelope); + } + + var formattedTopicName = _topicRegistry.GetTopicForName(options.TopicName) ?? + _topicRegistry.GetTopicForMessageType(message.GetType()); + var operationName = $"{message.GetType().GetPrettyName()} send"; + + using var activity = activitySource.StartActivity(operationName, ActivityKind.Producer); + + activity?.SetTag(TraceSemanticConventions.AttributeMessagingDestination, formattedTopicName); + activity?.SetTag(TracingTags.CorrelationId, Correlation.CorrelationManager.GetCorrelationId()?.ToString()); + + try + { + await _inner.PublishAsync(message, options with { EnvelopeCustomizer = NewCustomizer }, + cancellationToken); + + } + catch (Exception exception) + { + activity?.SetStatus(ActivityStatusCode.Error, exception.Message); + activity?.RecordException(exception); + throw; + } + } + } +} diff --git a/src/Messaging/NBB.Messaging.OpenTracing/README.md b/src/Messaging/NBB.Messaging.OpenTelemetry/README.md similarity index 76% rename from src/Messaging/NBB.Messaging.OpenTracing/README.md rename to src/Messaging/NBB.Messaging.OpenTelemetry/README.md index 2c2b1639..e098f61f 100644 --- a/src/Messaging/NBB.Messaging.OpenTracing/README.md +++ b/src/Messaging/NBB.Messaging.OpenTelemetry/README.md @@ -1,18 +1,18 @@ -# NBB.Messaging.OpenTracing +# NBB.Messaging.OpenTelemetry -This package provides open tracing support in the messaging publishers and subscribers. +This package provides open telemetry support in the messaging publishers and subscribers. ## NuGet install ``` -dotnet add package NBB.Messaging.OpenTracing +dotnet add package NBB.Messaging.OpenTelemetry ``` ## Publisher To enable creating a publisher span, register the following publisher decorator in DI. ```csharp -services.Decorate(); +services.Decorate(); ``` The span is tagged with **span.kind** = "producer", **component** = "NBB.Messaging", **message_bus.destination** = the topic for the published message, **nbb.correlation_id** = the current correlation ID @@ -25,7 +25,7 @@ To enable creating a subscriber span, add the following middleware in the messag { pipelineBuilder ... - .UseOpenTracing() + .UseOpenTelemetry() ... }); ``` diff --git a/src/Messaging/NBB.Messaging.OpenTelemetry/Subscriber/OpenTelemetrySubscriberDecorator.cs b/src/Messaging/NBB.Messaging.OpenTelemetry/Subscriber/OpenTelemetrySubscriberDecorator.cs new file mode 100644 index 00000000..f3fafaa0 --- /dev/null +++ b/src/Messaging/NBB.Messaging.OpenTelemetry/Subscriber/OpenTelemetrySubscriberDecorator.cs @@ -0,0 +1,68 @@ +// Copyright (c) TotalSoft. +// This source code is licensed under the MIT license. + +using NBB.Messaging.Abstractions; +using OpenTelemetry.Trace; +using OpenTelemetry; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using OpenTelemetry.Context.Propagation; +using NBB.Core.Abstractions; +using NBB.Messaging.OpenTelemetry.Publisher; +using System.Reflection.PortableExecutable; + +namespace NBB.Messaging.OpenTelemetry.Subscriber +{ + public class OpenTelemetrySubscriberDecorator : IMessageBusSubscriber + { + private readonly IMessageBusSubscriber _inner; + + private static readonly ActivitySource activitySource = MessagingActivitySource.Current; + private static readonly TextMapPropagator propagator = Propagators.DefaultTextMapPropagator; + + public OpenTelemetrySubscriberDecorator(IMessageBusSubscriber inner) + { + _inner = inner; + } + + public Task SubscribeAsync(Func, Task> handler, MessagingSubscriberOptions options = null, CancellationToken cancellationToken = default) + { + async Task NewHandler(MessagingEnvelope incommingEnvelope) + { + var parentContext = propagator.Extract(default, incommingEnvelope.Headers, + (headers, key) => headers.TryGetValue(key, out var value) ? new[] { value } : Enumerable.Empty()); + Baggage.Current = parentContext.Baggage; + + string activityName = $"{incommingEnvelope.Payload.GetType().GetPrettyName()} receive"; + + using var activity = activitySource.StartActivity(activityName, ActivityKind.Consumer, parentContext.ActivityContext); + activity?.SetTag(TraceSemanticConventions.AttributeMessagingDestination, options.TopicName); + activity?.SetTag(TraceSemanticConventions.AttributePeerService, incommingEnvelope.Headers.TryGetValue(MessagingHeaders.Source, out var value) + ? value + : default); + + foreach (var header in incommingEnvelope.Headers) + activity?.SetTag(TracingTags.MessagingEnvelopeHeaderSpanTagPrefix + header.Key.ToLower(), header.Value); + + try + { + await handler?.Invoke(incommingEnvelope); + } + catch (Exception exception) + { + activity?.SetStatus(ActivityStatusCode.Error, exception.Message); + activity?.RecordException(exception); + throw; + } + } + + return _inner.SubscribeAsync((Func, Task>)NewHandler, options, cancellationToken); + } + } +} diff --git a/src/Messaging/NBB.Messaging.OpenTelemetry/TracerProviderBuilderExtensions.cs b/src/Messaging/NBB.Messaging.OpenTelemetry/TracerProviderBuilderExtensions.cs new file mode 100644 index 00000000..cee6af48 --- /dev/null +++ b/src/Messaging/NBB.Messaging.OpenTelemetry/TracerProviderBuilderExtensions.cs @@ -0,0 +1,33 @@ +// Copyright (c) TotalSoft. +// This source code is licensed under the MIT license. + +using NBB.Messaging.OpenTelemetry.Subscriber; +using OpenTelemetry.Trace; +using System; +using Microsoft.Extensions.DependencyInjection; +using NBB.Messaging.Abstractions; +using NBB.Messaging.OpenTelemetry.Publisher; + +namespace NBB.Messaging.OpenTelemetry +{ + public static class TracerProviderBuilderExtensions + { + public static TracerProviderBuilder AddMessageBusInstrumentation(this TracerProviderBuilder builder) + { + if (builder is null) + { + throw new ArgumentNullException(nameof(builder), "Must not be null"); + } + + builder + .AddSource(MessagingActivitySource.Current.Name) + .ConfigureServices(services => + { + services.Decorate(); + services.Decorate(); + }); + + return builder; + } + } +} diff --git a/src/Messaging/NBB.Messaging.OpenTracing/Publisher/OpenTracingPublisherDecorator.cs b/src/Messaging/NBB.Messaging.OpenTracing/Publisher/OpenTracingPublisherDecorator.cs deleted file mode 100644 index dfee8554..00000000 --- a/src/Messaging/NBB.Messaging.OpenTracing/Publisher/OpenTracingPublisherDecorator.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) TotalSoft. -// This source code is licensed under the MIT license. - -using NBB.Core.Abstractions; -using NBB.Correlation; -using NBB.Messaging.Abstractions; -using OpenTracing; -using OpenTracing.Propagation; -using OpenTracing.Tag; -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace NBB.Messaging.OpenTracing.Publisher -{ - public class OpenTracingPublisherDecorator : IMessageBusPublisher - { - private readonly IMessageBusPublisher _inner; - private readonly ITracer _tracer; - private readonly ITopicRegistry _topicRegistry; - - public OpenTracingPublisherDecorator(IMessageBusPublisher inner, ITracer tracer, ITopicRegistry topicRegistry) - { - _inner = inner; - _tracer = tracer; - _topicRegistry = topicRegistry; - } - - public Task PublishAsync(T message, MessagingPublisherOptions options = null, - CancellationToken cancellationToken = default) - { - options ??= MessagingPublisherOptions.Default; - - void NewCustomizer(MessagingEnvelope outgoingEnvelope) - { - if (_tracer.ActiveSpan != null) - { - _tracer.Inject(_tracer.ActiveSpan.Context, BuiltinFormats.TextMap, - new TextMapInjectAdapter(outgoingEnvelope.Headers)); - } - - options.EnvelopeCustomizer?.Invoke(outgoingEnvelope); - - if (_tracer.ActiveSpan != null) - { - foreach (var header in outgoingEnvelope.Headers) - _tracer.ActiveSpan.SetTag(MessagingTags.MessagingEnvelopeHeaderSpanTagPrefix + header.Key.ToLower(), header.Value); - } - } - - var formattedTopicName = _topicRegistry.GetTopicForName(options.TopicName) ?? - _topicRegistry.GetTopicForMessageType(message.GetType()); - var operationName = $"Publisher {message.GetType().GetPrettyName()}"; - - using var scope = _tracer.BuildSpan(operationName) - .WithTag(Tags.Component, MessagingTags.ComponentMessaging) - .WithTag(Tags.SpanKind, Tags.SpanKindProducer) - .WithTag(Tags.MessageBusDestination, formattedTopicName) - .WithTag(MessagingTags.CorrelationId, CorrelationManager.GetCorrelationId()?.ToString()) - .WithTag(Tags.SamplingPriority, 1) - .StartActive(true); - try - { - return _inner.PublishAsync(message, options with { EnvelopeCustomizer = NewCustomizer }, - cancellationToken); - } - catch (Exception exception) - { - scope.Span.SetException(exception); - throw; - } - } - } -} diff --git a/src/Messaging/NBB.Messaging.OpenTracing/SpanExtensions.cs b/src/Messaging/NBB.Messaging.OpenTracing/SpanExtensions.cs deleted file mode 100644 index adca3ed5..00000000 --- a/src/Messaging/NBB.Messaging.OpenTracing/SpanExtensions.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) TotalSoft. -// This source code is licensed under the MIT license. - -using System; -using System.Collections.Generic; -using OpenTracing; -using OpenTracing.Tag; - -namespace NBB.Messaging.OpenTracing -{ - public static class SpanExtensions - { - public static void SetException(this ISpan span, Exception exception) - { - span.Log(new Dictionary(3) - { - {LogFields.Event, Tags.Error.Key}, - {LogFields.ErrorKind, exception.GetType().Name}, - {LogFields.ErrorObject, exception} - }); - - span.SetTag(Tags.Error, true); - } - } -} diff --git a/src/Messaging/NBB.Messaging.OpenTracing/Subscriber/MessagingPipelineExtensions.cs b/src/Messaging/NBB.Messaging.OpenTracing/Subscriber/MessagingPipelineExtensions.cs deleted file mode 100644 index 5298a263..00000000 --- a/src/Messaging/NBB.Messaging.OpenTracing/Subscriber/MessagingPipelineExtensions.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) TotalSoft. -// This source code is licensed under the MIT license. - -using NBB.Core.Pipeline; -using NBB.Messaging.Abstractions; -using NBB.Messaging.OpenTracing.Subscriber; - -// ReSharper disable once CheckNamespace -namespace NBB.Messaging.Host; - -public static class MessagingPipelineExtensions -{ - /// - /// Adds to the pipeline a middleware that creates an OpenTracing span. - /// - /// The pipeline builder. - /// The pipeline builder for further configuring the pipeline. It is used used in the fluent configuration API. - public static IPipelineBuilder UseOpenTracingMiddleware( - this IPipelineBuilder pipelineBuilder) - => UseMiddleware(pipelineBuilder); - - - private static IPipelineBuilder UseMiddleware( - this IPipelineBuilder pipelineBuilder) - where TMiddleware : IPipelineMiddleware - => pipelineBuilder.UseMiddleware(); -} diff --git a/src/Messaging/NBB.Messaging.OpenTracing/Subscriber/OpenTracingMiddleware.cs b/src/Messaging/NBB.Messaging.OpenTracing/Subscriber/OpenTracingMiddleware.cs deleted file mode 100644 index a5971a71..00000000 --- a/src/Messaging/NBB.Messaging.OpenTracing/Subscriber/OpenTracingMiddleware.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) TotalSoft. -// This source code is licensed under the MIT license. - -using NBB.Core.Abstractions; -using NBB.Core.Pipeline; -using NBB.Correlation; -using NBB.Messaging.Abstractions; -using OpenTracing; -using OpenTracing.Propagation; -using OpenTracing.Tag; -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace NBB.Messaging.OpenTracing.Subscriber -{ - public class OpenTracingMiddleware : IPipelineMiddleware - { - private readonly ITracer _tracer; - - public OpenTracingMiddleware(ITracer tracer) - { - _tracer = tracer; - } - - public async Task Invoke(MessagingContext context, CancellationToken cancellationToken, Func next) - { - var extractedSpanContext = _tracer.Extract(BuiltinFormats.TextMap, new TextMapExtractAdapter(context.MessagingEnvelope.Headers)); - string operationName = $"Subscriber {context.MessagingEnvelope.Payload.GetType().GetPrettyName()}"; - - using var scope = _tracer.BuildSpan(operationName) - .AddReference(References.FollowsFrom, extractedSpanContext) - .WithTag(Tags.Component, "NBB.Messaging") - .WithTag(Tags.SpanKind, Tags.SpanKindConsumer) - .WithTag(Tags.PeerService, - context.MessagingEnvelope.Headers.TryGetValue(MessagingHeaders.Source, out var value) - ? value - : default) - .WithTag(MessagingTags.CorrelationId, CorrelationManager.GetCorrelationId()?.ToString()) - .WithTag(Tags.SamplingPriority, 1) - .StartActive(true); - - foreach (var header in context.MessagingEnvelope.Headers) - scope.Span.SetTag(MessagingTags.MessagingEnvelopeHeaderSpanTagPrefix + header.Key.ToLower(), header.Value); - - try - { - await next(); - } - catch (Exception exception) - { - scope.Span.SetException(exception); - throw; - } - } - } -} diff --git a/src/Messaging/README.md b/src/Messaging/README.md index 2c3142e5..d66efcd3 100644 --- a/src/Messaging/README.md +++ b/src/Messaging/README.md @@ -46,4 +46,4 @@ Other packages * *NBB.Messaging.DataContracts* - helps us formalize and instrument messaging data contracts * *NBB.Messaging.Effects* - messaging side effects and handlers for the NBB effects infrastructure * *NBB.Messaging.MultiTenancy* - support for messaging in multi-tenant environments -* *NBB.Messaging.OpenTracing* - support for *OpenTracing* in messaging publishers and subscribers +* *NBB.Messaging.OpenTelemetry* - support for *OpenTelemetry* in messaging publishers and subscribers diff --git a/src/Tools/Serilog/NBB.Tools.Serilog.OpenTracingSink/Internal/OpenTracingContribFilter.cs b/src/Tools/Serilog/NBB.Tools.Serilog.OpenTelemetryTracingSink/Internal/OpenTelemetryTracingContribFilter.cs similarity index 87% rename from src/Tools/Serilog/NBB.Tools.Serilog.OpenTracingSink/Internal/OpenTracingContribFilter.cs rename to src/Tools/Serilog/NBB.Tools.Serilog.OpenTelemetryTracingSink/Internal/OpenTelemetryTracingContribFilter.cs index 0642d340..c7962a8b 100644 --- a/src/Tools/Serilog/NBB.Tools.Serilog.OpenTracingSink/Internal/OpenTracingContribFilter.cs +++ b/src/Tools/Serilog/NBB.Tools.Serilog.OpenTelemetryTracingSink/Internal/OpenTelemetryTracingContribFilter.cs @@ -5,9 +5,9 @@ using System.Linq; using Serilog.Events; -namespace NBB.Tools.Serilog.OpenTracingSink.Internal +namespace NBB.Tools.Serilog.OpenTelemetryTracingSink.Internal { - internal static class OpenTracingContribFilter + internal static class OpenTelemetryTracingContribFilter { private static List ExcludedLogSources = new() { diff --git a/src/Tools/Serilog/NBB.Tools.Serilog.OpenTelemetryTracingSink/Internal/OpenTelemetryTracingSink.cs b/src/Tools/Serilog/NBB.Tools.Serilog.OpenTelemetryTracingSink/Internal/OpenTelemetryTracingSink.cs new file mode 100644 index 00000000..c66dda5a --- /dev/null +++ b/src/Tools/Serilog/NBB.Tools.Serilog.OpenTelemetryTracingSink/Internal/OpenTelemetryTracingSink.cs @@ -0,0 +1,64 @@ +// Copyright (c) TotalSoft. +// This source code is licensed under the MIT license. + +using OpenTelemetry.Trace; +using Serilog.Core; +using Serilog.Events; +using System; +using System.Diagnostics; + +namespace NBB.Tools.Serilog.OpenTelemetryTracingSink.Internal +{ + internal class OpenTelemetryTracingSink : ILogEventSink + { + private readonly Func _filter; + + public OpenTelemetryTracingSink(Func filter = null) + { + + _filter = filter ?? (logEvent => false); + } + + public void Emit(LogEvent logEvent) + { + var activity = Activity.Current; + + if (!(activity?.IsAllDataRequested ?? false)) + { + // Creating a new activity for a log message seems brutal so we ignore messages if we can't attach it to the current span. + return; + } + + if (_filter(logEvent)) + { + return; + } + + try + { + var tags = new ActivityTagsCollection + { + { "Message", logEvent.RenderMessage() }, + { "LogLevel", logEvent.Level }, + }; + + foreach (var property in logEvent.Properties) + { + tags[property.Key] = property.Value.ToString(); + } + + var activityEvent = new ActivityEvent("log", logEvent.Timestamp, tags); + activity.AddEvent(activityEvent); + + if (logEvent.Exception != null) + { + activity.RecordException(logEvent.Exception); + } + } + catch (Exception logException) + { + activity.RecordException(logException); + } + } + } +} diff --git a/src/Tools/Serilog/NBB.Tools.Serilog.OpenTracingSink/NBB.Tools.Serilog.OpenTracingSink.csproj b/src/Tools/Serilog/NBB.Tools.Serilog.OpenTelemetryTracingSink/NBB.Tools.Serilog.OpenTelemetryTracingSink.csproj similarity index 71% rename from src/Tools/Serilog/NBB.Tools.Serilog.OpenTracingSink/NBB.Tools.Serilog.OpenTracingSink.csproj rename to src/Tools/Serilog/NBB.Tools.Serilog.OpenTelemetryTracingSink/NBB.Tools.Serilog.OpenTelemetryTracingSink.csproj index 0e5376e3..d4e2fd6b 100644 --- a/src/Tools/Serilog/NBB.Tools.Serilog.OpenTracingSink/NBB.Tools.Serilog.OpenTracingSink.csproj +++ b/src/Tools/Serilog/NBB.Tools.Serilog.OpenTelemetryTracingSink/NBB.Tools.Serilog.OpenTelemetryTracingSink.csproj @@ -5,7 +5,7 @@ - + diff --git a/src/Tools/Serilog/NBB.Tools.Serilog.OpenTracingSink/OpenTracingConfigurationExtensions.cs b/src/Tools/Serilog/NBB.Tools.Serilog.OpenTelemetryTracingSink/OpenTelemetryTracingConfigurationExtensions.cs similarity index 53% rename from src/Tools/Serilog/NBB.Tools.Serilog.OpenTracingSink/OpenTracingConfigurationExtensions.cs rename to src/Tools/Serilog/NBB.Tools.Serilog.OpenTelemetryTracingSink/OpenTelemetryTracingConfigurationExtensions.cs index 4f4d4468..16f26b3d 100644 --- a/src/Tools/Serilog/NBB.Tools.Serilog.OpenTracingSink/OpenTracingConfigurationExtensions.cs +++ b/src/Tools/Serilog/NBB.Tools.Serilog.OpenTelemetryTracingSink/OpenTelemetryTracingConfigurationExtensions.cs @@ -1,29 +1,29 @@ // Copyright (c) TotalSoft. // This source code is licensed under the MIT license. -using NBB.Tools.Serilog.OpenTracingSink.Internal; +using NBB.Tools.Serilog.OpenTelemetryTracingSink.Internal; using Serilog; using Serilog.Configuration; using Serilog.Core; using Serilog.Events; using System; -namespace NBB.Tools.Serilog.OpenTracingSink +namespace NBB.Tools.Serilog.OpenTelemetryTracingSink { - public static class OpenTracingConfigurationExtensions + public static class OpenTelemetryTracingConfigurationExtensions { - public static LoggerConfiguration OpenTracing( + public static LoggerConfiguration OpenTelemetryTracing( this LoggerSinkConfiguration sinkConfiguration, LogEventLevel restrictedToMinimumLevel = LogEventLevel.Warning, LoggingLevelSwitch levelSwitch = null, - bool exludeOpenTracingContribEvents = true + bool exludeOpenTelemetryContribEvents = true ) { if (sinkConfiguration == null) throw new ArgumentNullException(nameof(sinkConfiguration)); - var sink = exludeOpenTracingContribEvents ? - new Internal.OpenTracingSink(OpenTracingContribFilter.ShouldExclude) : - new Internal.OpenTracingSink(); + var sink = exludeOpenTelemetryContribEvents ? + new Internal.OpenTelemetryTracingSink(OpenTelemetryTracingContribFilter.ShouldExclude) : + new Internal.OpenTelemetryTracingSink(); return sinkConfiguration.Sink(sink, restrictedToMinimumLevel, levelSwitch); } diff --git a/src/Tools/Serilog/NBB.Tools.Serilog.OpenTracingSink/Internal/OpenTracingSink.cs b/src/Tools/Serilog/NBB.Tools.Serilog.OpenTracingSink/Internal/OpenTracingSink.cs deleted file mode 100644 index f5fb2708..00000000 --- a/src/Tools/Serilog/NBB.Tools.Serilog.OpenTracingSink/Internal/OpenTracingSink.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) TotalSoft. -// This source code is licensed under the MIT license. - -using OpenTracing; -using OpenTracing.Util; -using Serilog.Core; -using Serilog.Events; -using System; -using System.Collections.Generic; - -namespace NBB.Tools.Serilog.OpenTracingSink.Internal -{ - internal class OpenTracingSink : ILogEventSink - { - private readonly ITracer _tracer; - private readonly Func _filter; - - public OpenTracingSink(Func filter = null) - { - _tracer = GlobalTracer.Instance; - _filter = filter ?? (logEvent => false); - } - - public void Emit(LogEvent logEvent) - { - ISpan span = _tracer.ActiveSpan; - - if (span == null) - { - // Creating a new span for a log message seems brutal so we ignore messages if we can't attach it to an active span. - return; - } - - if (_tracer.IsNoopTracer()) - { - return; - } - - if (_filter(logEvent)) - { - return; - } - - var fields = new Dictionary(); - - try - { - fields[LogFields.Event] = "log"; - fields[LogFields.Message] = logEvent.RenderMessage(); - fields["level"] = logEvent.Level; - - if (logEvent.Exception != null) - { - fields[LogFields.ErrorKind] = logEvent.Exception.GetType().FullName; - fields[LogFields.ErrorObject] = logEvent.Exception; - } - - foreach (var property in logEvent.Properties) - { - fields[property.Key] = property.Value.ToString(); - } - } - catch (Exception logException) - { - fields["opentracing.contrib.netcore.error"] = logException.ToString(); - } - - span.Log(fields); - } - } -} diff --git a/src/Tools/Serilog/NBB.Tools.Serilog.OpenTracingSink/Internal/TracerExtensions.cs b/src/Tools/Serilog/NBB.Tools.Serilog.OpenTracingSink/Internal/TracerExtensions.cs deleted file mode 100644 index b58490e8..00000000 --- a/src/Tools/Serilog/NBB.Tools.Serilog.OpenTracingSink/Internal/TracerExtensions.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) TotalSoft. -// This source code is licensed under the MIT license. - -using OpenTracing; -using OpenTracing.Noop; -using OpenTracing.Util; - -namespace NBB.Tools.Serilog.OpenTracingSink.Internal -{ - internal static class TracerExtensions - { - public static bool IsNoopTracer(this ITracer tracer) - { - if (tracer is NoopTracer) - return true; - - if (tracer is GlobalTracer && !GlobalTracer.IsRegistered()) - return true; - - return false; - } - } -} diff --git a/src/Tools/Serilog/README.md b/src/Tools/Serilog/README.md index 52f1474a..e08a8d09 100644 --- a/src/Tools/Serilog/README.md +++ b/src/Tools/Serilog/README.md @@ -6,4 +6,4 @@ The package [`NBB.Tools.Serilog.Enrichers.ServiceIdentifier`](NBB.Tools.Serilog. The package [`NBB.Tools.Serilog.Enrichers.TenantId`](NBB.Tools.Serilog.Enrichers.TenantId) provides an enricher for tenant id from nbb tenant context. -The package [`NBB.Tools.Serilog.OpenTracingSink`](NBB.Tools.Serilog.OpenTracingSink) provides a sink for serilog and opentracing. +The package [`NBB.Tools.Serilog.OpenTelemetryTracingSink`](NBB.Tools.Serilog.OpenTelemetrySink) provides a sink for serilog and OpenTelemetry.