End-to-end example applications (#936)

* Suite of example applications demonstrating context propagation with RabbitMQ

* Put System.* namespaces first

* Newline at EOF

* Fix markdownlint errors

* markdownlint fix

* Refactor WorkerService separating OpenTelemetry related logic from RabbitMQ boilerplate

* Refactor WebApi separating OpenTelemetry related logic from RabbitMQ boilerplate

* Use environment variables for RabbitMQ user/pass

* Add some comments

* Fix RabbitMQ default user/pass

* Use correct ActivityKind

* Add attributes following messaging specification

* Clearing a path through the RabbitMQ jungle

* Log something when sending/receiving a message

* Code style clean up

* Default logging to info level

* Create queue if it does not exist

* Rename controller to SendMessageController

* Refine the readme

* MarkdownCop

* Fix merge snafu

* Add null checks on activity

* Add a missed null check

* Use AddOpenTelemetry in ConfigureServices

Co-authored-by: Cijo Thomas <cithomas@microsoft.com>
Co-authored-by: Mikel Blanchard <mblanchard@macrosssoftware.com>
This commit is contained in:
Alan West 2020-08-09 23:59:10 -07:00 committed by GitHub
parent 47a6fe7621
commit bd738f0447
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 811 additions and 0 deletions

View File

@ -186,6 +186,19 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "logs", "logs", "{3862190B-E
docs\logs\logging-correlation.md = docs\logs\logging-correlation.md
EndProjectSection
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
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
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Utils", "examples\MicroserviceExample\Utils\Utils.csproj", "{5435517C-AEC5-4182-87AE-14E13D31525F}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{CB401DF1-FF5C-4055-886E-1183E832B2D6}"
ProjectSection(SolutionItems) = preProject
docs\Directory.Build.props = docs\Directory.Build.props
@ -347,6 +360,18 @@ 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
{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
{B1891B31-B021-4074-8E42-B4AC170CD208}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B1891B31-B021-4074-8E42-B4AC170CD208}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B1891B31-B021-4074-8E42-B4AC170CD208}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -378,6 +403,10 @@ 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}
{5435517C-AEC5-4182-87AE-14E13D31525F} = {4D492D62-5150-45F9-817F-C99562E364E2}
{2C7DD1DA-C229-4D9E-9AF0-BCD5CD3E4948} = {7CB2F02E-03FA-4FFF-89A5-C51F107623FD}
{5B7FB835-3FFF-4BC2-99C5-A5B5FAE3C818} = {7C87CAF9-79D7-4C26-9FFB-F3F1FB6911F1}
{B1891B31-B021-4074-8E42-B4AC170CD208} = {5B7FB835-3FFF-4BC2-99C5-A5B5FAE3C818}

View File

@ -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

View File

@ -0,0 +1,59 @@
# OpenTelemetry Example Application
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 application demonstrates a number of OpenTelemetry concepts:
* OpenTelemetry APIs for distributed context propagation.
* Basic conventions of how messaging systems are handled in OpenTelemetry.
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.
The Zipkin exporter is configured for viewing the distributed traces.
## 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
```
With everything running:
* [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
* [Docker Desktop](https://www.docker.com/products/docker-desktop)
* [OpenTelemetry Project](https://opentelemetry.io/)
* [RabbitMQ](https://www.rabbitmq.com/)
* [Worker Service](https://docs.microsoft.com/en-us/azure/azure-monitor/app/worker-service)
* [Zipkin](https://zipkin.io)

View File

@ -0,0 +1,110 @@
// <copyright file="MessageReceiver.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
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 static readonly ActivitySource ActivitySource = new ActivitySource(nameof(MessageReceiver));
private static readonly ITextFormat TextFormat = new TraceContextFormat();
private readonly ILogger<MessageReceiver> logger;
private readonly IConnection connection;
private readonly IModel channel;
public MessageReceiver(ILogger<MessageReceiver> logger)
{
this.logger = logger;
this.connection = RabbitMqHelper.CreateConnection();
this.channel = RabbitMqHelper.CreateModelAndDeclareTestQueue(this.connection);
}
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, 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
var activityName = $"{ea.RoutingKey} receive";
using (var activity = ActivitySource.StartActivity(activityName, ActivityKind.Consumer, parentContext))
{
try
{
var message = Encoding.UTF8.GetString(ea.Body.Span.ToArray());
this.logger.LogInformation($"Message received: [{message}]");
if (activity != null)
{
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<string> 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<string>();
}
}
}

View File

@ -0,0 +1,107 @@
// <copyright file="MessageSender.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
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 static readonly ActivitySource ActivitySource = new ActivitySource(nameof(MessageSender));
private static readonly ITextFormat TextFormat = new TraceContextFormat();
private readonly ILogger<MessageSender> logger;
private readonly IConnection connection;
private readonly IModel channel;
public MessageSender(ILogger<MessageSender> 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 = this.channel.CreateBasicProperties();
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);
}
var body = $"Published message: DateTime.Now = {DateTime.Now}.";
this.channel.BasicPublish(
exchange: RabbitMqHelper.DefaultExchangeName,
routingKey: RabbitMqHelper.TestQueueName,
basicProperties: props,
body: Encoding.UTF8.GetBytes(body));
this.logger.LogInformation($"Message sent: [{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<string, object>();
}
props.Headers[key] = value;
}
catch (Exception ex)
{
this.logger.LogError(ex, "Failed to inject trace context.");
}
}
}
}

View File

@ -0,0 +1,83 @@
// <copyright file="RabbitMqHelper.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
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)
{
var channel = connection.CreateModel();
channel.QueueDeclare(
queue: TestQueueName,
durable: false,
exclusive: false,
autoDelete: false,
arguments: null);
return channel;
}
public static void StartConsumer(IModel channel, Action<BasicDeliverEventArgs> 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);
}
}
}

View File

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.6" />
<PackageReference Include="OpenTelemetry" Version="0.4.0-beta.2" />
<PackageReference Include="RabbitMQ.Client" Version="6.1.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,42 @@
// <copyright file="SendMessageController.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Utils.Messaging;
namespace WebApi.Controllers
{
[ApiController]
[Route("[controller]")]
public class SendMessageController : ControllerBase
{
private readonly ILogger<SendMessageController> logger;
private readonly MessageSender messageSender;
public SendMessageController(ILogger<SendMessageController> logger, MessageSender messageSender)
{
this.logger = logger;
this.messageSender = messageSender;
}
[HttpGet]
public string Get()
{
return this.messageSender.SendMessage();
}
}
}

View File

@ -0,0 +1,10 @@
FROM mcr.microsoft.com/dotnet/core/sdk:3.1-alpine AS build
WORKDIR /app
COPY . ./
RUN dotnet publish WebApi -c Release -o /out
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-alpine AS runtime
WORKDIR /app
COPY --from=build /out ./
ENTRYPOINT ["dotnet", "WebApi.dll"]

View File

@ -0,0 +1,36 @@
// <copyright file="Program.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
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<Startup>();
});
}
}

View File

@ -0,0 +1,69 @@
// <copyright file="Startup.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
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;
using Utils.Messaging;
namespace WebApi
{
public class Startup
{
public Startup(IConfiguration configuration)
{
this.Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSingleton<MessageSender>();
services.AddOpenTelemetry((builder) => builder
.AddAspNetCoreInstrumentation()
.AddActivitySource(nameof(MessageSender))
.UseZipkinExporter(b =>
{
var zipkinHostName = Environment.GetEnvironmentVariable("ZIPKIN_HOSTNAME") ?? "localhost";
b.ServiceName = nameof(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();
});
}
}
}

View File

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OpenTelemetry.Exporter.Zipkin" Version="0.4.0-beta.2" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="0.4.0-beta.2" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="0.4.0-beta.2" />
<PackageReference Include="RabbitMQ.Client" Version="6.1.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Utils\Utils.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

View File

@ -0,0 +1,10 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}

View File

@ -0,0 +1,10 @@
FROM mcr.microsoft.com/dotnet/core/sdk:3.1-alpine AS build
WORKDIR /app
COPY . ./
RUN dotnet publish WorkerService -c Release -o /out
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-alpine AS runtime
WORKDIR /app
COPY --from=build /out ./
ENTRYPOINT ["dotnet", "WorkerService.dll"]

View File

@ -0,0 +1,53 @@
// <copyright file="Program.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using OpenTelemetry.Trace;
using Utils.Messaging;
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<Worker>();
services.AddSingleton<MessageReceiver>();
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");
});
});
});
}
}

View File

@ -0,0 +1,52 @@
// <copyright file="Worker.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Utils.Messaging;
namespace WorkerService
{
public partial class Worker : BackgroundService
{
private readonly MessageReceiver messageReceiver;
public Worker(MessageReceiver messageReceiver)
{
this.messageReceiver = messageReceiver;
}
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;
}
}
}

View File

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk.Worker">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="3.1.6" />
<PackageReference Include="OpenTelemetry" Version="0.4.0-beta.2" />
<PackageReference Include="OpenTelemetry.Exporter.Zipkin" Version="0.4.0-beta.2" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="0.4.0-beta.2" />
<PackageReference Include="RabbitMQ.Client" Version="6.1.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Utils\Utils.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

View File

@ -0,0 +1,49 @@
version: '3.8'
services:
zipkin:
image: openzipkin/zipkin
ports:
- 9411:9411
rabbitmq:
image: rabbitmq:3-management-alpine
environment:
- RABBITMQ_DEFAULT_USER=guest
- RABBITMQ_DEFAULT_PASS=guest
ports:
- 5672:5672
- 15672:15672
webapi:
build:
context: .
dockerfile: ./WebApi/Dockerfile
image: opentelemetry-example-webapi
environment:
- ASPNETCORE_ENVIRONMENT=Development
- RABBITMQ_HOSTNAME=rabbitmq
- RABBITMQ_DEFAULT_USER=guest
- RABBITMQ_DEFAULT_PASS=guest
- ZIPKIN_HOSTNAME=zipkin
ports:
- 5000:5000
depends_on:
- rabbitmq
- zipkin
workerservice:
build:
context: .
dockerfile: ./WorkerService/Dockerfile
image: opentelemetry-example-workerservice
environment:
- DOTNET_ENVIRONMENT=Development
- RABBITMQ_HOSTNAME=rabbitmq
- RABBITMQ_DEFAULT_USER=guest
- RABBITMQ_DEFAULT_PASS=guest
- ZIPKIN_HOSTNAME=zipkin
restart: on-failure
depends_on:
- rabbitmq
- zipkin