-
Notifications
You must be signed in to change notification settings - Fork 785
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
Changes from 14 commits
41b1921
d899d2a
d3b2e51
203cad3
38a2a5b
5fc6e6f
c75f113
bbbdb09
0ab8939
b58a421
5248c04
834503e
6191d9d
c38f060
225fadf
ba436f0
403faae
59da6cd
b1e5de9
9682076
4d51283
c925fa2
807d0ea
c2d0484
413a56b
de660c6
a647188
c38945d
7f0de78
bbdb926
0d1a90a
ccc0847
51784cf
437a990
2d8bade
dcf093a
dc654ad
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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) |
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(); | ||
} | ||
} | ||
} |
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"] |
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)) | ||
{ | ||
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}"); | ||
} | ||
} | ||
} | ||
} |
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>(); | ||
}); | ||
} | ||
} |
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", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 😄 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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(); | ||
} | ||
} | ||
} |
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"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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(); | ||
}); | ||
} | ||
} | ||
} |
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" | ||
} | ||
} | ||
} |
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": "*" | ||
} |
There was a problem hiding this comment.
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 theStartActivity
call?There was a problem hiding this comment.
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!