From 41b19213b2397d96401aa4df164bcd9f7c5f6029 Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Mon, 27 Jul 2020 17:23:22 -0700 Subject: [PATCH 01/24] Suite of example applications demonstrating context propagation with RabbitMQ --- OpenTelemetry.sln | 21 +++ examples/MicroserviceExample/README.md | 31 ++++ .../MicroserviceExample/WebApi/.dockerignore | 21 +++ .../WebApi/Controllers/RabbitMqController.cs | 25 ++++ .../MicroserviceExample/WebApi/Dockerfile | 12 ++ .../MicroserviceExample/WebApi/Program.cs | 20 +++ .../WebApi/RabbitMqService.cs | 111 +++++++++++++++ .../MicroserviceExample/WebApi/Startup.cs | 52 +++++++ .../MicroserviceExample/WebApi/WebApi.csproj | 12 ++ .../WebApi/appsettings.Development.json | 9 ++ .../WebApi/appsettings.json | 10 ++ .../WorkerService/.dockerignore | 21 +++ .../WorkerService/Dockerfile | 12 ++ .../WorkerService/Program.cs | 35 +++++ .../WorkerService/RabbitMqConsumer.cs | 132 ++++++++++++++++++ .../WorkerService/WorkerService.csproj | 13 ++ .../appsettings.Development.json | 9 ++ .../WorkerService/appsettings.json | 9 ++ .../MicroserviceExample/docker-compose.yml | 38 +++++ 19 files changed, 593 insertions(+) create mode 100644 examples/MicroserviceExample/README.md create mode 100644 examples/MicroserviceExample/WebApi/.dockerignore create mode 100644 examples/MicroserviceExample/WebApi/Controllers/RabbitMqController.cs create mode 100644 examples/MicroserviceExample/WebApi/Dockerfile create mode 100644 examples/MicroserviceExample/WebApi/Program.cs create mode 100644 examples/MicroserviceExample/WebApi/RabbitMqService.cs create mode 100644 examples/MicroserviceExample/WebApi/Startup.cs create mode 100644 examples/MicroserviceExample/WebApi/WebApi.csproj create mode 100644 examples/MicroserviceExample/WebApi/appsettings.Development.json create mode 100644 examples/MicroserviceExample/WebApi/appsettings.json create mode 100644 examples/MicroserviceExample/WorkerService/.dockerignore create mode 100644 examples/MicroserviceExample/WorkerService/Dockerfile create mode 100644 examples/MicroserviceExample/WorkerService/Program.cs create mode 100644 examples/MicroserviceExample/WorkerService/RabbitMqConsumer.cs create mode 100644 examples/MicroserviceExample/WorkerService/WorkerService.csproj create mode 100644 examples/MicroserviceExample/WorkerService/appsettings.Development.json create mode 100644 examples/MicroserviceExample/WorkerService/appsettings.json create mode 100644 examples/MicroserviceExample/docker-compose.yml diff --git a/OpenTelemetry.sln b/OpenTelemetry.sln index a7752736aeb..73dbbe1e445 100644 --- a/OpenTelemetry.sln +++ b/OpenTelemetry.sln @@ -154,6 +154,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{7C87CAF9-7 docs\getting-started.md = docs\getting-started.md EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MicroserviceExample", "MicroserviceExample", "{4D492D62-5150-45F9-817F-C99562E364E2}" + ProjectSection(SolutionItems) = preProject + examples\MicroserviceExample\docker-compose.yml = examples\MicroserviceExample\docker-compose.yml + examples\MicroserviceExample\README.md = examples\MicroserviceExample\README.md + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApi", "examples\MicroserviceExample\WebApi\WebApi.csproj", "{07336602-860B-4975-95DD-405D19C00901}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkerService", "examples\MicroserviceExample\WorkerService\WorkerService.csproj", "{FA7A6F67-1F2F-4855-890D-51B5829578A9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -304,6 +314,14 @@ Global {0246BFC4-8AAF-45E1-A127-DB43D6E345BB}.Debug|Any CPU.Build.0 = Debug|Any CPU {0246BFC4-8AAF-45E1-A127-DB43D6E345BB}.Release|Any CPU.ActiveCfg = Release|Any CPU {0246BFC4-8AAF-45E1-A127-DB43D6E345BB}.Release|Any CPU.Build.0 = Release|Any CPU + {07336602-860B-4975-95DD-405D19C00901}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {07336602-860B-4975-95DD-405D19C00901}.Debug|Any CPU.Build.0 = Debug|Any CPU + {07336602-860B-4975-95DD-405D19C00901}.Release|Any CPU.ActiveCfg = Release|Any CPU + {07336602-860B-4975-95DD-405D19C00901}.Release|Any CPU.Build.0 = Release|Any CPU + {FA7A6F67-1F2F-4855-890D-51B5829578A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FA7A6F67-1F2F-4855-890D-51B5829578A9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FA7A6F67-1F2F-4855-890D-51B5829578A9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FA7A6F67-1F2F-4855-890D-51B5829578A9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -319,6 +337,9 @@ Global {FF3E6E08-E8E4-4523-B526-847CD989279F} = {E359BB2B-9AEC-497D-B321-7DF2450C3B8E} {0935622B-9377-4056-8343-AE6ECDC274CF} = {E359BB2B-9AEC-497D-B321-7DF2450C3B8E} {DE9130A4-F30A-49D7-8834-41DE3021218B} = {0169B149-FB8B-46F4-9EF7-8A0E69F8FAAF} + {4D492D62-5150-45F9-817F-C99562E364E2} = {E359BB2B-9AEC-497D-B321-7DF2450C3B8E} + {07336602-860B-4975-95DD-405D19C00901} = {4D492D62-5150-45F9-817F-C99562E364E2} + {FA7A6F67-1F2F-4855-890D-51B5829578A9} = {4D492D62-5150-45F9-817F-C99562E364E2} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {55639B5C-0770-4A22-AB56-859604650521} diff --git a/examples/MicroserviceExample/README.md b/examples/MicroserviceExample/README.md new file mode 100644 index 00000000000..b2c97a930fb --- /dev/null +++ b/examples/MicroserviceExample/README.md @@ -0,0 +1,31 @@ +# End-to-end example + +This directory contains a suite of example applications that communicate with each other. + +1. An ASP.NET Core Web API +2. A background Worker Service + +The Web API publishes messages to RabbitMQ and the Worker Service consumes the messages. + +Trace context propagation is achieved between the two applications using the .NET OpenTelemetry API. + +Traces are exported to a containerized Zipkin instance. + +## Running the sample applications + +The sample applications can easily be run using Docker Desktop by running: + +```shell +docker-compose up --build +``` + +Once the containers are up, invoke the Web API by navigating to http://localhost:5000/RabbitMq. +Your traces can be viewed by going to http://localhost:9411/zipkin/ and RabbitMQ can be managed at http://localhost:15672/ (user: guest, password: guest). + +## References + +* [Docker Desktop](https://www.docker.com/products/docker-desktop) +* [Zipkin](https://zipkin.io) +* [RabbitMQ](https://www.rabbitmq.com/) +* [Worker Service](https://docs.microsoft.com/en-us/azure/azure-monitor/app/worker-service) +* [OpenTelemetry Project](https://opentelemetry.io/) diff --git a/examples/MicroserviceExample/WebApi/.dockerignore b/examples/MicroserviceExample/WebApi/.dockerignore new file mode 100644 index 00000000000..67a5e049745 --- /dev/null +++ b/examples/MicroserviceExample/WebApi/.dockerignore @@ -0,0 +1,21 @@ +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/docker-compose* +**/Dockerfile* +**/bin +**/obj +**/*.yaml +**/*.yml +**/*.md +**/*.ps1 \ No newline at end of file diff --git a/examples/MicroserviceExample/WebApi/Controllers/RabbitMqController.cs b/examples/MicroserviceExample/WebApi/Controllers/RabbitMqController.cs new file mode 100644 index 00000000000..5c40df477c8 --- /dev/null +++ b/examples/MicroserviceExample/WebApi/Controllers/RabbitMqController.cs @@ -0,0 +1,25 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace WebApi.Controllers +{ + [ApiController] + [Route("[controller]")] + public class RabbitMqController : ControllerBase + { + private readonly ILogger _logger; + private readonly IRabbitMqService _rabbitMqService; + + public RabbitMqController(ILogger logger, IRabbitMqService rabbitMqService) + { + _logger = logger; + _rabbitMqService = rabbitMqService; + } + + [HttpGet] + public string Get() + { + return _rabbitMqService.PublishMessage(); + } + } +} diff --git a/examples/MicroserviceExample/WebApi/Dockerfile b/examples/MicroserviceExample/WebApi/Dockerfile new file mode 100644 index 00000000000..8a4bd1bc9a5 --- /dev/null +++ b/examples/MicroserviceExample/WebApi/Dockerfile @@ -0,0 +1,12 @@ +FROM mcr.microsoft.com/dotnet/core/sdk:3.1-alpine AS build + +WORKDIR /app +COPY ./*.csproj ./ +RUN dotnet restore +COPY . . +RUN dotnet publish -c Release -o /out --no-restore + +FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-alpine AS runtime +WORKDIR /app +COPY --from=build /out ./ +ENTRYPOINT ["dotnet", "WebApi.dll"] diff --git a/examples/MicroserviceExample/WebApi/Program.cs b/examples/MicroserviceExample/WebApi/Program.cs new file mode 100644 index 00000000000..3109278093f --- /dev/null +++ b/examples/MicroserviceExample/WebApi/Program.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; + +namespace WebApi +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseUrls("http://*:5000").UseStartup(); + }); + } +} diff --git a/examples/MicroserviceExample/WebApi/RabbitMqService.cs b/examples/MicroserviceExample/WebApi/RabbitMqService.cs new file mode 100644 index 00000000000..fd0a8e62f87 --- /dev/null +++ b/examples/MicroserviceExample/WebApi/RabbitMqService.cs @@ -0,0 +1,111 @@ +using Microsoft.Extensions.Logging; +using OpenTelemetry.Context.Propagation; +using RabbitMQ.Client; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace WebApi +{ + public interface IRabbitMqService : IDisposable + { + string PublishMessage(); + } + + public class RabbitMqService : IRabbitMqService + { + private const string QueueName = "TestQueue"; + + private readonly ILogger logger; + private readonly ConnectionFactory connectionFactory; + private readonly IConnection connection; + private readonly IModel channel; + private readonly ActivitySource activitySource; + private readonly ITextFormat textFormat; + + public RabbitMqService(ILogger logger) + { + this.logger = logger; + + this.connectionFactory = new ConnectionFactory() + { + HostName = Environment.GetEnvironmentVariable("RABBIT_HOSTNAME") ?? "localhost", + UserName = "guest", + Password = "guest", + Port = 5672, + RequestedConnectionTimeout = TimeSpan.FromMilliseconds(3000), + }; + + this.connection = connectionFactory.CreateConnection(); + this.channel = connection.CreateModel(); + channel.QueueDeclare( + queue: QueueName, + durable: false, + exclusive: false, + autoDelete: false, + arguments: null); + + this.activitySource = new ActivitySource(nameof(RabbitMqService)); + + this.textFormat = new TraceContextFormat(); + } + + public string PublishMessage() + { + try + { + string activityName = $"{nameof(RabbitMqService)}.{nameof(PublishMessage)}"; + using (var activity = activitySource.StartActivity(activityName)) + { + var props = this.channel.CreateBasicProperties(); + props.ContentType = "text/plain"; + props.DeliveryMode = 2; + + textFormat.Inject(activity.Context, props, InjectTraceContextIntoBasicProperties); + + var body = $"Published message. DateTime.Now = {DateTime.Now}."; + + this.channel.BasicPublish( + exchange: string.Empty, + routingKey: QueueName, + basicProperties: props, + body: Encoding.UTF8.GetBytes(body)); + + this.logger.LogInformation($"Published message: {body}."); + + return body; + } + } + catch (Exception ex) + { + this.logger.LogError(ex.ToString()); + throw; + } + } + + public void Dispose() + { + this.connection.Dispose(); + this.channel.Dispose(); + this.activitySource.Dispose(); + } + + private void InjectTraceContextIntoBasicProperties(IBasicProperties props, string key, string value) + { + try + { + if (props.Headers == null) + { + props.Headers = new Dictionary(); + } + + props.Headers[key] = value; + } + catch (Exception ex) + { + this.logger.LogError($"Failed to inject trace context: {ex}"); + } + } + } +} diff --git a/examples/MicroserviceExample/WebApi/Startup.cs b/examples/MicroserviceExample/WebApi/Startup.cs new file mode 100644 index 00000000000..a64241ef3d2 --- /dev/null +++ b/examples/MicroserviceExample/WebApi/Startup.cs @@ -0,0 +1,52 @@ +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using OpenTelemetry.Trace; + +namespace WebApi +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers(); + + services.AddSingleton(); + + services.AddOpenTelemetry((builder) => builder + .AddAspNetCoreInstrumentation() + .AddActivitySource(nameof(RabbitMqService)) + .UseZipkinExporter(b => + { + var zipkinHostName = Environment.GetEnvironmentVariable("ZIPKIN_HOSTNAME") ?? "localhost"; + b.ServiceName = "WebApi"; + b.Endpoint = new Uri($"http://{zipkinHostName}:9411/api/v2/spans"); + })); + } + + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseRouting(); + + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + } + } +} diff --git a/examples/MicroserviceExample/WebApi/WebApi.csproj b/examples/MicroserviceExample/WebApi/WebApi.csproj new file mode 100644 index 00000000000..0d95d4d0d1f --- /dev/null +++ b/examples/MicroserviceExample/WebApi/WebApi.csproj @@ -0,0 +1,12 @@ + + + netcoreapp3.1 + + + + + + + + + diff --git a/examples/MicroserviceExample/WebApi/appsettings.Development.json b/examples/MicroserviceExample/WebApi/appsettings.Development.json new file mode 100644 index 00000000000..8983e0fc1c5 --- /dev/null +++ b/examples/MicroserviceExample/WebApi/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/examples/MicroserviceExample/WebApi/appsettings.json b/examples/MicroserviceExample/WebApi/appsettings.json new file mode 100644 index 00000000000..d9d9a9bff6f --- /dev/null +++ b/examples/MicroserviceExample/WebApi/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} diff --git a/examples/MicroserviceExample/WorkerService/.dockerignore b/examples/MicroserviceExample/WorkerService/.dockerignore new file mode 100644 index 00000000000..67a5e049745 --- /dev/null +++ b/examples/MicroserviceExample/WorkerService/.dockerignore @@ -0,0 +1,21 @@ +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/docker-compose* +**/Dockerfile* +**/bin +**/obj +**/*.yaml +**/*.yml +**/*.md +**/*.ps1 \ No newline at end of file diff --git a/examples/MicroserviceExample/WorkerService/Dockerfile b/examples/MicroserviceExample/WorkerService/Dockerfile new file mode 100644 index 00000000000..2aa41977dd3 --- /dev/null +++ b/examples/MicroserviceExample/WorkerService/Dockerfile @@ -0,0 +1,12 @@ +FROM mcr.microsoft.com/dotnet/core/sdk:3.1-alpine AS build + +WORKDIR /app +COPY ./*.csproj ./ +RUN dotnet restore +COPY . . +RUN dotnet publish -c Release -o /out --no-restore + +FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-alpine AS runtime +WORKDIR /app +COPY --from=build /out ./ +ENTRYPOINT ["dotnet", "WorkerService.dll"] diff --git a/examples/MicroserviceExample/WorkerService/Program.cs b/examples/MicroserviceExample/WorkerService/Program.cs new file mode 100644 index 00000000000..e1faa1de98e --- /dev/null +++ b/examples/MicroserviceExample/WorkerService/Program.cs @@ -0,0 +1,35 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using OpenTelemetry.Trace; +using System; + +namespace WorkerService +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureServices((hostContext, services) => + { + services.AddHostedService(); + + // TODO: Determine if this can be done here in a WorkerService. It does not seem to work... doing this in the RabbitMqConsumer for now. + // services.AddOpenTelemetry((builder) => + // { + // builder + // .AddActivitySource(nameof(RabbitMqConsumer)) + // .UseZipkinExporter(b => + // { + // var zipkinHostName = Environment.GetEnvironmentVariable("ZIPKIN_HOSTNAME") ?? "localhost"; + // b.ServiceName = "Worker"; + // b.Endpoint = new Uri($"http://{zipkinHostName}:9411/api/v2/spans"); + // }); + // }); + }); + } +} diff --git a/examples/MicroserviceExample/WorkerService/RabbitMqConsumer.cs b/examples/MicroserviceExample/WorkerService/RabbitMqConsumer.cs new file mode 100644 index 00000000000..d112bef065f --- /dev/null +++ b/examples/MicroserviceExample/WorkerService/RabbitMqConsumer.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using OpenTelemetry; +using OpenTelemetry.Context.Propagation; +using OpenTelemetry.Trace; +using RabbitMQ.Client; +using RabbitMQ.Client.Events; + +namespace WorkerService +{ + public class RabbitMqConsumer : BackgroundService + { + private const string QueueName = "TestQueue"; + + private readonly ILogger logger; + + private IConnection connection; + private IModel channel; + private ActivitySource activitySource; + private ITextFormat textFormat; + private TracerProvider tracerProvider; + + public RabbitMqConsumer(ILogger logger) + { + this.logger = logger; + } + + public override Task StartAsync(CancellationToken cancellationToken) + { + this.tracerProvider = Sdk.CreateTracerProvider((builder) => + { + builder + .AddActivitySource(nameof(RabbitMqConsumer)) + .UseZipkinExporter(b => + { + var zipkinHostName = Environment.GetEnvironmentVariable("ZIPKIN_HOSTNAME") ?? "localhost"; + b.ServiceName = nameof(RabbitMqConsumer); + b.Endpoint = new Uri($"http://{zipkinHostName}:9411/api/v2/spans"); + }); + }); + + var connectionFactory = new ConnectionFactory() + { + HostName = Environment.GetEnvironmentVariable("RABBIT_HOSTNAME") ?? "localhost", + UserName = "guest", + Password = "guest", + Port = 5672, + DispatchConsumersAsync = true, + RequestedConnectionTimeout = TimeSpan.FromMilliseconds(3000), + }; + + this.connection = connectionFactory.CreateConnection(); + this.channel = this.connection.CreateModel(); + this.channel.QueueDeclarePassive(QueueName); + this.channel.BasicQos(0, 1, false); + + this.activitySource = new ActivitySource(nameof(RabbitMqConsumer)); + + this.textFormat = new TraceContextFormat(); + + return base.StartAsync(cancellationToken); + } + + public override async Task StopAsync(CancellationToken cancellationToken) + { + await base.StopAsync(cancellationToken); + this.tracerProvider.Dispose(); + this.connection.Dispose(); + this.channel.Dispose(); + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + stoppingToken.ThrowIfCancellationRequested(); + + var consumer = new AsyncEventingBasicConsumer(channel); + consumer.Received += async (bc, ea) => + { + var parentContext = this.textFormat.Extract(ea.BasicProperties, ExtractTraceContextFromBasicProperties); + + string activityName = $"{nameof(RabbitMqConsumer)}.{nameof(ExecuteAsync)}"; + using (var activity = activitySource.StartActivity(activityName, ActivityKind.Server, parentContext)) + { + try + { + var message = Encoding.UTF8.GetString(ea.Body.Span); + activity.AddTag("message", message); + + this.logger.LogInformation($"Received message: {message}."); + + await Task.Delay(new Random().Next(1, 3) * 1000, stoppingToken); + + channel.BasicAck(ea.DeliveryTag, false); + } + catch (Exception ex) + { + this.logger.LogError(ex, ex.Message); + } + } + }; + + channel.BasicConsume(queue: QueueName, autoAck: false, consumer: consumer); + + await Task.CompletedTask; + } + + private IEnumerable ExtractTraceContextFromBasicProperties(IBasicProperties props, string key) + { + try + { + if (props.Headers.TryGetValue(key, out object value)) + { + var bytes = value as byte[]; + return new[] { Encoding.UTF8.GetString(bytes) }; + } + } + catch (Exception ex) + { + this.logger.LogError(ex, "Failed to extract trace context."); + } + + return Enumerable.Empty(); + } + } +} diff --git a/examples/MicroserviceExample/WorkerService/WorkerService.csproj b/examples/MicroserviceExample/WorkerService/WorkerService.csproj new file mode 100644 index 00000000000..86b66338188 --- /dev/null +++ b/examples/MicroserviceExample/WorkerService/WorkerService.csproj @@ -0,0 +1,13 @@ + + + netcoreapp3.1 + + + + + + + + + + diff --git a/examples/MicroserviceExample/WorkerService/appsettings.Development.json b/examples/MicroserviceExample/WorkerService/appsettings.Development.json new file mode 100644 index 00000000000..8983e0fc1c5 --- /dev/null +++ b/examples/MicroserviceExample/WorkerService/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/examples/MicroserviceExample/WorkerService/appsettings.json b/examples/MicroserviceExample/WorkerService/appsettings.json new file mode 100644 index 00000000000..6727805c62b --- /dev/null +++ b/examples/MicroserviceExample/WorkerService/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Warning", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/examples/MicroserviceExample/docker-compose.yml b/examples/MicroserviceExample/docker-compose.yml new file mode 100644 index 00000000000..4c18938a6a4 --- /dev/null +++ b/examples/MicroserviceExample/docker-compose.yml @@ -0,0 +1,38 @@ +version: '3.8' + +services: + zipkin: + image: openzipkin/zipkin + ports: + - 9411:9411 + + rabbitmq: + image: rabbitmq:3-management-alpine + ports: + - 5672:5672 + - 15672:15672 + + webapi: + build: ./WebApi + image: opentelemetry-example-webapi + environment: + - ASPNETCORE_ENVIRONMENT=Development + - RABBIT_HOSTNAME=rabbitmq + - ZIPKIN_HOSTNAME=zipkin + ports: + - 5000:5000 + depends_on: + - rabbitmq + - zipkin + + workerservice: + build: ./WorkerService + image: opentelemetry-example-workerservice + environment: + - DOTNET_ENVIRONMENT=Development + - RABBIT_HOSTNAME=rabbitmq + - ZIPKIN_HOSTNAME=zipkin + restart: on-failure + depends_on: + - rabbitmq + - zipkin From d899d2af510a3b6a1351bb6abe05b86926e62fab Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Mon, 27 Jul 2020 17:46:12 -0700 Subject: [PATCH 02/24] Put System.* namespaces first --- examples/MicroserviceExample/WebApi/RabbitMqService.cs | 8 ++++---- examples/MicroserviceExample/WorkerService/Program.cs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/MicroserviceExample/WebApi/RabbitMqService.cs b/examples/MicroserviceExample/WebApi/RabbitMqService.cs index fd0a8e62f87..d5e22705d90 100644 --- a/examples/MicroserviceExample/WebApi/RabbitMqService.cs +++ b/examples/MicroserviceExample/WebApi/RabbitMqService.cs @@ -1,10 +1,10 @@ -using Microsoft.Extensions.Logging; -using OpenTelemetry.Context.Propagation; -using RabbitMQ.Client; -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Text; +using Microsoft.Extensions.Logging; +using OpenTelemetry.Context.Propagation; +using RabbitMQ.Client; namespace WebApi { diff --git a/examples/MicroserviceExample/WorkerService/Program.cs b/examples/MicroserviceExample/WorkerService/Program.cs index e1faa1de98e..434795b5679 100644 --- a/examples/MicroserviceExample/WorkerService/Program.cs +++ b/examples/MicroserviceExample/WorkerService/Program.cs @@ -1,7 +1,7 @@ -using Microsoft.Extensions.DependencyInjection; +using System; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using OpenTelemetry.Trace; -using System; namespace WorkerService { From d3b2e514e7c1afcf01b437ddccbe166112865ec1 Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Mon, 27 Jul 2020 17:46:57 -0700 Subject: [PATCH 03/24] Newline at EOF --- examples/MicroserviceExample/WebApi/.dockerignore | 2 +- examples/MicroserviceExample/WorkerService/.dockerignore | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/MicroserviceExample/WebApi/.dockerignore b/examples/MicroserviceExample/WebApi/.dockerignore index 67a5e049745..ef449543015 100644 --- a/examples/MicroserviceExample/WebApi/.dockerignore +++ b/examples/MicroserviceExample/WebApi/.dockerignore @@ -18,4 +18,4 @@ **/*.yaml **/*.yml **/*.md -**/*.ps1 \ No newline at end of file +**/*.ps1 diff --git a/examples/MicroserviceExample/WorkerService/.dockerignore b/examples/MicroserviceExample/WorkerService/.dockerignore index 67a5e049745..ef449543015 100644 --- a/examples/MicroserviceExample/WorkerService/.dockerignore +++ b/examples/MicroserviceExample/WorkerService/.dockerignore @@ -18,4 +18,4 @@ **/*.yaml **/*.yml **/*.md -**/*.ps1 \ No newline at end of file +**/*.ps1 From 203cad33f6390501240b0bb0642cabc749168cbe Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Mon, 27 Jul 2020 17:47:15 -0700 Subject: [PATCH 04/24] Fix markdownlint errors --- examples/MicroserviceExample/README.md | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/examples/MicroserviceExample/README.md b/examples/MicroserviceExample/README.md index b2c97a930fb..407dbd7d64e 100644 --- a/examples/MicroserviceExample/README.md +++ b/examples/MicroserviceExample/README.md @@ -1,13 +1,16 @@ # End-to-end example -This directory contains a suite of example applications that communicate with each other. +This directory contains a suite of example applications that communicate with +each other. 1. An ASP.NET Core Web API 2. A background Worker Service -The Web API publishes messages to RabbitMQ and the Worker Service consumes the messages. +The Web API publishes messages to RabbitMQ and the Worker Service consumes +the messages. -Trace context propagation is achieved between the two applications using the .NET OpenTelemetry API. +Trace context propagation is achieved between the two applications using the +.NET OpenTelemetry API. Traces are exported to a containerized Zipkin instance. @@ -19,13 +22,17 @@ The sample applications can easily be run using Docker Desktop by running: docker-compose up --build ``` -Once the containers are up, invoke the Web API by navigating to http://localhost:5000/RabbitMq. -Your traces can be viewed by going to http://localhost:9411/zipkin/ and RabbitMQ can be managed at http://localhost:15672/ (user: guest, password: guest). +Once the containers are up, you can: +* [Invoke the Web API](http://localhost:5000/RabbitMq) +* View your traces with Zipkin [here](http://localhost:9411/zipkin) +* Manage RabbitMQ [here](http://localhost:15672/) + * user = guest + * password = guest ## References * [Docker Desktop](https://www.docker.com/products/docker-desktop) -* [Zipkin](https://zipkin.io) +* [OpenTelemetry Project](https://opentelemetry.io/) * [RabbitMQ](https://www.rabbitmq.com/) * [Worker Service](https://docs.microsoft.com/en-us/azure/azure-monitor/app/worker-service) -* [OpenTelemetry Project](https://opentelemetry.io/) +* [Zipkin](https://zipkin.io) From 38a2a5b3c93dd9b8287b0d5c4b025bfce431a108 Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Mon, 27 Jul 2020 18:01:27 -0700 Subject: [PATCH 05/24] markdownlint fix --- examples/MicroserviceExample/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/MicroserviceExample/README.md b/examples/MicroserviceExample/README.md index 407dbd7d64e..bc0525d2b74 100644 --- a/examples/MicroserviceExample/README.md +++ b/examples/MicroserviceExample/README.md @@ -23,6 +23,7 @@ docker-compose up --build ``` Once the containers are up, you can: + * [Invoke the Web API](http://localhost:5000/RabbitMq) * View your traces with Zipkin [here](http://localhost:9411/zipkin) * Manage RabbitMQ [here](http://localhost:15672/) From 0ab89395e59efee0c945ac193d82cf678072e7a7 Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Wed, 29 Jul 2020 16:40:46 -0700 Subject: [PATCH 06/24] Refactor WorkerService separating OpenTelemetry related logic from RabbitMQ boilerplate --- .../WorkerService/MessageProcessor.cs | 84 +++++++++++ .../WorkerService/Program.cs | 4 +- .../WorkerService/RabbitMqConsumer.cs | 132 ------------------ .../WorkerService/RabbitMqService.cs | 65 +++++++++ 4 files changed, 152 insertions(+), 133 deletions(-) create mode 100644 examples/MicroserviceExample/WorkerService/MessageProcessor.cs delete mode 100644 examples/MicroserviceExample/WorkerService/RabbitMqConsumer.cs create mode 100644 examples/MicroserviceExample/WorkerService/RabbitMqService.cs diff --git a/examples/MicroserviceExample/WorkerService/MessageProcessor.cs b/examples/MicroserviceExample/WorkerService/MessageProcessor.cs new file mode 100644 index 00000000000..13555e51b74 --- /dev/null +++ b/examples/MicroserviceExample/WorkerService/MessageProcessor.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using OpenTelemetry; +using OpenTelemetry.Context.Propagation; +using OpenTelemetry.Trace; +using RabbitMQ.Client; +using RabbitMQ.Client.Events; + +namespace WorkerService +{ + public class MessageProcessor : IDisposable + { + private static readonly ActivitySource ActivitySource = new ActivitySource(nameof(MessageProcessor)); + private static readonly ITextFormat TextFormat = new TraceContextFormat(); + + private readonly TracerProvider tracerProvider; + + public MessageProcessor() + { + this.tracerProvider = Sdk.CreateTracerProvider((builder) => + { + builder + .AddActivitySource(nameof(MessageProcessor)) + .UseZipkinExporter(b => + { + var zipkinHostName = Environment.GetEnvironmentVariable("ZIPKIN_HOSTNAME") ?? "localhost"; + b.ServiceName = nameof(WorkerService); + b.Endpoint = new Uri($"http://{zipkinHostName}:9411/api/v2/spans"); + }); + }); + } + + public void Dispose() + { + this.tracerProvider.Dispose(); + } + + public async Task ProcessMessage(BasicDeliverEventArgs ea) + { + var activityName = $"{ea.RoutingKey} receive"; + + var parentContext = TextFormat.Extract(ea.BasicProperties, ExtractTraceContextFromBasicProperties); + + using (var activity = ActivitySource.StartActivity(activityName, ActivityKind.Server, parentContext)) + { + try + { + var message = Encoding.UTF8.GetString(ea.Body.Span); + + activity.AddTag("message", message); + + // Simulate some work + await Task.Delay(1000); + } + catch (Exception ex) + { + Console.WriteLine(ex); + } + } + } + + private static IEnumerable ExtractTraceContextFromBasicProperties(IBasicProperties props, string key) + { + try + { + if (props.Headers.TryGetValue(key, out object value)) + { + var bytes = value as byte[]; + return new[] { Encoding.UTF8.GetString(bytes) }; + } + } + catch (Exception ex) + { + Console.WriteLine($"Failed to extract trace context: {ex}"); + } + + return Enumerable.Empty(); + } + } +} diff --git a/examples/MicroserviceExample/WorkerService/Program.cs b/examples/MicroserviceExample/WorkerService/Program.cs index 434795b5679..e249ae59a53 100644 --- a/examples/MicroserviceExample/WorkerService/Program.cs +++ b/examples/MicroserviceExample/WorkerService/Program.cs @@ -16,7 +16,9 @@ public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureServices((hostContext, services) => { - services.AddHostedService(); + services.AddHostedService(); + + services.AddSingleton(); // TODO: Determine if this can be done here in a WorkerService. It does not seem to work... doing this in the RabbitMqConsumer for now. // services.AddOpenTelemetry((builder) => diff --git a/examples/MicroserviceExample/WorkerService/RabbitMqConsumer.cs b/examples/MicroserviceExample/WorkerService/RabbitMqConsumer.cs deleted file mode 100644 index d112bef065f..00000000000 --- a/examples/MicroserviceExample/WorkerService/RabbitMqConsumer.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using OpenTelemetry; -using OpenTelemetry.Context.Propagation; -using OpenTelemetry.Trace; -using RabbitMQ.Client; -using RabbitMQ.Client.Events; - -namespace WorkerService -{ - public class RabbitMqConsumer : BackgroundService - { - private const string QueueName = "TestQueue"; - - private readonly ILogger logger; - - private IConnection connection; - private IModel channel; - private ActivitySource activitySource; - private ITextFormat textFormat; - private TracerProvider tracerProvider; - - public RabbitMqConsumer(ILogger logger) - { - this.logger = logger; - } - - public override Task StartAsync(CancellationToken cancellationToken) - { - this.tracerProvider = Sdk.CreateTracerProvider((builder) => - { - builder - .AddActivitySource(nameof(RabbitMqConsumer)) - .UseZipkinExporter(b => - { - var zipkinHostName = Environment.GetEnvironmentVariable("ZIPKIN_HOSTNAME") ?? "localhost"; - b.ServiceName = nameof(RabbitMqConsumer); - b.Endpoint = new Uri($"http://{zipkinHostName}:9411/api/v2/spans"); - }); - }); - - var connectionFactory = new ConnectionFactory() - { - HostName = Environment.GetEnvironmentVariable("RABBIT_HOSTNAME") ?? "localhost", - UserName = "guest", - Password = "guest", - Port = 5672, - DispatchConsumersAsync = true, - RequestedConnectionTimeout = TimeSpan.FromMilliseconds(3000), - }; - - this.connection = connectionFactory.CreateConnection(); - this.channel = this.connection.CreateModel(); - this.channel.QueueDeclarePassive(QueueName); - this.channel.BasicQos(0, 1, false); - - this.activitySource = new ActivitySource(nameof(RabbitMqConsumer)); - - this.textFormat = new TraceContextFormat(); - - return base.StartAsync(cancellationToken); - } - - public override async Task StopAsync(CancellationToken cancellationToken) - { - await base.StopAsync(cancellationToken); - this.tracerProvider.Dispose(); - this.connection.Dispose(); - this.channel.Dispose(); - } - - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - stoppingToken.ThrowIfCancellationRequested(); - - var consumer = new AsyncEventingBasicConsumer(channel); - consumer.Received += async (bc, ea) => - { - var parentContext = this.textFormat.Extract(ea.BasicProperties, ExtractTraceContextFromBasicProperties); - - string activityName = $"{nameof(RabbitMqConsumer)}.{nameof(ExecuteAsync)}"; - using (var activity = activitySource.StartActivity(activityName, ActivityKind.Server, parentContext)) - { - try - { - var message = Encoding.UTF8.GetString(ea.Body.Span); - activity.AddTag("message", message); - - this.logger.LogInformation($"Received message: {message}."); - - await Task.Delay(new Random().Next(1, 3) * 1000, stoppingToken); - - channel.BasicAck(ea.DeliveryTag, false); - } - catch (Exception ex) - { - this.logger.LogError(ex, ex.Message); - } - } - }; - - channel.BasicConsume(queue: QueueName, autoAck: false, consumer: consumer); - - await Task.CompletedTask; - } - - private IEnumerable ExtractTraceContextFromBasicProperties(IBasicProperties props, string key) - { - try - { - if (props.Headers.TryGetValue(key, out object value)) - { - var bytes = value as byte[]; - return new[] { Encoding.UTF8.GetString(bytes) }; - } - } - catch (Exception ex) - { - this.logger.LogError(ex, "Failed to extract trace context."); - } - - return Enumerable.Empty(); - } - } -} diff --git a/examples/MicroserviceExample/WorkerService/RabbitMqService.cs b/examples/MicroserviceExample/WorkerService/RabbitMqService.cs new file mode 100644 index 00000000000..af1a6168183 --- /dev/null +++ b/examples/MicroserviceExample/WorkerService/RabbitMqService.cs @@ -0,0 +1,65 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using RabbitMQ.Client; +using RabbitMQ.Client.Events; + +namespace WorkerService +{ + public partial class RabbitMqService : BackgroundService + { + private const string QueueName = "TestQueue"; + + private readonly MessageProcessor receiver; + + private IConnection connection; + private IModel channel; + + public RabbitMqService(MessageProcessor receiver) + { + this.receiver = receiver; + } + + public override Task StartAsync(CancellationToken cancellationToken) + { + var connectionFactory = new ConnectionFactory() + { + HostName = Environment.GetEnvironmentVariable("RABBIT_HOSTNAME") ?? "localhost", + UserName = "guest", + Password = "guest", + Port = 5672, + DispatchConsumersAsync = true, + RequestedConnectionTimeout = TimeSpan.FromMilliseconds(3000), + }; + + this.connection = connectionFactory.CreateConnection(); + this.channel = this.connection.CreateModel(); + this.channel.QueueDeclarePassive(QueueName); + + return base.StartAsync(cancellationToken); + } + + public override async Task StopAsync(CancellationToken cancellationToken) + { + await base.StopAsync(cancellationToken); + this.connection.Dispose(); + this.channel.Dispose(); + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + stoppingToken.ThrowIfCancellationRequested(); + + var consumer = new AsyncEventingBasicConsumer(channel); + consumer.Received += async (bc, ea) => + { + await this.receiver.ProcessMessage(ea); + }; + + channel.BasicConsume(queue: QueueName, autoAck: true, consumer: consumer); + + await Task.CompletedTask; + } + } +} From b58a421e9cfde89ef253c846ba48313b7d90c150 Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Wed, 29 Jul 2020 17:16:01 -0700 Subject: [PATCH 07/24] Refactor WebApi separating OpenTelemetry related logic from RabbitMQ boilerplate --- .../WebApi/Controllers/RabbitMqController.cs | 12 ++-- .../WebApi/MessageSender.cs | 62 +++++++++++++++++++ .../WebApi/RabbitMqService.cs | 62 ++----------------- .../MicroserviceExample/WebApi/Startup.cs | 8 ++- 4 files changed, 79 insertions(+), 65 deletions(-) create mode 100644 examples/MicroserviceExample/WebApi/MessageSender.cs diff --git a/examples/MicroserviceExample/WebApi/Controllers/RabbitMqController.cs b/examples/MicroserviceExample/WebApi/Controllers/RabbitMqController.cs index 5c40df477c8..6ad6030c7f8 100644 --- a/examples/MicroserviceExample/WebApi/Controllers/RabbitMqController.cs +++ b/examples/MicroserviceExample/WebApi/Controllers/RabbitMqController.cs @@ -7,19 +7,19 @@ namespace WebApi.Controllers [Route("[controller]")] public class RabbitMqController : ControllerBase { - private readonly ILogger _logger; - private readonly IRabbitMqService _rabbitMqService; + private readonly ILogger logger; + private readonly RabbitMqService rabbitMqService; - public RabbitMqController(ILogger logger, IRabbitMqService rabbitMqService) + public RabbitMqController(ILogger logger, RabbitMqService rabbitMqService) { - _logger = logger; - _rabbitMqService = rabbitMqService; + this.logger = logger; + this.rabbitMqService = rabbitMqService; } [HttpGet] public string Get() { - return _rabbitMqService.PublishMessage(); + return this.rabbitMqService.PublishMessage(); } } } diff --git a/examples/MicroserviceExample/WebApi/MessageSender.cs b/examples/MicroserviceExample/WebApi/MessageSender.cs new file mode 100644 index 00000000000..5f089f426f1 --- /dev/null +++ b/examples/MicroserviceExample/WebApi/MessageSender.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using Microsoft.Extensions.Logging; +using OpenTelemetry.Context.Propagation; +using RabbitMQ.Client; + +namespace WebApi +{ + public class MessageSender + { + private static readonly ITextFormat TextFormat = new TraceContextFormat(); + + private readonly ActivitySource activitySource = new ActivitySource(nameof(MessageSender)); + private readonly ILogger logger; + + public MessageSender(ILogger logger) + { + this.activitySource = new ActivitySource(nameof(MessageSender)); + this.logger = logger; + } + + internal string PublishMessage(IModel channel, string queueName) + { + string activityName = $"{queueName} send"; + using (var activity = activitySource.StartActivity(activityName)) + { + var props = channel.CreateBasicProperties(); + + TextFormat.Inject(activity.Context, props, InjectTraceContextIntoBasicProperties); + + var body = $"Published message: DateTime.Now = {DateTime.Now}."; + + channel.BasicPublish( + exchange: string.Empty, + routingKey: queueName, + basicProperties: props, + body: Encoding.UTF8.GetBytes(body)); + + return body; + } + } + + private void InjectTraceContextIntoBasicProperties(IBasicProperties props, string key, string value) + { + try + { + if (props.Headers == null) + { + props.Headers = new Dictionary(); + } + + props.Headers[key] = value; + } + catch (Exception ex) + { + this.logger.LogError($"Failed to inject trace context: {ex}"); + } + } + } +} diff --git a/examples/MicroserviceExample/WebApi/RabbitMqService.cs b/examples/MicroserviceExample/WebApi/RabbitMqService.cs index d5e22705d90..d25d968ce6e 100644 --- a/examples/MicroserviceExample/WebApi/RabbitMqService.cs +++ b/examples/MicroserviceExample/WebApi/RabbitMqService.cs @@ -1,33 +1,25 @@ using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; using Microsoft.Extensions.Logging; -using OpenTelemetry.Context.Propagation; using RabbitMQ.Client; namespace WebApi { - public interface IRabbitMqService : IDisposable - { - string PublishMessage(); - } - - public class RabbitMqService : IRabbitMqService + public class RabbitMqService { private const string QueueName = "TestQueue"; private readonly ILogger logger; + private readonly MessageSender messageSender; private readonly ConnectionFactory connectionFactory; private readonly IConnection connection; private readonly IModel channel; - private readonly ActivitySource activitySource; - private readonly ITextFormat textFormat; - public RabbitMqService(ILogger logger) + public RabbitMqService(ILogger logger, MessageSender messageSender) { this.logger = logger; + this.messageSender = messageSender; + this.connectionFactory = new ConnectionFactory() { HostName = Environment.GetEnvironmentVariable("RABBIT_HOSTNAME") ?? "localhost", @@ -45,37 +37,13 @@ public RabbitMqService(ILogger logger) exclusive: false, autoDelete: false, arguments: null); - - this.activitySource = new ActivitySource(nameof(RabbitMqService)); - - this.textFormat = new TraceContextFormat(); } public string PublishMessage() { try { - string activityName = $"{nameof(RabbitMqService)}.{nameof(PublishMessage)}"; - using (var activity = activitySource.StartActivity(activityName)) - { - var props = this.channel.CreateBasicProperties(); - props.ContentType = "text/plain"; - props.DeliveryMode = 2; - - textFormat.Inject(activity.Context, props, InjectTraceContextIntoBasicProperties); - - var body = $"Published message. DateTime.Now = {DateTime.Now}."; - - this.channel.BasicPublish( - exchange: string.Empty, - routingKey: QueueName, - basicProperties: props, - body: Encoding.UTF8.GetBytes(body)); - - this.logger.LogInformation($"Published message: {body}."); - - return body; - } + return this.messageSender.PublishMessage(channel, QueueName); } catch (Exception ex) { @@ -88,24 +56,6 @@ public void Dispose() { this.connection.Dispose(); this.channel.Dispose(); - this.activitySource.Dispose(); - } - - private void InjectTraceContextIntoBasicProperties(IBasicProperties props, string key, string value) - { - try - { - if (props.Headers == null) - { - props.Headers = new Dictionary(); - } - - props.Headers[key] = value; - } - catch (Exception ex) - { - this.logger.LogError($"Failed to inject trace context: {ex}"); - } } } } diff --git a/examples/MicroserviceExample/WebApi/Startup.cs b/examples/MicroserviceExample/WebApi/Startup.cs index a64241ef3d2..2202e14db0b 100644 --- a/examples/MicroserviceExample/WebApi/Startup.cs +++ b/examples/MicroserviceExample/WebApi/Startup.cs @@ -21,15 +21,17 @@ public void ConfigureServices(IServiceCollection services) { services.AddControllers(); - services.AddSingleton(); + services.AddSingleton(); + + services.AddSingleton(); services.AddOpenTelemetry((builder) => builder .AddAspNetCoreInstrumentation() - .AddActivitySource(nameof(RabbitMqService)) + .AddActivitySource(nameof(MessageSender)) .UseZipkinExporter(b => { var zipkinHostName = Environment.GetEnvironmentVariable("ZIPKIN_HOSTNAME") ?? "localhost"; - b.ServiceName = "WebApi"; + b.ServiceName = nameof(WebApi); b.Endpoint = new Uri($"http://{zipkinHostName}:9411/api/v2/spans"); })); } From 5248c0446704c1206314578f682cc067734fd82b Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Wed, 29 Jul 2020 19:15:01 -0700 Subject: [PATCH 08/24] Use environment variables for RabbitMQ user/pass --- .../MicroserviceExample/WebApi/RabbitMqService.cs | 6 +++--- .../WorkerService/RabbitMqService.cs | 6 +++--- examples/MicroserviceExample/docker-compose.yml | 11 +++++++++-- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/examples/MicroserviceExample/WebApi/RabbitMqService.cs b/examples/MicroserviceExample/WebApi/RabbitMqService.cs index d25d968ce6e..dbd6e48a31c 100644 --- a/examples/MicroserviceExample/WebApi/RabbitMqService.cs +++ b/examples/MicroserviceExample/WebApi/RabbitMqService.cs @@ -22,9 +22,9 @@ public RabbitMqService(ILogger logger, MessageSender messageSen this.connectionFactory = new ConnectionFactory() { - HostName = Environment.GetEnvironmentVariable("RABBIT_HOSTNAME") ?? "localhost", - UserName = "guest", - Password = "guest", + HostName = Environment.GetEnvironmentVariable("RABBITMQ_HOSTNAME") ?? "localhost", + UserName = Environment.GetEnvironmentVariable("RABBITMQ_DEFAULT_USER") ?? "localhost", + Password = Environment.GetEnvironmentVariable("RABBITMQ_DEFAULT_PASS") ?? "localhost", Port = 5672, RequestedConnectionTimeout = TimeSpan.FromMilliseconds(3000), }; diff --git a/examples/MicroserviceExample/WorkerService/RabbitMqService.cs b/examples/MicroserviceExample/WorkerService/RabbitMqService.cs index af1a6168183..45e4d9ca35a 100644 --- a/examples/MicroserviceExample/WorkerService/RabbitMqService.cs +++ b/examples/MicroserviceExample/WorkerService/RabbitMqService.cs @@ -25,9 +25,9 @@ public override Task StartAsync(CancellationToken cancellationToken) { var connectionFactory = new ConnectionFactory() { - HostName = Environment.GetEnvironmentVariable("RABBIT_HOSTNAME") ?? "localhost", - UserName = "guest", - Password = "guest", + HostName = Environment.GetEnvironmentVariable("RABBITMQ_HOSTNAME") ?? "localhost", + UserName = Environment.GetEnvironmentVariable("RABBITMQ_DEFAULT_USER") ?? "localhost", + Password = Environment.GetEnvironmentVariable("RABBITMQ_DEFAULT_PASS") ?? "localhost", Port = 5672, DispatchConsumersAsync = true, RequestedConnectionTimeout = TimeSpan.FromMilliseconds(3000), diff --git a/examples/MicroserviceExample/docker-compose.yml b/examples/MicroserviceExample/docker-compose.yml index 4c18938a6a4..5897952fccc 100644 --- a/examples/MicroserviceExample/docker-compose.yml +++ b/examples/MicroserviceExample/docker-compose.yml @@ -8,6 +8,9 @@ services: rabbitmq: image: rabbitmq:3-management-alpine + environment: + - RABBITMQ_DEFAULT_USER=guest + - RABBITMQ_DEFAULT_PASS=guest ports: - 5672:5672 - 15672:15672 @@ -17,7 +20,9 @@ services: image: opentelemetry-example-webapi environment: - ASPNETCORE_ENVIRONMENT=Development - - RABBIT_HOSTNAME=rabbitmq + - RABBITMQ_HOSTNAME=rabbitmq + - RABBITMQ_DEFAULT_USER=guest + - RABBITMQ_DEFAULT_PASS=guest - ZIPKIN_HOSTNAME=zipkin ports: - 5000:5000 @@ -30,7 +35,9 @@ services: image: opentelemetry-example-workerservice environment: - DOTNET_ENVIRONMENT=Development - - RABBIT_HOSTNAME=rabbitmq + - RABBITMQ_HOSTNAME=rabbitmq + - RABBITMQ_DEFAULT_USER=guest + - RABBITMQ_DEFAULT_PASS=guest - ZIPKIN_HOSTNAME=zipkin restart: on-failure depends_on: From 834503e9d70e885aa0b73ee9b850a4e548a3e03d Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Wed, 29 Jul 2020 19:29:00 -0700 Subject: [PATCH 09/24] Add some comments --- examples/MicroserviceExample/WebApi/MessageSender.cs | 3 +++ .../MicroserviceExample/WorkerService/MessageProcessor.cs | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/examples/MicroserviceExample/WebApi/MessageSender.cs b/examples/MicroserviceExample/WebApi/MessageSender.cs index 5f089f426f1..674df9e0cf3 100644 --- a/examples/MicroserviceExample/WebApi/MessageSender.cs +++ b/examples/MicroserviceExample/WebApi/MessageSender.cs @@ -23,11 +23,14 @@ public MessageSender(ILogger logger) internal string PublishMessage(IModel channel, string queueName) { + // Start an activity with a name following the semantic convention of the OpenTelemetry messaging specification. + // https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/messaging.md#span-name string activityName = $"{queueName} send"; using (var activity = activitySource.StartActivity(activityName)) { var props = channel.CreateBasicProperties(); + // Inject the ActivityContext into the message headers. TextFormat.Inject(activity.Context, props, InjectTraceContextIntoBasicProperties); var body = $"Published message: DateTime.Now = {DateTime.Now}."; diff --git a/examples/MicroserviceExample/WorkerService/MessageProcessor.cs b/examples/MicroserviceExample/WorkerService/MessageProcessor.cs index 13555e51b74..e074eb0f3d5 100644 --- a/examples/MicroserviceExample/WorkerService/MessageProcessor.cs +++ b/examples/MicroserviceExample/WorkerService/MessageProcessor.cs @@ -41,10 +41,12 @@ public void Dispose() public async Task ProcessMessage(BasicDeliverEventArgs ea) { - var activityName = $"{ea.RoutingKey} receive"; - + // Extract the ActivityContext of the upstream parent from the message headers. var parentContext = TextFormat.Extract(ea.BasicProperties, ExtractTraceContextFromBasicProperties); + // Start an activity with a name following the semantic convention of the OpenTelemetry messaging specification. + // https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/messaging.md#span-name + var activityName = $"{ea.RoutingKey} receive"; using (var activity = ActivitySource.StartActivity(activityName, ActivityKind.Server, parentContext)) { try From 225fadf710f20b8306ab7f7fe286881c01d2328d Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Sat, 1 Aug 2020 08:15:19 -0700 Subject: [PATCH 10/24] Fix RabbitMQ default user/pass --- examples/MicroserviceExample/WebApi/RabbitMqService.cs | 4 ++-- examples/MicroserviceExample/WorkerService/RabbitMqService.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/MicroserviceExample/WebApi/RabbitMqService.cs b/examples/MicroserviceExample/WebApi/RabbitMqService.cs index dbd6e48a31c..6e4622e32f7 100644 --- a/examples/MicroserviceExample/WebApi/RabbitMqService.cs +++ b/examples/MicroserviceExample/WebApi/RabbitMqService.cs @@ -23,8 +23,8 @@ public RabbitMqService(ILogger logger, MessageSender messageSen this.connectionFactory = new ConnectionFactory() { HostName = Environment.GetEnvironmentVariable("RABBITMQ_HOSTNAME") ?? "localhost", - UserName = Environment.GetEnvironmentVariable("RABBITMQ_DEFAULT_USER") ?? "localhost", - Password = Environment.GetEnvironmentVariable("RABBITMQ_DEFAULT_PASS") ?? "localhost", + UserName = Environment.GetEnvironmentVariable("RABBITMQ_DEFAULT_USER") ?? "guest", + Password = Environment.GetEnvironmentVariable("RABBITMQ_DEFAULT_PASS") ?? "guest", Port = 5672, RequestedConnectionTimeout = TimeSpan.FromMilliseconds(3000), }; diff --git a/examples/MicroserviceExample/WorkerService/RabbitMqService.cs b/examples/MicroserviceExample/WorkerService/RabbitMqService.cs index 45e4d9ca35a..cfcd5c2f21f 100644 --- a/examples/MicroserviceExample/WorkerService/RabbitMqService.cs +++ b/examples/MicroserviceExample/WorkerService/RabbitMqService.cs @@ -26,8 +26,8 @@ public override Task StartAsync(CancellationToken cancellationToken) var connectionFactory = new ConnectionFactory() { HostName = Environment.GetEnvironmentVariable("RABBITMQ_HOSTNAME") ?? "localhost", - UserName = Environment.GetEnvironmentVariable("RABBITMQ_DEFAULT_USER") ?? "localhost", - Password = Environment.GetEnvironmentVariable("RABBITMQ_DEFAULT_PASS") ?? "localhost", + UserName = Environment.GetEnvironmentVariable("RABBITMQ_DEFAULT_USER") ?? "guest", + Password = Environment.GetEnvironmentVariable("RABBITMQ_DEFAULT_PASS") ?? "guest", Port = 5672, DispatchConsumersAsync = true, RequestedConnectionTimeout = TimeSpan.FromMilliseconds(3000), From ba436f074b5e7c8935bfb0557a86f0f2c2884eb4 Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Sat, 1 Aug 2020 08:25:03 -0700 Subject: [PATCH 11/24] Use correct ActivityKind --- examples/MicroserviceExample/WebApi/MessageSender.cs | 2 +- examples/MicroserviceExample/WorkerService/MessageProcessor.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/MicroserviceExample/WebApi/MessageSender.cs b/examples/MicroserviceExample/WebApi/MessageSender.cs index 674df9e0cf3..bd45c4cc722 100644 --- a/examples/MicroserviceExample/WebApi/MessageSender.cs +++ b/examples/MicroserviceExample/WebApi/MessageSender.cs @@ -26,7 +26,7 @@ internal string PublishMessage(IModel channel, string queueName) // Start an activity with a name following the semantic convention of the OpenTelemetry messaging specification. // https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/messaging.md#span-name string activityName = $"{queueName} send"; - using (var activity = activitySource.StartActivity(activityName)) + using (var activity = activitySource.StartActivity(activityName, ActivityKind.Producer)) { var props = channel.CreateBasicProperties(); diff --git a/examples/MicroserviceExample/WorkerService/MessageProcessor.cs b/examples/MicroserviceExample/WorkerService/MessageProcessor.cs index e074eb0f3d5..06a46c3701e 100644 --- a/examples/MicroserviceExample/WorkerService/MessageProcessor.cs +++ b/examples/MicroserviceExample/WorkerService/MessageProcessor.cs @@ -47,7 +47,7 @@ public async Task ProcessMessage(BasicDeliverEventArgs ea) // Start an activity with a name following the semantic convention of the OpenTelemetry messaging specification. // https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/messaging.md#span-name var activityName = $"{ea.RoutingKey} receive"; - using (var activity = ActivitySource.StartActivity(activityName, ActivityKind.Server, parentContext)) + using (var activity = ActivitySource.StartActivity(activityName, ActivityKind.Consumer, parentContext)) { try { From 403faaeff648f14399399a8df26224da63fc0239 Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Sat, 1 Aug 2020 08:26:02 -0700 Subject: [PATCH 12/24] Add attributes following messaging specification --- examples/MicroserviceExample/WebApi/MessageSender.cs | 10 ++++++++++ .../WorkerService/MessageProcessor.cs | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/examples/MicroserviceExample/WebApi/MessageSender.cs b/examples/MicroserviceExample/WebApi/MessageSender.cs index bd45c4cc722..d1a753810c6 100644 --- a/examples/MicroserviceExample/WebApi/MessageSender.cs +++ b/examples/MicroserviceExample/WebApi/MessageSender.cs @@ -33,6 +33,16 @@ internal string PublishMessage(IModel channel, string queueName) // Inject the ActivityContext into the message headers. TextFormat.Inject(activity.Context, props, InjectTraceContextIntoBasicProperties); + // These tags are added demonstrating the semantic conventions of the messaging specification + // See: + // * https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/messaging.md#messaging-attributes + // * https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/messaging.md#rabbitmq + string DefaultExchange = string.Empty; + activity.AddTag("messaging.system", "rabbitmq"); + activity.AddTag("messaging.destination_kind", "queue"); + activity.AddTag("messaging.destination", DefaultExchange); + activity.AddTag("messaging.rabbitmq.routing_key", queueName); + var body = $"Published message: DateTime.Now = {DateTime.Now}."; channel.BasicPublish( diff --git a/examples/MicroserviceExample/WorkerService/MessageProcessor.cs b/examples/MicroserviceExample/WorkerService/MessageProcessor.cs index 06a46c3701e..d6708c616a3 100644 --- a/examples/MicroserviceExample/WorkerService/MessageProcessor.cs +++ b/examples/MicroserviceExample/WorkerService/MessageProcessor.cs @@ -55,6 +55,16 @@ public async Task ProcessMessage(BasicDeliverEventArgs ea) activity.AddTag("message", message); + // These tags are added demonstrating the semantic conventions of the messaging specification + // See: + // * https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/messaging.md#messaging-attributes + // * https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/messaging.md#rabbitmq + string DefaultExchange = string.Empty; + activity.AddTag("messaging.system", "rabbitmq"); + activity.AddTag("messaging.destination_kind", "queue"); + activity.AddTag("messaging.destination", DefaultExchange); + activity.AddTag("messaging.rabbitmq.routing_key", ea.RoutingKey); + // Simulate some work await Task.Delay(1000); } From 59da6cd0777e0c225df8fd7f31ac3a825315f2e5 Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Sat, 1 Aug 2020 18:30:40 -0700 Subject: [PATCH 13/24] Clearing a path through the RabbitMQ jungle --- OpenTelemetry.sln | 8 ++ .../{WebApi => }/.dockerignore | 0 .../Utils/Messaging/MessageReceiver.cs | 89 +++++++++++++++++ .../Utils/Messaging/MessageSender.cs | 86 +++++++++++++++++ .../Utils/Messaging/RabbitMqHelper.cs | 74 ++++++++++++++ .../MicroserviceExample/Utils/Utils.csproj | 11 +++ .../WebApi/Controllers/RabbitMqController.cs | 9 +- .../MicroserviceExample/WebApi/Dockerfile | 6 +- .../WebApi/MessageSender.cs | 75 --------------- .../WebApi/RabbitMqService.cs | 61 ------------ .../MicroserviceExample/WebApi/Startup.cs | 3 +- .../MicroserviceExample/WebApi/WebApi.csproj | 4 + .../WorkerService/.dockerignore | 21 ---- .../WorkerService/Dockerfile | 6 +- .../WorkerService/MessageProcessor.cs | 96 ------------------- .../WorkerService/Program.cs | 5 +- .../WorkerService/RabbitMqService.cs | 65 ------------- .../WorkerService/Worker.cs | 52 ++++++++++ .../WorkerService/WorkerService.csproj | 4 + .../MicroserviceExample/docker-compose.yml | 8 +- 20 files changed, 347 insertions(+), 336 deletions(-) rename examples/MicroserviceExample/{WebApi => }/.dockerignore (100%) create mode 100644 examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs create mode 100644 examples/MicroserviceExample/Utils/Messaging/MessageSender.cs create mode 100644 examples/MicroserviceExample/Utils/Messaging/RabbitMqHelper.cs create mode 100644 examples/MicroserviceExample/Utils/Utils.csproj delete mode 100644 examples/MicroserviceExample/WebApi/MessageSender.cs delete mode 100644 examples/MicroserviceExample/WebApi/RabbitMqService.cs delete mode 100644 examples/MicroserviceExample/WorkerService/.dockerignore delete mode 100644 examples/MicroserviceExample/WorkerService/MessageProcessor.cs delete mode 100644 examples/MicroserviceExample/WorkerService/RabbitMqService.cs create mode 100644 examples/MicroserviceExample/WorkerService/Worker.cs diff --git a/OpenTelemetry.sln b/OpenTelemetry.sln index c792ccc85f1..4401f1b2541 100644 --- a/OpenTelemetry.sln +++ b/OpenTelemetry.sln @@ -157,6 +157,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{7C87CAF9-7 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MicroserviceExample", "MicroserviceExample", "{4D492D62-5150-45F9-817F-C99562E364E2}" ProjectSection(SolutionItems) = preProject + examples\MicroserviceExample\.dockerignore = examples\MicroserviceExample\.dockerignore examples\MicroserviceExample\docker-compose.yml = examples\MicroserviceExample\docker-compose.yml examples\MicroserviceExample\README.md = examples\MicroserviceExample\README.md EndProjectSection @@ -165,6 +166,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApi", "examples\Microser EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkerService", "examples\MicroserviceExample\WorkerService\WorkerService.csproj", "{FA7A6F67-1F2F-4855-890D-51B5829578A9}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Utils", "examples\MicroserviceExample\Utils\Utils.csproj", "{5435517C-AEC5-4182-87AE-14E13D31525F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -323,6 +326,10 @@ Global {FA7A6F67-1F2F-4855-890D-51B5829578A9}.Debug|Any CPU.Build.0 = Debug|Any CPU {FA7A6F67-1F2F-4855-890D-51B5829578A9}.Release|Any CPU.ActiveCfg = Release|Any CPU {FA7A6F67-1F2F-4855-890D-51B5829578A9}.Release|Any CPU.Build.0 = Release|Any CPU + {5435517C-AEC5-4182-87AE-14E13D31525F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5435517C-AEC5-4182-87AE-14E13D31525F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5435517C-AEC5-4182-87AE-14E13D31525F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5435517C-AEC5-4182-87AE-14E13D31525F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -341,6 +348,7 @@ Global {4D492D62-5150-45F9-817F-C99562E364E2} = {E359BB2B-9AEC-497D-B321-7DF2450C3B8E} {07336602-860B-4975-95DD-405D19C00901} = {4D492D62-5150-45F9-817F-C99562E364E2} {FA7A6F67-1F2F-4855-890D-51B5829578A9} = {4D492D62-5150-45F9-817F-C99562E364E2} + {5435517C-AEC5-4182-87AE-14E13D31525F} = {4D492D62-5150-45F9-817F-C99562E364E2} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {55639B5C-0770-4A22-AB56-859604650521} diff --git a/examples/MicroserviceExample/WebApi/.dockerignore b/examples/MicroserviceExample/.dockerignore similarity index 100% rename from examples/MicroserviceExample/WebApi/.dockerignore rename to examples/MicroserviceExample/.dockerignore diff --git a/examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs b/examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs new file mode 100644 index 00000000000..f01a15f41ab --- /dev/null +++ b/examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading; +using Microsoft.Extensions.Logging; +using OpenTelemetry.Context.Propagation; +using RabbitMQ.Client; +using RabbitMQ.Client.Events; + +namespace Utils.Messaging +{ + public class MessageReceiver : IDisposable + { + private ILogger logger; + private readonly IConnection connection; + private readonly IModel channel; + + private static readonly ActivitySource ActivitySource = new ActivitySource(nameof(MessageReceiver)); + private static readonly ITextFormat TextFormat = new TraceContextFormat(); + + public MessageReceiver(ILogger logger) + { + this.logger = logger; + this.connection = RabbitMqHelper.CreateConnection(); + this.channel = RabbitMqHelper.CreateModelAndDeclareTestQueue(this.connection, declarePassive: true); + } + + public void Dispose() + { + this.channel.Dispose(); + this.connection.Dispose(); + } + + public void StartConsumer() + { + RabbitMqHelper.StartConsumer(this.channel, this.ReceiveMessage); + } + + public void ReceiveMessage(BasicDeliverEventArgs ea) + { + // Extract the ActivityContext of the upstream parent from the message headers. + var parentContext = TextFormat.Extract(ea.BasicProperties, ExtractTraceContextFromBasicProperties); + + // Start an activity with a name following the semantic convention of the OpenTelemetry messaging specification. + // https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/messaging.md#span-name + var activityName = $"{ea.RoutingKey} receive"; + + using (var activity = ActivitySource.StartActivity(activityName, ActivityKind.Consumer, parentContext)) + { + try + { + var message = Encoding.UTF8.GetString(ea.Body.Span.ToArray()); + + activity.AddTag("message", message); + + // The OpenTelemetry messaging specification defines a number of attributes. These attributes are added here. + RabbitMqHelper.AddMessagingTags(activity); + + // Simulate some work + Thread.Sleep(1000); + } + catch (Exception ex) + { + this.logger.LogError(ex, "Message processing failed."); + } + } + } + + private IEnumerable ExtractTraceContextFromBasicProperties(IBasicProperties props, string key) + { + try + { + if (props.Headers.TryGetValue(key, out var value)) + { + var bytes = value as byte[]; + return new[] { Encoding.UTF8.GetString(bytes) }; + } + } + catch (Exception ex) + { + this.logger.LogError(ex, "Failed to extract trace context: {ex}"); + } + + return Enumerable.Empty(); + } + } +} diff --git a/examples/MicroserviceExample/Utils/Messaging/MessageSender.cs b/examples/MicroserviceExample/Utils/Messaging/MessageSender.cs new file mode 100644 index 00000000000..2e85268afda --- /dev/null +++ b/examples/MicroserviceExample/Utils/Messaging/MessageSender.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using Microsoft.Extensions.Logging; +using OpenTelemetry.Context.Propagation; +using RabbitMQ.Client; + +namespace Utils.Messaging +{ + public class MessageSender : IDisposable + { + private readonly ILogger logger; + private readonly IConnection connection; + private readonly IModel channel; + + private static readonly ActivitySource activitySource = new ActivitySource(nameof(MessageSender)); + private static readonly ITextFormat TextFormat = new TraceContextFormat(); + + public MessageSender(ILogger logger) + { + this.logger = logger; + this.connection = RabbitMqHelper.CreateConnection(); + this.channel = RabbitMqHelper.CreateModelAndDeclareTestQueue(this.connection); + } + + public void Dispose() + { + this.channel.Dispose(); + this.connection.Dispose(); + } + + public string SendMessage() + { + try + { + // Start an activity with a name following the semantic convention of the OpenTelemetry messaging specification. + // https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/messaging.md#span-name + var activityName = $"{RabbitMqHelper.TestQueueName} send"; + + using (var activity = activitySource.StartActivity(activityName, ActivityKind.Producer)) + { + var props = channel.CreateBasicProperties(); + + // Inject the ActivityContext into the message headers to propagate trace context to the receiving service. + TextFormat.Inject(activity.Context, props, InjectTraceContextIntoBasicProperties); + + // The OpenTelemetry messaging specification defines a number of attributes. These attributes are added here. + RabbitMqHelper.AddMessagingTags(activity); + + var body = $"Published message: DateTime.Now = {DateTime.Now}."; + + channel.BasicPublish( + exchange: RabbitMqHelper.DefaultExchangeName, + routingKey: RabbitMqHelper.TestQueueName, + basicProperties: props, + body: Encoding.UTF8.GetBytes(body)); + + return body; + } + } + catch (Exception ex) + { + this.logger.LogError(ex, "Message publishing failed."); + throw; + } + } + + private void InjectTraceContextIntoBasicProperties(IBasicProperties props, string key, string value) + { + try + { + if (props.Headers == null) + { + props.Headers = new Dictionary(); + } + + props.Headers[key] = value; + } + catch (Exception ex) + { + this.logger.LogError(ex, "Failed to inject trace context."); + } + } + } +} diff --git a/examples/MicroserviceExample/Utils/Messaging/RabbitMqHelper.cs b/examples/MicroserviceExample/Utils/Messaging/RabbitMqHelper.cs new file mode 100644 index 00000000000..7d9efee2718 --- /dev/null +++ b/examples/MicroserviceExample/Utils/Messaging/RabbitMqHelper.cs @@ -0,0 +1,74 @@ +using System; +using System.Diagnostics; +using RabbitMQ.Client; +using RabbitMQ.Client.Events; + +namespace Utils.Messaging +{ + public static class RabbitMqHelper + { + public const string DefaultExchangeName = ""; + public const string TestQueueName = "TestQueue"; + + private static readonly ConnectionFactory ConnectionFactory; + + static RabbitMqHelper() + { + ConnectionFactory = new ConnectionFactory() + { + HostName = Environment.GetEnvironmentVariable("RABBITMQ_HOSTNAME") ?? "localhost", + UserName = Environment.GetEnvironmentVariable("RABBITMQ_DEFAULT_USER") ?? "guest", + Password = Environment.GetEnvironmentVariable("RABBITMQ_DEFAULT_PASS") ?? "guest", + Port = 5672, + RequestedConnectionTimeout = TimeSpan.FromMilliseconds(3000), + }; + } + + public static IConnection CreateConnection() + { + return ConnectionFactory.CreateConnection(); + } + + public static IModel CreateModelAndDeclareTestQueue(IConnection connection, bool declarePassive = false) + { + var channel = connection.CreateModel(); + + if (declarePassive) + { + channel.QueueDeclarePassive(TestQueueName); + } + else + { + channel.QueueDeclare( + queue: TestQueueName, + durable: false, + exclusive: false, + autoDelete: false, + arguments: null); + } + + return channel; + } + + public static void StartConsumer(IModel channel, Action processMessage) + { + var consumer = new EventingBasicConsumer(channel); + + consumer.Received += (bc, ea) => processMessage(ea); + + channel.BasicConsume(queue: TestQueueName, autoAck: true, consumer: consumer); + } + + public static void AddMessagingTags(Activity activity) + { + // These tags are added demonstrating the semantic conventions of the OpenTelemetry messaging specification + // See: + // * https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/messaging.md#messaging-attributes + // * https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/messaging.md#rabbitmq + activity.AddTag("messaging.system", "rabbitmq"); + activity.AddTag("messaging.destination_kind", "queue"); + activity.AddTag("messaging.destination", DefaultExchangeName); + activity.AddTag("messaging.rabbitmq.routing_key", TestQueueName); + } + } +} diff --git a/examples/MicroserviceExample/Utils/Utils.csproj b/examples/MicroserviceExample/Utils/Utils.csproj new file mode 100644 index 00000000000..e01b3db3251 --- /dev/null +++ b/examples/MicroserviceExample/Utils/Utils.csproj @@ -0,0 +1,11 @@ + + + netstandard2.0 + + + + + + + + diff --git a/examples/MicroserviceExample/WebApi/Controllers/RabbitMqController.cs b/examples/MicroserviceExample/WebApi/Controllers/RabbitMqController.cs index 6ad6030c7f8..befbe702ea5 100644 --- a/examples/MicroserviceExample/WebApi/Controllers/RabbitMqController.cs +++ b/examples/MicroserviceExample/WebApi/Controllers/RabbitMqController.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; +using Utils.Messaging; namespace WebApi.Controllers { @@ -8,18 +9,18 @@ namespace WebApi.Controllers public class RabbitMqController : ControllerBase { private readonly ILogger logger; - private readonly RabbitMqService rabbitMqService; + private readonly MessageSender messageSender; - public RabbitMqController(ILogger logger, RabbitMqService rabbitMqService) + public RabbitMqController(ILogger logger, MessageSender messageSender) { this.logger = logger; - this.rabbitMqService = rabbitMqService; + this.messageSender = messageSender; } [HttpGet] public string Get() { - return this.rabbitMqService.PublishMessage(); + return this.messageSender.SendMessage(); } } } diff --git a/examples/MicroserviceExample/WebApi/Dockerfile b/examples/MicroserviceExample/WebApi/Dockerfile index 8a4bd1bc9a5..7ef8057f875 100644 --- a/examples/MicroserviceExample/WebApi/Dockerfile +++ b/examples/MicroserviceExample/WebApi/Dockerfile @@ -1,10 +1,8 @@ FROM mcr.microsoft.com/dotnet/core/sdk:3.1-alpine AS build WORKDIR /app -COPY ./*.csproj ./ -RUN dotnet restore -COPY . . -RUN dotnet publish -c Release -o /out --no-restore +COPY . ./ +RUN dotnet publish WebApi -c Release -o /out FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-alpine AS runtime WORKDIR /app diff --git a/examples/MicroserviceExample/WebApi/MessageSender.cs b/examples/MicroserviceExample/WebApi/MessageSender.cs deleted file mode 100644 index d1a753810c6..00000000000 --- a/examples/MicroserviceExample/WebApi/MessageSender.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; -using Microsoft.Extensions.Logging; -using OpenTelemetry.Context.Propagation; -using RabbitMQ.Client; - -namespace WebApi -{ - public class MessageSender - { - private static readonly ITextFormat TextFormat = new TraceContextFormat(); - - private readonly ActivitySource activitySource = new ActivitySource(nameof(MessageSender)); - private readonly ILogger logger; - - public MessageSender(ILogger logger) - { - this.activitySource = new ActivitySource(nameof(MessageSender)); - this.logger = logger; - } - - internal string PublishMessage(IModel channel, string queueName) - { - // Start an activity with a name following the semantic convention of the OpenTelemetry messaging specification. - // https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/messaging.md#span-name - string activityName = $"{queueName} send"; - using (var activity = activitySource.StartActivity(activityName, ActivityKind.Producer)) - { - var props = channel.CreateBasicProperties(); - - // Inject the ActivityContext into the message headers. - TextFormat.Inject(activity.Context, props, InjectTraceContextIntoBasicProperties); - - // These tags are added demonstrating the semantic conventions of the messaging specification - // See: - // * https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/messaging.md#messaging-attributes - // * https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/messaging.md#rabbitmq - string DefaultExchange = string.Empty; - activity.AddTag("messaging.system", "rabbitmq"); - activity.AddTag("messaging.destination_kind", "queue"); - activity.AddTag("messaging.destination", DefaultExchange); - activity.AddTag("messaging.rabbitmq.routing_key", queueName); - - var body = $"Published message: DateTime.Now = {DateTime.Now}."; - - channel.BasicPublish( - exchange: string.Empty, - routingKey: queueName, - basicProperties: props, - body: Encoding.UTF8.GetBytes(body)); - - return body; - } - } - - private void InjectTraceContextIntoBasicProperties(IBasicProperties props, string key, string value) - { - try - { - if (props.Headers == null) - { - props.Headers = new Dictionary(); - } - - props.Headers[key] = value; - } - catch (Exception ex) - { - this.logger.LogError($"Failed to inject trace context: {ex}"); - } - } - } -} diff --git a/examples/MicroserviceExample/WebApi/RabbitMqService.cs b/examples/MicroserviceExample/WebApi/RabbitMqService.cs deleted file mode 100644 index 6e4622e32f7..00000000000 --- a/examples/MicroserviceExample/WebApi/RabbitMqService.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using Microsoft.Extensions.Logging; -using RabbitMQ.Client; - -namespace WebApi -{ - public class RabbitMqService - { - private const string QueueName = "TestQueue"; - - private readonly ILogger logger; - private readonly MessageSender messageSender; - private readonly ConnectionFactory connectionFactory; - private readonly IConnection connection; - private readonly IModel channel; - - public RabbitMqService(ILogger logger, MessageSender messageSender) - { - this.logger = logger; - - this.messageSender = messageSender; - - this.connectionFactory = new ConnectionFactory() - { - HostName = Environment.GetEnvironmentVariable("RABBITMQ_HOSTNAME") ?? "localhost", - UserName = Environment.GetEnvironmentVariable("RABBITMQ_DEFAULT_USER") ?? "guest", - Password = Environment.GetEnvironmentVariable("RABBITMQ_DEFAULT_PASS") ?? "guest", - Port = 5672, - RequestedConnectionTimeout = TimeSpan.FromMilliseconds(3000), - }; - - this.connection = connectionFactory.CreateConnection(); - this.channel = connection.CreateModel(); - channel.QueueDeclare( - queue: QueueName, - durable: false, - exclusive: false, - autoDelete: false, - arguments: null); - } - - public string PublishMessage() - { - try - { - return this.messageSender.PublishMessage(channel, QueueName); - } - catch (Exception ex) - { - this.logger.LogError(ex.ToString()); - throw; - } - } - - public void Dispose() - { - this.connection.Dispose(); - this.channel.Dispose(); - } - } -} diff --git a/examples/MicroserviceExample/WebApi/Startup.cs b/examples/MicroserviceExample/WebApi/Startup.cs index 2202e14db0b..ef3ac251725 100644 --- a/examples/MicroserviceExample/WebApi/Startup.cs +++ b/examples/MicroserviceExample/WebApi/Startup.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using OpenTelemetry.Trace; +using Utils.Messaging; namespace WebApi { @@ -21,8 +22,6 @@ public void ConfigureServices(IServiceCollection services) { services.AddControllers(); - services.AddSingleton(); - services.AddSingleton(); services.AddOpenTelemetry((builder) => builder diff --git a/examples/MicroserviceExample/WebApi/WebApi.csproj b/examples/MicroserviceExample/WebApi/WebApi.csproj index 0d95d4d0d1f..bb617b03ff9 100644 --- a/examples/MicroserviceExample/WebApi/WebApi.csproj +++ b/examples/MicroserviceExample/WebApi/WebApi.csproj @@ -9,4 +9,8 @@ + + + + diff --git a/examples/MicroserviceExample/WorkerService/.dockerignore b/examples/MicroserviceExample/WorkerService/.dockerignore deleted file mode 100644 index ef449543015..00000000000 --- a/examples/MicroserviceExample/WorkerService/.dockerignore +++ /dev/null @@ -1,21 +0,0 @@ -**/.classpath -**/.dockerignore -**/.env -**/.git -**/.gitignore -**/.project -**/.settings -**/.toolstarget -**/.vs -**/.vscode -**/*.*proj.user -**/*.dbmdl -**/*.jfm -**/docker-compose* -**/Dockerfile* -**/bin -**/obj -**/*.yaml -**/*.yml -**/*.md -**/*.ps1 diff --git a/examples/MicroserviceExample/WorkerService/Dockerfile b/examples/MicroserviceExample/WorkerService/Dockerfile index 2aa41977dd3..1ea09a3afc5 100644 --- a/examples/MicroserviceExample/WorkerService/Dockerfile +++ b/examples/MicroserviceExample/WorkerService/Dockerfile @@ -1,10 +1,8 @@ FROM mcr.microsoft.com/dotnet/core/sdk:3.1-alpine AS build WORKDIR /app -COPY ./*.csproj ./ -RUN dotnet restore -COPY . . -RUN dotnet publish -c Release -o /out --no-restore +COPY . ./ +RUN dotnet publish WorkerService -c Release -o /out FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-alpine AS runtime WORKDIR /app diff --git a/examples/MicroserviceExample/WorkerService/MessageProcessor.cs b/examples/MicroserviceExample/WorkerService/MessageProcessor.cs deleted file mode 100644 index d6708c616a3..00000000000 --- a/examples/MicroserviceExample/WorkerService/MessageProcessor.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using OpenTelemetry; -using OpenTelemetry.Context.Propagation; -using OpenTelemetry.Trace; -using RabbitMQ.Client; -using RabbitMQ.Client.Events; - -namespace WorkerService -{ - public class MessageProcessor : IDisposable - { - private static readonly ActivitySource ActivitySource = new ActivitySource(nameof(MessageProcessor)); - private static readonly ITextFormat TextFormat = new TraceContextFormat(); - - private readonly TracerProvider tracerProvider; - - public MessageProcessor() - { - this.tracerProvider = Sdk.CreateTracerProvider((builder) => - { - builder - .AddActivitySource(nameof(MessageProcessor)) - .UseZipkinExporter(b => - { - var zipkinHostName = Environment.GetEnvironmentVariable("ZIPKIN_HOSTNAME") ?? "localhost"; - b.ServiceName = nameof(WorkerService); - b.Endpoint = new Uri($"http://{zipkinHostName}:9411/api/v2/spans"); - }); - }); - } - - public void Dispose() - { - this.tracerProvider.Dispose(); - } - - public async Task ProcessMessage(BasicDeliverEventArgs ea) - { - // Extract the ActivityContext of the upstream parent from the message headers. - var parentContext = TextFormat.Extract(ea.BasicProperties, ExtractTraceContextFromBasicProperties); - - // Start an activity with a name following the semantic convention of the OpenTelemetry messaging specification. - // https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/messaging.md#span-name - var activityName = $"{ea.RoutingKey} receive"; - using (var activity = ActivitySource.StartActivity(activityName, ActivityKind.Consumer, parentContext)) - { - try - { - var message = Encoding.UTF8.GetString(ea.Body.Span); - - activity.AddTag("message", message); - - // These tags are added demonstrating the semantic conventions of the messaging specification - // See: - // * https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/messaging.md#messaging-attributes - // * https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/messaging.md#rabbitmq - string DefaultExchange = string.Empty; - activity.AddTag("messaging.system", "rabbitmq"); - activity.AddTag("messaging.destination_kind", "queue"); - activity.AddTag("messaging.destination", DefaultExchange); - activity.AddTag("messaging.rabbitmq.routing_key", ea.RoutingKey); - - // Simulate some work - await Task.Delay(1000); - } - catch (Exception ex) - { - Console.WriteLine(ex); - } - } - } - - private static IEnumerable ExtractTraceContextFromBasicProperties(IBasicProperties props, string key) - { - try - { - if (props.Headers.TryGetValue(key, out object value)) - { - var bytes = value as byte[]; - return new[] { Encoding.UTF8.GetString(bytes) }; - } - } - catch (Exception ex) - { - Console.WriteLine($"Failed to extract trace context: {ex}"); - } - - return Enumerable.Empty(); - } - } -} diff --git a/examples/MicroserviceExample/WorkerService/Program.cs b/examples/MicroserviceExample/WorkerService/Program.cs index e249ae59a53..9499477f2eb 100644 --- a/examples/MicroserviceExample/WorkerService/Program.cs +++ b/examples/MicroserviceExample/WorkerService/Program.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using OpenTelemetry.Trace; +using Utils.Messaging; namespace WorkerService { @@ -16,9 +17,9 @@ public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureServices((hostContext, services) => { - services.AddHostedService(); + services.AddHostedService(); - services.AddSingleton(); + services.AddSingleton(); // TODO: Determine if this can be done here in a WorkerService. It does not seem to work... doing this in the RabbitMqConsumer for now. // services.AddOpenTelemetry((builder) => diff --git a/examples/MicroserviceExample/WorkerService/RabbitMqService.cs b/examples/MicroserviceExample/WorkerService/RabbitMqService.cs deleted file mode 100644 index cfcd5c2f21f..00000000000 --- a/examples/MicroserviceExample/WorkerService/RabbitMqService.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Hosting; -using RabbitMQ.Client; -using RabbitMQ.Client.Events; - -namespace WorkerService -{ - public partial class RabbitMqService : BackgroundService - { - private const string QueueName = "TestQueue"; - - private readonly MessageProcessor receiver; - - private IConnection connection; - private IModel channel; - - public RabbitMqService(MessageProcessor receiver) - { - this.receiver = receiver; - } - - public override Task StartAsync(CancellationToken cancellationToken) - { - var connectionFactory = new ConnectionFactory() - { - HostName = Environment.GetEnvironmentVariable("RABBITMQ_HOSTNAME") ?? "localhost", - UserName = Environment.GetEnvironmentVariable("RABBITMQ_DEFAULT_USER") ?? "guest", - Password = Environment.GetEnvironmentVariable("RABBITMQ_DEFAULT_PASS") ?? "guest", - Port = 5672, - DispatchConsumersAsync = true, - RequestedConnectionTimeout = TimeSpan.FromMilliseconds(3000), - }; - - this.connection = connectionFactory.CreateConnection(); - this.channel = this.connection.CreateModel(); - this.channel.QueueDeclarePassive(QueueName); - - return base.StartAsync(cancellationToken); - } - - public override async Task StopAsync(CancellationToken cancellationToken) - { - await base.StopAsync(cancellationToken); - this.connection.Dispose(); - this.channel.Dispose(); - } - - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - stoppingToken.ThrowIfCancellationRequested(); - - var consumer = new AsyncEventingBasicConsumer(channel); - consumer.Received += async (bc, ea) => - { - await this.receiver.ProcessMessage(ea); - }; - - channel.BasicConsume(queue: QueueName, autoAck: true, consumer: consumer); - - await Task.CompletedTask; - } - } -} diff --git a/examples/MicroserviceExample/WorkerService/Worker.cs b/examples/MicroserviceExample/WorkerService/Worker.cs new file mode 100644 index 00000000000..bae5bc73e77 --- /dev/null +++ b/examples/MicroserviceExample/WorkerService/Worker.cs @@ -0,0 +1,52 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using OpenTelemetry; +using OpenTelemetry.Trace; +using Utils.Messaging; + +namespace WorkerService +{ + public partial class Worker : BackgroundService + { + private readonly MessageReceiver messageReceiver; + private readonly TracerProvider tracerProvider; + + public Worker(MessageReceiver messageReceiver) + { + this.messageReceiver = messageReceiver; + + this.tracerProvider = Sdk.CreateTracerProvider((builder) => + { + builder + .AddActivitySource(nameof(MessageReceiver)) + .UseZipkinExporter(b => + { + var zipkinHostName = Environment.GetEnvironmentVariable("ZIPKIN_HOSTNAME") ?? "localhost"; + b.ServiceName = nameof(WorkerService); + b.Endpoint = new Uri($"http://{zipkinHostName}:9411/api/v2/spans"); + }); + }); + } + + public override Task StartAsync(CancellationToken cancellationToken) + { + return base.StartAsync(cancellationToken); + } + + public override async Task StopAsync(CancellationToken cancellationToken) + { + await base.StopAsync(cancellationToken); + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + stoppingToken.ThrowIfCancellationRequested(); + + this.messageReceiver.StartConsumer(); + + await Task.CompletedTask; + } + } +} diff --git a/examples/MicroserviceExample/WorkerService/WorkerService.csproj b/examples/MicroserviceExample/WorkerService/WorkerService.csproj index 86b66338188..fb44a849e30 100644 --- a/examples/MicroserviceExample/WorkerService/WorkerService.csproj +++ b/examples/MicroserviceExample/WorkerService/WorkerService.csproj @@ -10,4 +10,8 @@ + + + + diff --git a/examples/MicroserviceExample/docker-compose.yml b/examples/MicroserviceExample/docker-compose.yml index 5897952fccc..9c75b96941c 100644 --- a/examples/MicroserviceExample/docker-compose.yml +++ b/examples/MicroserviceExample/docker-compose.yml @@ -16,7 +16,9 @@ services: - 15672:15672 webapi: - build: ./WebApi + build: + context: . + dockerfile: ./WebApi/Dockerfile image: opentelemetry-example-webapi environment: - ASPNETCORE_ENVIRONMENT=Development @@ -31,7 +33,9 @@ services: - zipkin workerservice: - build: ./WorkerService + build: + context: . + dockerfile: ./WorkerService/Dockerfile image: opentelemetry-example-workerservice environment: - DOTNET_ENVIRONMENT=Development From 807d0ea7ef3a49f6e743eab4b69ae2b9a71cd07e Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Thu, 6 Aug 2020 12:58:03 -0700 Subject: [PATCH 14/24] Log something when sending/receiving a message --- examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs | 2 ++ examples/MicroserviceExample/Utils/Messaging/MessageSender.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs b/examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs index 2725e6786c7..f6d055f7631 100644 --- a/examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs +++ b/examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs @@ -69,6 +69,8 @@ public void ReceiveMessage(BasicDeliverEventArgs ea) { var message = Encoding.UTF8.GetString(ea.Body.Span.ToArray()); + this.logger.LogInformation($"Message received: [{message}]"); + activity.AddTag("message", message); // The OpenTelemetry messaging specification defines a number of attributes. These attributes are added here. diff --git a/examples/MicroserviceExample/Utils/Messaging/MessageSender.cs b/examples/MicroserviceExample/Utils/Messaging/MessageSender.cs index b0556347a3c..c16f89fa3a8 100644 --- a/examples/MicroserviceExample/Utils/Messaging/MessageSender.cs +++ b/examples/MicroserviceExample/Utils/Messaging/MessageSender.cs @@ -72,6 +72,8 @@ public string SendMessage() basicProperties: props, body: Encoding.UTF8.GetBytes(body)); + this.logger.LogInformation($"Message sent: [{body}]"); + return body; } } From c2d0484a34d556f4aae8acbabeae7b986bc03626 Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Thu, 6 Aug 2020 15:17:25 -0700 Subject: [PATCH 15/24] Code style clean up --- .../Utils/Messaging/MessageReceiver.cs | 10 +++++----- .../Utils/Messaging/MessageSender.cs | 14 +++++++------- examples/MicroserviceExample/WebApi/Startup.cs | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs b/examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs index f6d055f7631..3ea9782a64d 100644 --- a/examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs +++ b/examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs @@ -29,13 +29,13 @@ namespace Utils.Messaging { public class MessageReceiver : IDisposable { - private ILogger logger; - private readonly IConnection connection; - private readonly IModel channel; - private static readonly ActivitySource ActivitySource = new ActivitySource(nameof(MessageReceiver)); private static readonly ITextFormat TextFormat = new TraceContextFormat(); + private readonly ILogger logger; + private readonly IConnection connection; + private readonly IModel channel; + public MessageReceiver(ILogger logger) { this.logger = logger; @@ -57,7 +57,7 @@ public void StartConsumer() public void ReceiveMessage(BasicDeliverEventArgs ea) { // Extract the ActivityContext of the upstream parent from the message headers. - var parentContext = TextFormat.Extract(ea.BasicProperties, ExtractTraceContextFromBasicProperties); + var parentContext = TextFormat.Extract(ea.BasicProperties, this.ExtractTraceContextFromBasicProperties); // Start an activity with a name following the semantic convention of the OpenTelemetry messaging specification. // https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/messaging.md#span-name diff --git a/examples/MicroserviceExample/Utils/Messaging/MessageSender.cs b/examples/MicroserviceExample/Utils/Messaging/MessageSender.cs index c16f89fa3a8..5f43c4ce6e2 100644 --- a/examples/MicroserviceExample/Utils/Messaging/MessageSender.cs +++ b/examples/MicroserviceExample/Utils/Messaging/MessageSender.cs @@ -26,13 +26,13 @@ namespace Utils.Messaging { public class MessageSender : IDisposable { + private static readonly ActivitySource ActivitySource = new ActivitySource(nameof(MessageSender)); + private static readonly ITextFormat TextFormat = new TraceContextFormat(); + private readonly ILogger logger; private readonly IConnection connection; private readonly IModel channel; - private static readonly ActivitySource activitySource = new ActivitySource(nameof(MessageSender)); - private static readonly ITextFormat TextFormat = new TraceContextFormat(); - public MessageSender(ILogger logger) { this.logger = logger; @@ -54,19 +54,19 @@ public string SendMessage() // https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/messaging.md#span-name var activityName = $"{RabbitMqHelper.TestQueueName} send"; - using (var activity = activitySource.StartActivity(activityName, ActivityKind.Producer)) + using (var activity = ActivitySource.StartActivity(activityName, ActivityKind.Producer)) { - var props = channel.CreateBasicProperties(); + var props = this.channel.CreateBasicProperties(); // Inject the ActivityContext into the message headers to propagate trace context to the receiving service. - TextFormat.Inject(activity.Context, props, InjectTraceContextIntoBasicProperties); + TextFormat.Inject(activity.Context, props, this.InjectTraceContextIntoBasicProperties); // The OpenTelemetry messaging specification defines a number of attributes. These attributes are added here. RabbitMqHelper.AddMessagingTags(activity); var body = $"Published message: DateTime.Now = {DateTime.Now}."; - channel.BasicPublish( + this.channel.BasicPublish( exchange: RabbitMqHelper.DefaultExchangeName, routingKey: RabbitMqHelper.TestQueueName, basicProperties: props, diff --git a/examples/MicroserviceExample/WebApi/Startup.cs b/examples/MicroserviceExample/WebApi/Startup.cs index 9612d442a4b..eee59c30d1c 100644 --- a/examples/MicroserviceExample/WebApi/Startup.cs +++ b/examples/MicroserviceExample/WebApi/Startup.cs @@ -29,7 +29,7 @@ public class Startup { public Startup(IConfiguration configuration) { - Configuration = configuration; + this.Configuration = configuration; } public IConfiguration Configuration { get; } From 413a56bdb2e1c09ae9a9aa610d5637e6cb59c0f8 Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Thu, 6 Aug 2020 15:52:03 -0700 Subject: [PATCH 16/24] Default logging to info level --- examples/MicroserviceExample/WorkerService/appsettings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/MicroserviceExample/WorkerService/appsettings.json b/examples/MicroserviceExample/WorkerService/appsettings.json index 6727805c62b..8983e0fc1c5 100644 --- a/examples/MicroserviceExample/WorkerService/appsettings.json +++ b/examples/MicroserviceExample/WorkerService/appsettings.json @@ -1,7 +1,7 @@ { "Logging": { "LogLevel": { - "Default": "Warning", + "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } From de660c69f9878b549802f59d104fdb3c77299a61 Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Thu, 6 Aug 2020 15:55:59 -0700 Subject: [PATCH 17/24] Create queue if it does not exist --- .../Utils/Messaging/MessageReceiver.cs | 2 +- .../Utils/Messaging/RabbitMqHelper.cs | 21 +++++++------------ 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs b/examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs index 3ea9782a64d..bd4996ad308 100644 --- a/examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs +++ b/examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs @@ -40,7 +40,7 @@ public MessageReceiver(ILogger logger) { this.logger = logger; this.connection = RabbitMqHelper.CreateConnection(); - this.channel = RabbitMqHelper.CreateModelAndDeclareTestQueue(this.connection, declarePassive: true); + this.channel = RabbitMqHelper.CreateModelAndDeclareTestQueue(this.connection); } public void Dispose() diff --git a/examples/MicroserviceExample/Utils/Messaging/RabbitMqHelper.cs b/examples/MicroserviceExample/Utils/Messaging/RabbitMqHelper.cs index 24556d4099f..9738e6c2963 100644 --- a/examples/MicroserviceExample/Utils/Messaging/RabbitMqHelper.cs +++ b/examples/MicroserviceExample/Utils/Messaging/RabbitMqHelper.cs @@ -45,23 +45,16 @@ public static IConnection CreateConnection() return ConnectionFactory.CreateConnection(); } - public static IModel CreateModelAndDeclareTestQueue(IConnection connection, bool declarePassive = false) + public static IModel CreateModelAndDeclareTestQueue(IConnection connection) { var channel = connection.CreateModel(); - if (declarePassive) - { - channel.QueueDeclarePassive(TestQueueName); - } - else - { - channel.QueueDeclare( - queue: TestQueueName, - durable: false, - exclusive: false, - autoDelete: false, - arguments: null); - } + channel.QueueDeclare( + queue: TestQueueName, + durable: false, + exclusive: false, + autoDelete: false, + arguments: null); return channel; } From a64718804dcaa820035a9cd6fb6db5b7ec0f656c Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Thu, 6 Aug 2020 16:38:06 -0700 Subject: [PATCH 18/24] Rename controller to SendMessageController --- .../{RabbitMqController.cs => SendMessageController.cs} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename examples/MicroserviceExample/WebApi/Controllers/{RabbitMqController.cs => SendMessageController.cs} (77%) diff --git a/examples/MicroserviceExample/WebApi/Controllers/RabbitMqController.cs b/examples/MicroserviceExample/WebApi/Controllers/SendMessageController.cs similarity index 77% rename from examples/MicroserviceExample/WebApi/Controllers/RabbitMqController.cs rename to examples/MicroserviceExample/WebApi/Controllers/SendMessageController.cs index 7af0fb71081..fd2630e63b0 100644 --- a/examples/MicroserviceExample/WebApi/Controllers/RabbitMqController.cs +++ b/examples/MicroserviceExample/WebApi/Controllers/SendMessageController.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,12 +22,12 @@ namespace WebApi.Controllers { [ApiController] [Route("[controller]")] - public class RabbitMqController : ControllerBase + public class SendMessageController : ControllerBase { - private readonly ILogger logger; + private readonly ILogger logger; private readonly MessageSender messageSender; - public RabbitMqController(ILogger logger, MessageSender messageSender) + public SendMessageController(ILogger logger, MessageSender messageSender) { this.logger = logger; this.messageSender = messageSender; From c38945d72c93a92db0162def5da54f04d2366918 Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Thu, 6 Aug 2020 16:45:02 -0700 Subject: [PATCH 19/24] Refine the readme --- examples/MicroserviceExample/README.md | 52 ++++++++++++++++++-------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/examples/MicroserviceExample/README.md b/examples/MicroserviceExample/README.md index bc0525d2b74..0b7750e6ace 100644 --- a/examples/MicroserviceExample/README.md +++ b/examples/MicroserviceExample/README.md @@ -1,34 +1,54 @@ -# End-to-end example +# OpenTelemetry Example Application -This directory contains a suite of example applications that communicate with -each other. +This set of projects is an example distributed application comprised of two +components: 1. An ASP.NET Core Web API 2. A background Worker Service -The Web API publishes messages to RabbitMQ and the Worker Service consumes -the messages. +The application demonstrates a number of OpenTelemetry concepts: -Trace context propagation is achieved between the two applications using the -.NET OpenTelemetry API. +* OpenTelemetry APIs for distributed context propagation. +* Basic conventions of how messaging systems are handled in OpenTelemetry. -Traces are exported to a containerized Zipkin instance. +The Web API publishes messages to RabbitMQ which the Worker Service consumes. +Distributed context propagation is achieved using OpenTelemetry APIs to inject +and extract trace context in the headers of the published messages. -## Running the sample applications +The Zipkin exporter is configured for viewing the distributed traces. -The sample applications can easily be run using Docker Desktop by running: +## Running the example + +A running instance of RabbitMQ and Zipkin are required. These can easily be +spun up in docker containers. + +The `WebApi` and `WorkerService` projects can be run from this directory as +follows: + +```shell +dotnet run --project WebApi +dotnet run --project WorkerService +``` + +Instead of running the projects individually, if you are using Docker Desktop, +a `docker-compose` file is provided. This makes standing up the Zipkin and +RabbitMQ dependencies easy, as well as starting both applications. + +To run the example using `docker-compose`, run the following from this +directory: ```shell docker-compose up --build ``` -Once the containers are up, you can: +With everything running: -* [Invoke the Web API](http://localhost:5000/RabbitMq) -* View your traces with Zipkin [here](http://localhost:9411/zipkin) -* Manage RabbitMQ [here](http://localhost:15672/) - * user = guest - * password = guest +* [Invoke the Web API](http://localhost:5000/SendMessage) to send a message. +* If you have run RabbitMQ and Zipkin with default settings: + * View your traces with Zipkin [here](http://localhost:9411/zipkin) + * Manage RabbitMQ [here](http://localhost:15672/) + * user = guest + * password = guest ## References From 7f0de784b03a87d699b07101ee6c95ae460bcf98 Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Thu, 6 Aug 2020 17:34:56 -0700 Subject: [PATCH 20/24] MarkdownCop --- examples/MicroserviceExample/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/MicroserviceExample/README.md b/examples/MicroserviceExample/README.md index 0b7750e6ace..f19e6e1c8ee 100644 --- a/examples/MicroserviceExample/README.md +++ b/examples/MicroserviceExample/README.md @@ -1,7 +1,7 @@ # OpenTelemetry Example Application This set of projects is an example distributed application comprised of two -components: +components: 1. An ASP.NET Core Web API 2. A background Worker Service From ccc0847b527c749a100ac4f1dbe5901ea26397c2 Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Sat, 8 Aug 2020 07:46:21 -0700 Subject: [PATCH 21/24] Fix merge snafu --- OpenTelemetry.sln | 1 - 1 file changed, 1 deletion(-) diff --git a/OpenTelemetry.sln b/OpenTelemetry.sln index f7afd12c273..5c670a93940 100644 --- a/OpenTelemetry.sln +++ b/OpenTelemetry.sln @@ -186,7 +186,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "logs", "logs", "{3862190B-E docs\logs\logging-correlation.md = docs\logs\logging-correlation.md EndProjectSection EndProject -<<<<<<< HEAD Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MicroserviceExample", "MicroserviceExample", "{4D492D62-5150-45F9-817F-C99562E364E2}" ProjectSection(SolutionItems) = preProject examples\MicroserviceExample\.dockerignore = examples\MicroserviceExample\.dockerignore From 437a990a19e23bcbe0af3476c5cebf1d2e476c53 Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Sat, 8 Aug 2020 20:40:19 -0700 Subject: [PATCH 22/24] Add null checks on activity --- .../Utils/Messaging/MessageReceiver.cs | 7 +++++-- .../MicroserviceExample/Utils/Messaging/MessageSender.cs | 5 ++++- .../MicroserviceExample/Utils/Messaging/RabbitMqHelper.cs | 8 ++++---- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs b/examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs index bd4996ad308..1439f03cea4 100644 --- a/examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs +++ b/examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs @@ -71,10 +71,13 @@ public void ReceiveMessage(BasicDeliverEventArgs ea) this.logger.LogInformation($"Message received: [{message}]"); - activity.AddTag("message", message); + activity?.AddTag("message", message); // The OpenTelemetry messaging specification defines a number of attributes. These attributes are added here. - RabbitMqHelper.AddMessagingTags(activity); + if (activity != null) + { + RabbitMqHelper.AddMessagingTags(activity); + } // Simulate some work Thread.Sleep(1000); diff --git a/examples/MicroserviceExample/Utils/Messaging/MessageSender.cs b/examples/MicroserviceExample/Utils/Messaging/MessageSender.cs index 5f43c4ce6e2..f21a45efddf 100644 --- a/examples/MicroserviceExample/Utils/Messaging/MessageSender.cs +++ b/examples/MicroserviceExample/Utils/Messaging/MessageSender.cs @@ -62,7 +62,10 @@ public string SendMessage() TextFormat.Inject(activity.Context, props, this.InjectTraceContextIntoBasicProperties); // The OpenTelemetry messaging specification defines a number of attributes. These attributes are added here. - RabbitMqHelper.AddMessagingTags(activity); + if (activity != null) + { + RabbitMqHelper.AddMessagingTags(activity); + } var body = $"Published message: DateTime.Now = {DateTime.Now}."; diff --git a/examples/MicroserviceExample/Utils/Messaging/RabbitMqHelper.cs b/examples/MicroserviceExample/Utils/Messaging/RabbitMqHelper.cs index 9738e6c2963..3772aa2a6db 100644 --- a/examples/MicroserviceExample/Utils/Messaging/RabbitMqHelper.cs +++ b/examples/MicroserviceExample/Utils/Messaging/RabbitMqHelper.cs @@ -74,10 +74,10 @@ public static void AddMessagingTags(Activity activity) // See: // * https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/messaging.md#messaging-attributes // * https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/messaging.md#rabbitmq - activity.AddTag("messaging.system", "rabbitmq"); - activity.AddTag("messaging.destination_kind", "queue"); - activity.AddTag("messaging.destination", DefaultExchangeName); - activity.AddTag("messaging.rabbitmq.routing_key", TestQueueName); + activity?.AddTag("messaging.system", "rabbitmq"); + activity?.AddTag("messaging.destination_kind", "queue"); + activity?.AddTag("messaging.destination", DefaultExchangeName); + activity?.AddTag("messaging.rabbitmq.routing_key", TestQueueName); } } } From 2d8badef77f8e706347e1e0908ff4eb22e7782ed Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Sat, 8 Aug 2020 20:47:00 -0700 Subject: [PATCH 23/24] Add a missed null check --- .../Utils/Messaging/MessageReceiver.cs | 6 +++--- .../MicroserviceExample/Utils/Messaging/MessageSender.cs | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs b/examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs index 1439f03cea4..bca3d40d48f 100644 --- a/examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs +++ b/examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs @@ -71,11 +71,11 @@ public void ReceiveMessage(BasicDeliverEventArgs ea) this.logger.LogInformation($"Message received: [{message}]"); - activity?.AddTag("message", message); - - // The OpenTelemetry messaging specification defines a number of attributes. These attributes are added here. if (activity != null) { + activity.AddTag("message", message); + + // The OpenTelemetry messaging specification defines a number of attributes. These attributes are added here. RabbitMqHelper.AddMessagingTags(activity); } diff --git a/examples/MicroserviceExample/Utils/Messaging/MessageSender.cs b/examples/MicroserviceExample/Utils/Messaging/MessageSender.cs index f21a45efddf..8eded341382 100644 --- a/examples/MicroserviceExample/Utils/Messaging/MessageSender.cs +++ b/examples/MicroserviceExample/Utils/Messaging/MessageSender.cs @@ -58,12 +58,12 @@ public string SendMessage() { var props = this.channel.CreateBasicProperties(); - // Inject the ActivityContext into the message headers to propagate trace context to the receiving service. - TextFormat.Inject(activity.Context, props, this.InjectTraceContextIntoBasicProperties); - - // The OpenTelemetry messaging specification defines a number of attributes. These attributes are added here. if (activity != null) { + // Inject the ActivityContext into the message headers to propagate trace context to the receiving service. + TextFormat.Inject(activity.Context, props, this.InjectTraceContextIntoBasicProperties); + + // The OpenTelemetry messaging specification defines a number of attributes. These attributes are added here. RabbitMqHelper.AddMessagingTags(activity); } From dcf093af84f2be65a323a6810019a7a2e3ce5dc6 Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Sat, 8 Aug 2020 21:13:00 -0700 Subject: [PATCH 24/24] Use AddOpenTelemetry in ConfigureServices --- .../WorkerService/Program.cs | 23 +++++++++---------- .../WorkerService/Worker.cs | 16 ------------- 2 files changed, 11 insertions(+), 28 deletions(-) diff --git a/examples/MicroserviceExample/WorkerService/Program.cs b/examples/MicroserviceExample/WorkerService/Program.cs index a81c0e2ef00..36916ef6ddb 100644 --- a/examples/MicroserviceExample/WorkerService/Program.cs +++ b/examples/MicroserviceExample/WorkerService/Program.cs @@ -37,18 +37,17 @@ public static IHostBuilder CreateHostBuilder(string[] args) => services.AddSingleton(); - // TODO: Determine if this can be done here in a WorkerService. It does not seem to work... doing this in the RabbitMqConsumer for now. - // services.AddOpenTelemetry((builder) => - // { - // builder - // .AddActivitySource(nameof(RabbitMqConsumer)) - // .UseZipkinExporter(b => - // { - // var zipkinHostName = Environment.GetEnvironmentVariable("ZIPKIN_HOSTNAME") ?? "localhost"; - // b.ServiceName = "Worker"; - // b.Endpoint = new Uri($"http://{zipkinHostName}:9411/api/v2/spans"); - // }); - // }); + services.AddOpenTelemetry((builder) => + { + builder + .AddActivitySource(nameof(MessageReceiver)) + .UseZipkinExporter(b => + { + var zipkinHostName = Environment.GetEnvironmentVariable("ZIPKIN_HOSTNAME") ?? "localhost"; + b.ServiceName = nameof(WorkerService); + b.Endpoint = new Uri($"http://{zipkinHostName}:9411/api/v2/spans"); + }); + }); }); } } diff --git a/examples/MicroserviceExample/WorkerService/Worker.cs b/examples/MicroserviceExample/WorkerService/Worker.cs index 9c7e5886b54..a4c62e3a282 100644 --- a/examples/MicroserviceExample/WorkerService/Worker.cs +++ b/examples/MicroserviceExample/WorkerService/Worker.cs @@ -14,12 +14,9 @@ // limitations under the License. // -using System; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; -using OpenTelemetry; -using OpenTelemetry.Trace; using Utils.Messaging; namespace WorkerService @@ -27,23 +24,10 @@ namespace WorkerService public partial class Worker : BackgroundService { private readonly MessageReceiver messageReceiver; - private readonly TracerProvider tracerProvider; public Worker(MessageReceiver messageReceiver) { this.messageReceiver = messageReceiver; - - this.tracerProvider = Sdk.CreateTracerProvider((builder) => - { - builder - .AddActivitySource(nameof(MessageReceiver)) - .UseZipkinExporter(b => - { - var zipkinHostName = Environment.GetEnvironmentVariable("ZIPKIN_HOSTNAME") ?? "localhost"; - b.ServiceName = nameof(WorkerService); - b.Endpoint = new Uri($"http://{zipkinHostName}:9411/api/v2/spans"); - }); - }); } public override Task StartAsync(CancellationToken cancellationToken)