Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

End-to-end example applications #936

Merged
merged 37 commits into from
Aug 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
41b1921
Suite of example applications demonstrating context propagation with …
alanwest Jul 28, 2020
d899d2a
Put System.* namespaces first
alanwest Jul 28, 2020
d3b2e51
Newline at EOF
alanwest Jul 28, 2020
203cad3
Fix markdownlint errors
alanwest Jul 28, 2020
38a2a5b
markdownlint fix
alanwest Jul 28, 2020
5fc6e6f
Merge branch 'master' into alanwest/end-to-end-example
cijothomas Jul 28, 2020
c75f113
Merge branch 'master' into alanwest/end-to-end-example
cijothomas Jul 28, 2020
bbbdb09
Merge branch 'master' into alanwest/end-to-end-example
cijothomas Jul 29, 2020
0ab8939
Refactor WorkerService separating OpenTelemetry related logic from Ra…
alanwest Jul 29, 2020
b58a421
Refactor WebApi separating OpenTelemetry related logic from RabbitMQ …
alanwest Jul 30, 2020
5248c04
Use environment variables for RabbitMQ user/pass
alanwest Jul 30, 2020
834503e
Add some comments
alanwest Jul 30, 2020
6191d9d
Merge remote-tracking branch 'upstream/master' into master
alanwest Jul 30, 2020
c38f060
Merge branch 'master' into alanwest/end-to-end-example
alanwest Jul 30, 2020
225fadf
Fix RabbitMQ default user/pass
alanwest Aug 1, 2020
ba436f0
Use correct ActivityKind
alanwest Aug 1, 2020
403faae
Add attributes following messaging specification
alanwest Aug 1, 2020
59da6cd
Clearing a path through the RabbitMQ jungle
alanwest Aug 2, 2020
b1e5de9
Merge branch 'master' into alanwest/end-to-end-example
alanwest Aug 2, 2020
9682076
Merge branch 'master' into alanwest/end-to-end-example
alanwest Aug 2, 2020
4d51283
Merge branch 'master' into alanwest/end-to-end-example
alanwest Aug 3, 2020
c925fa2
Merge branch 'master' into alanwest/end-to-end-example
alanwest Aug 6, 2020
807d0ea
Log something when sending/receiving a message
alanwest Aug 6, 2020
c2d0484
Code style clean up
alanwest Aug 6, 2020
413a56b
Default logging to info level
alanwest Aug 6, 2020
de660c6
Create queue if it does not exist
alanwest Aug 6, 2020
a647188
Rename controller to SendMessageController
alanwest Aug 6, 2020
c38945d
Refine the readme
alanwest Aug 6, 2020
7f0de78
MarkdownCop
alanwest Aug 7, 2020
bbdb926
Merge branch 'master' into alanwest/end-to-end-example
CodeBlanch Aug 7, 2020
0d1a90a
Merge branch 'master' into alanwest/end-to-end-example
alanwest Aug 8, 2020
ccc0847
Fix merge snafu
alanwest Aug 8, 2020
51784cf
Merge branch 'alanwest/end-to-end-example' of github.com:alanwest/ope…
alanwest Aug 8, 2020
437a990
Add null checks on activity
alanwest Aug 9, 2020
2d8bade
Add a missed null check
alanwest Aug 9, 2020
dcf093a
Use AddOpenTelemetry in ConfigureServices
alanwest Aug 9, 2020
dc654ad
Merge branch 'master' into alanwest/end-to-end-example
cijothomas Aug 10, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions OpenTelemetry.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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}
Expand Down
21 changes: 21 additions & 0 deletions examples/MicroserviceExample/.dockerignore
Original file line number Diff line number Diff line change
@@ -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
59 changes: 59 additions & 0 deletions examples/MicroserviceExample/README.md
Original file line number Diff line number Diff line change
@@ -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)
110 changes: 110 additions & 0 deletions examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs
Original file line number Diff line number Diff line change
@@ -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>();
}
}
}
107 changes: 107 additions & 0 deletions examples/MicroserviceExample/Utils/Messaging/MessageSender.cs
Original file line number Diff line number Diff line change
@@ -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.");
}
}
}
}
Loading