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 14 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
21 changes: 21 additions & 0 deletions OpenTelemetry.sln
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,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
Expand Down Expand Up @@ -305,6 +315,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
Expand All @@ -320,6 +338,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}
Expand Down
39 changes: 39 additions & 0 deletions examples/MicroserviceExample/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# 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, 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)
* [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)
21 changes: 21 additions & 0 deletions examples/MicroserviceExample/WebApi/.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
Original file line number Diff line number Diff line change
@@ -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<RabbitMqController> logger;
private readonly RabbitMqService rabbitMqService;

public RabbitMqController(ILogger<RabbitMqController> logger, RabbitMqService rabbitMqService)
{
this.logger = logger;
this.rabbitMqService = rabbitMqService;
}

[HttpGet]
public string Get()
{
return this.rabbitMqService.PublishMessage();
}
}
}
12 changes: 12 additions & 0 deletions examples/MicroserviceExample/WebApi/Dockerfile
Original file line number Diff line number Diff line change
@@ -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"]
65 changes: 65 additions & 0 deletions examples/MicroserviceExample/WebApi/MessageSender.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
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<MessageSender> logger;

public MessageSender(ILogger<MessageSender> 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))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alanwest Would you please update to pass ActivityKind.Producer on the StartActivity call?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, thanks for reviewing this with an eye on the spec!

{
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}.";

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<string, object>();
}

props.Headers[key] = value;
}
catch (Exception ex)
{
this.logger.LogError($"Failed to inject trace context: {ex}");
}
}
}
}
20 changes: 20 additions & 0 deletions examples/MicroserviceExample/WebApi/Program.cs
Original file line number Diff line number Diff line change
@@ -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<Startup>();
});
}
}
61 changes: 61 additions & 0 deletions examples/MicroserviceExample/WebApi/RabbitMqService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System;
using Microsoft.Extensions.Logging;
using RabbitMQ.Client;

namespace WebApi
{
public class RabbitMqService
{
private const string QueueName = "TestQueue";

private readonly ILogger<RabbitMqService> logger;
private readonly MessageSender messageSender;
private readonly ConnectionFactory connectionFactory;
private readonly IConnection connection;
private readonly IModel channel;

public RabbitMqService(ILogger<RabbitMqService> 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") ?? "localhost",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean to put guest/guest for credentials? Looks like localhost snuck in with the refactor 😄

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't actually code. I just copy and paste things 😆.

Password = Environment.GetEnvironmentVariable("RABBITMQ_DEFAULT_PASS") ?? "localhost",
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();
}
}
}
54 changes: 54 additions & 0 deletions examples/MicroserviceExample/WebApi/Startup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
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<RabbitMqService>();

services.AddSingleton<MessageSender>();

services.AddOpenTelemetry((builder) => builder
.AddAspNetCoreInstrumentation()
.AddActivitySource(nameof(MessageSender))
.UseZipkinExporter(b =>
{
var zipkinHostName = Environment.GetEnvironmentVariable("ZIPKIN_HOSTNAME") ?? "localhost";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On a followup PR, we should simply get zipkinhostname from this.Configuration. And set this in appsettings.json.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is namely for when running with docker-compose. I can double-check, but the Zipkin hostname is different as seen by the other containers as it is when hitting it at localhost from a browser.

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();
});
}
}
}
12 changes: 12 additions & 0 deletions examples/MicroserviceExample/WebApi/WebApi.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<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>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
10 changes: 10 additions & 0 deletions examples/MicroserviceExample/WebApi/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
Loading