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 8 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 IRabbitMqService _rabbitMqService;

public RabbitMqController(ILogger<RabbitMqController> logger, IRabbitMqService rabbitMqService)
{
_logger = logger;
_rabbitMqService = rabbitMqService;
}

[HttpGet]
public string Get()
{
return _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"]
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>();
});
}
}
111 changes: 111 additions & 0 deletions examples/MicroserviceExample/WebApi/RabbitMqService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using Microsoft.Extensions.Logging;
using OpenTelemetry.Context.Propagation;
using RabbitMQ.Client;

namespace WebApi
{
public interface IRabbitMqService : IDisposable
{
string PublishMessage();
}

public class RabbitMqService : IRabbitMqService
{
private const string QueueName = "TestQueue";

private readonly ILogger<RabbitMqService> logger;
private readonly ConnectionFactory connectionFactory;
private readonly IConnection connection;
private readonly IModel channel;
private readonly ActivitySource activitySource;
private readonly ITextFormat textFormat;

public RabbitMqService(ILogger<RabbitMqService> logger)
{
this.logger = logger;

this.connectionFactory = new ConnectionFactory()
{
HostName = Environment.GetEnvironmentVariable("RABBIT_HOSTNAME") ?? "localhost",
UserName = "guest",
Copy link
Member

Choose a reason for hiding this comment

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

Should we switch username & password to also use EnvVar if available? Just to be good internet citizens and encourage people to not hardcode their credentials.

UserName = Environment.GetEnvironmentVariable("RABBIT_USERNAME") ?? "guest",`
Password = Environment.GetEnvironmentVariable("RABBIT_PASSWORD") ?? "guest",`

Copy link
Member Author

Choose a reason for hiding this comment

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

👍 Good point.

Password = "guest",
Port = 5672,
RequestedConnectionTimeout = TimeSpan.FromMilliseconds(3000),
};

this.connection = connectionFactory.CreateConnection();
this.channel = connection.CreateModel();
channel.QueueDeclare(
queue: QueueName,
durable: false,
exclusive: false,
autoDelete: false,
arguments: null);

this.activitySource = new ActivitySource(nameof(RabbitMqService));

this.textFormat = new TraceContextFormat();
}

public string PublishMessage()
{
try
{
string activityName = $"{nameof(RabbitMqService)}.{nameof(PublishMessage)}";
Copy link
Member

Choose a reason for hiding this comment

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

Spec says operationName should be <destination name> <operation name> example $"{QueueName} send" for publish here. I just noticed that yesterday it might be new?

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah cool, great call out. I'm really glad to see there's guidance here.

using (var activity = activitySource.StartActivity(activityName))
{
var props = this.channel.CreateBasicProperties();
props.ContentType = "text/plain";
props.DeliveryMode = 2;

textFormat.Inject(activity.Context, props, InjectTraceContextIntoBasicProperties);

var body = $"Published message. DateTime.Now = {DateTime.Now}.";

this.channel.BasicPublish(
exchange: string.Empty,
routingKey: QueueName,
basicProperties: props,
body: Encoding.UTF8.GetBytes(body));

this.logger.LogInformation($"Published message: {body}.");

return body;
}
}
catch (Exception ex)
{
this.logger.LogError(ex.ToString());
throw;
}
}

public void Dispose()
{
this.connection.Dispose();
this.channel.Dispose();
this.activitySource.Dispose();
}

private void InjectTraceContextIntoBasicProperties(IBasicProperties props, string key, string value)
{
try
{
if (props.Headers == null)
{
props.Headers = new Dictionary<string, object>();
}

props.Headers[key] = value;
}
catch (Exception ex)
{
this.logger.LogError($"Failed to inject trace context: {ex}");
}
}
}
}
52 changes: 52 additions & 0 deletions examples/MicroserviceExample/WebApi/Startup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using OpenTelemetry.Trace;

namespace WebApi
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}

public IConfiguration Configuration { get; }

public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();

services.AddSingleton<IRabbitMqService, RabbitMqService>();

services.AddOpenTelemetry((builder) => builder
.AddAspNetCoreInstrumentation()
.AddActivitySource(nameof(RabbitMqService))
.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 = "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": "*"
}
21 changes: 21 additions & 0 deletions examples/MicroserviceExample/WorkerService/.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
12 changes: 12 additions & 0 deletions examples/MicroserviceExample/WorkerService/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", "WorkerService.dll"]
Loading