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:
parent
47a6fe7621
commit
bd738f0447
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"]
|
||||
|
|
@ -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>();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
|
|
@ -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"]
|
||||
|
|
@ -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");
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue