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

Add zipkin and dashboard #99

Merged
merged 4 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -428,3 +428,6 @@ CoverageReport/

# Rider SonarLint plugin
**/.idea/sonarlint/

# Docker container volumes
**/docker-volumes/
1 change: 1 addition & 0 deletions src/backend/Backend.sln
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApi.Tests", "..\..\tests
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{9EBA80A2-4F3D-4EBC-AC53-3D4DE1719443}"
ProjectSection(SolutionItems) = preProject
..\..\.gitignore = ..\..\.gitignore
..\..\README.md = ..\..\README.md
EndProjectSection
EndProject
Expand Down
14 changes: 7 additions & 7 deletions src/backend/Makefile
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
.PHONY: run
run:
mkdir -p log
chmod 777 log
mkdir -p rabbitmq/data
chmod 777 rabbitmq/data
mkdir -p rabbitmq/log
chmod 777 rabbitmq/log
mkdir -p docker-volumes/log && chmod 777 docker-volumes/log
mkdir -p docker-volumes/rabbitmq/data && chmod 777 docker-volumes/rabbitmq/data
mkdir -p docker-volumes/rabbitmq/log && chmod 777 docker-volumes/rabbitmq/log
mkdir -p docker-volumes/mssql/data && chmod 777 docker-volumes/mssql/data
mkdir -p docker-volumes/mssql/log && chmod 777 docker-volumes/mssql/log
mkdir -p docker-volumes/mssql/secrets && chmod 777 docker-volumes/mssql/secrets
docker compose up -d --build

.PHONY: clean
Expand All @@ -15,7 +15,7 @@ clean:

.PHONY: wait
wait:
until [ -f "./log/traces.log" ] && [ -f "./log/metrics.log" ] && [ -f "./log/logs.log" ]; do sleep 5; done
until [ -f "./docker-volumes/log/traces.log" ] && [ -f "./docker-volumes/log/metrics.log" ] && [ -f "./docker-volumes/log/logs.log" ]; do sleep 5; done

.PHONY: test
test: run wait clean
67 changes: 67 additions & 0 deletions src/backend/WebApi/BloggingContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
ο»Ώusing Microsoft.EntityFrameworkCore;

namespace WebApi;

/// <summary>
/// TODO
/// </summary>
public class BloggingContext(DbContextOptions<BloggingContext> options) : DbContext(options)
{
/// <summary>
/// TODO
/// </summary>
public DbSet<Blog> Blogs { get; set; }

Check notice on line 13 in src/backend/WebApi/BloggingContext.cs

View workflow job for this annotation

GitHub Actions / build

"[PropertyCanBeMadeInitOnly.Global] Property can be made init-only" on /home/runner/work/test/test/src/backend/WebApi/BloggingContext.cs(13,284)

/// <summary>
/// TODO
/// </summary>
public DbSet<Post> Posts { get; set; }

Check notice on line 18 in src/backend/WebApi/BloggingContext.cs

View workflow job for this annotation

GitHub Actions / build

"[PropertyCanBeMadeInitOnly.Global] Property can be made init-only" on /home/runner/work/test/test/src/backend/WebApi/BloggingContext.cs(18,382)
}

/// <summary>
/// TODO
/// </summary>
public class Blog
{
/// <summary>
/// TODO
/// </summary>
public int BlogId { get; set; }

Check notice on line 29 in src/backend/WebApi/BloggingContext.cs

View workflow job for this annotation

GitHub Actions / build

"[PropertyCanBeMadeInitOnly.Global] Property can be made init-only" on /home/runner/work/test/test/src/backend/WebApi/BloggingContext.cs(29,537)

/// <summary>
/// TODO
/// </summary>
public string Url { get; set; } = string.Empty;

Check notice on line 34 in src/backend/WebApi/BloggingContext.cs

View workflow job for this annotation

GitHub Actions / build

"[PropertyCanBeMadeInitOnly.Global] Property can be made init-only" on /home/runner/work/test/test/src/backend/WebApi/BloggingContext.cs(34,628)

/// <summary>
/// TODO
/// </summary>
public List<Post> Posts { get; } = new();

Check warning on line 39 in src/backend/WebApi/BloggingContext.cs

View workflow job for this annotation

GitHub Actions / build

"[CollectionNeverUpdated.Global] Content of collection 'Posts' is never updated" on /home/runner/work/test/test/src/backend/WebApi/BloggingContext.cs(39,728)

Check notice on line 39 in src/backend/WebApi/BloggingContext.cs

View workflow job for this annotation

GitHub Actions / build

"[UseCollectionExpression] Use collection expression" on /home/runner/work/test/test/src/backend/WebApi/BloggingContext.cs(39,745)
}

/// <summary>
/// TODO
/// </summary>
public class Post
{
/// <summary>
/// TODO
/// </summary>
public int PostId { get; set; }

Check notice on line 50 in src/backend/WebApi/BloggingContext.cs

View workflow job for this annotation

GitHub Actions / build

"[PropertyCanBeMadeInitOnly.Global] Property can be made init-only" on /home/runner/work/test/test/src/backend/WebApi/BloggingContext.cs(50,900)
/// <summary>
/// TODO
/// </summary>
public string Title { get; set; } = string.Empty;

Check notice on line 54 in src/backend/WebApi/BloggingContext.cs

View workflow job for this annotation

GitHub Actions / build

"[PropertyCanBeMadeInitOnly.Global] Property can be made init-only" on /home/runner/work/test/test/src/backend/WebApi/BloggingContext.cs(54,992)
/// <summary>
/// TODO
/// </summary>
public string Content { get; set; } = string.Empty;

Check notice on line 58 in src/backend/WebApi/BloggingContext.cs

View workflow job for this annotation

GitHub Actions / build

"[PropertyCanBeMadeInitOnly.Global] Property can be made init-only" on /home/runner/work/test/test/src/backend/WebApi/BloggingContext.cs(58,1102)
/// <summary>
/// TODO
/// </summary>
public int BlogId { get; set; }

Check notice on line 62 in src/backend/WebApi/BloggingContext.cs

View workflow job for this annotation

GitHub Actions / build

"[PropertyCanBeMadeInitOnly.Global] Property can be made init-only" on /home/runner/work/test/test/src/backend/WebApi/BloggingContext.cs(62,1208)
/// <summary>
/// TODO
/// </summary>
public Blog? Blog { get; set; }

Check notice on line 66 in src/backend/WebApi/BloggingContext.cs

View workflow job for this annotation

GitHub Actions / build

"[PropertyCanBeMadeInitOnly.Global] Property can be made init-only" on /home/runner/work/test/test/src/backend/WebApi/BloggingContext.cs(66,1298)
}
60 changes: 60 additions & 0 deletions src/backend/WebApi/Controllers/BloggingController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace WebApi.Controllers;

/// <summary>
/// Blogging controller
/// </summary>
[ApiController]
[Route("[controller]")]
public class BloggingController(ILogger<BloggingController> logger, BloggingContext bloggingContext) : ControllerBase
{
/// <summary>
/// Get blogs
/// </summary>
/// <returns></returns>
[HttpGet("Blog", Name = "GetBlogs")]
public async Task<ActionResult<IEnumerable<Blog>>> GetBlogs(CancellationToken cancellationToken)
{
logger.LogInformation("GetBlogs was called");
return await bloggingContext.Blogs.ToListAsync(cancellationToken);
}

/// <summary>
/// Create blog
/// </summary>
/// <returns></returns>
[HttpPost("Blog", Name = "PostBlog")]
public async Task<ActionResult<Blog>> PostBlog(Blog blog, CancellationToken cancellationToken)
{
logger.LogInformation("PostBlog was called");
var blogEntry = bloggingContext.Blogs.Add(blog);
await bloggingContext.SaveChangesAsync(cancellationToken);
return blogEntry.Entity;
}

/// <summary>
/// Get posts
/// </summary>
/// <returns></returns>
[HttpGet("Post", Name = "GetPosts")]
public async Task<ActionResult<IEnumerable<Post>>> GetPosts(CancellationToken cancellationToken)
{
logger.LogInformation("GetPosts was called");
return await bloggingContext.Posts.ToListAsync(cancellationToken);
}

/// <summary>
/// Create post
/// </summary>
/// <returns></returns>
[HttpPost("Post", Name = "PostPost")]
public async Task<ActionResult<Post>> PostPost(Post post, CancellationToken cancellationToken)
{
logger.LogInformation("PostPost was called");
var postEntry = bloggingContext.Posts.Add(post);
await bloggingContext.SaveChangesAsync(cancellationToken);
return postEntry.Entity;
}
}
36 changes: 36 additions & 0 deletions src/backend/WebApi/Controllers/SendMessageController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
ο»Ώ// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

using Microsoft.AspNetCore.Mvc;
using Utils.Messaging;

namespace WebApi.Controllers;

/// <summary>
/// TODO
/// </summary>
[ApiController]
[Route("[controller]")]
public class SendMessageController : ControllerBase
{
private readonly ILogger<SendMessageController> logger;

Check warning on line 16 in src/backend/WebApi/Controllers/SendMessageController.cs

View workflow job for this annotation

GitHub Actions / build

"[NotAccessedField.Local] Field 'logger' is assigned but its value is never used" on /home/runner/work/test/test/src/backend/WebApi/Controllers/SendMessageController.cs(16,354)

Check warning on line 16 in src/backend/WebApi/Controllers/SendMessageController.cs

View workflow job for this annotation

GitHub Actions / build

"[InconsistentNaming] Name 'logger' does not match rule 'Instance fields (private)'. Suggested name is '_logger'." on /home/runner/work/test/test/src/backend/WebApi/Controllers/SendMessageController.cs(16,354)
private readonly MessageSender messageSender;

Check warning on line 17 in src/backend/WebApi/Controllers/SendMessageController.cs

View workflow job for this annotation

GitHub Actions / build

"[InconsistentNaming] Name 'messageSender' does not match rule 'Instance fields (private)'. Suggested name is '_messageSender'." on /home/runner/work/test/test/src/backend/WebApi/Controllers/SendMessageController.cs(17,397)

/// <summary>
/// TODO
/// </summary>
/// <param name="logger"></param>
/// <param name="messageSender"></param>
public SendMessageController(ILogger<SendMessageController> logger, MessageSender messageSender)
{
this.logger = logger;
this.messageSender = messageSender;
}

/// <summary>
/// TODO
/// </summary>
/// <returns></returns>
[HttpGet]
public string Get() => messageSender.SendMessage();
}
104 changes: 104 additions & 0 deletions src/backend/WebApi/Messaging/MessageReceiver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
ο»Ώ// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

using System.Diagnostics;
using System.Text;
using OpenTelemetry;
using OpenTelemetry.Context.Propagation;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;

namespace Utils.Messaging;

Check warning on line 11 in src/backend/WebApi/Messaging/MessageReceiver.cs

View workflow job for this annotation

GitHub Actions / build

"[CheckNamespace] Namespace does not correspond to file location, must be: 'WebApi.Messaging'" on /home/runner/work/test/test/src/backend/WebApi/Messaging/MessageReceiver.cs(11,250)

/// <summary>
/// TODO
/// </summary>
public class MessageReceiver : IDisposable
{
private static readonly ActivitySource ActivitySource = new(nameof(MessageReceiver));
private static readonly TextMapPropagator Propagator = Propagators.DefaultTextMapPropagator;

private readonly ILogger<MessageReceiver> logger;

Check warning on line 21 in src/backend/WebApi/Messaging/MessageReceiver.cs

View workflow job for this annotation

GitHub Actions / build

"[InconsistentNaming] Name 'logger' does not match rule 'Instance fields (private)'. Suggested name is '_logger'." on /home/runner/work/test/test/src/backend/WebApi/Messaging/MessageReceiver.cs(21,589)
private readonly IConnection connection;

Check warning on line 22 in src/backend/WebApi/Messaging/MessageReceiver.cs

View workflow job for this annotation

GitHub Actions / build

"[InconsistentNaming] Name 'connection' does not match rule 'Instance fields (private)'. Suggested name is '_connection'." on /home/runner/work/test/test/src/backend/WebApi/Messaging/MessageReceiver.cs(22,630)
private readonly IModel channel;

Check warning on line 23 in src/backend/WebApi/Messaging/MessageReceiver.cs

View workflow job for this annotation

GitHub Actions / build

"[InconsistentNaming] Name 'channel' does not match rule 'Instance fields (private)'. Suggested name is '_channel'." on /home/runner/work/test/test/src/backend/WebApi/Messaging/MessageReceiver.cs(23,670)

/// <summary>
/// TODO
/// </summary>
public MessageReceiver(ILogger<MessageReceiver> logger)
{
this.logger = logger;
connection = RabbitMqHelper.CreateConnection();
channel = RabbitMqHelper.CreateModelAndDeclareTestQueue(connection);
}

/// <summary>
/// TODO
/// </summary>
public void Dispose()
{
channel.Dispose();
connection.Dispose();
}

/// <summary>
/// TODO
/// </summary>
public void StartConsumer()
{
RabbitMqHelper.StartConsumer(channel, ReceiveMessage);
}

/// <summary>
/// TODO
/// </summary>
public void ReceiveMessage(BasicDeliverEventArgs ea)
{
// Extract the PropagationContext of the upstream parent from the message headers.
var parentContext = Propagator.Extract(default, ea.BasicProperties, ExtractTraceContextFromBasicProperties);
Baggage.Current = parentContext.Baggage;

// Start an activity with a name following the semantic convention of the OpenTelemetry messaging specification.
// https://github.com/open-telemetry/semantic-conventions/blob/main/docs/messaging/messaging-spans.md#span-name
var activityName = $"{ea.RoutingKey} receive";

using var activity = ActivitySource.StartActivity(activityName, ActivityKind.Consumer, parentContext.ActivityContext);
try
{
var message = Encoding.UTF8.GetString(ea.Body.Span.ToArray());

logger.LogInformation($"Message received: [{message}]");

activity?.SetTag("message", message);

// The OpenTelemetry messaging specification defines a number of attributes. These attributes are added here.
if (activity != null)
RabbitMqHelper.AddMessagingTags(activity);

// Simulate some work
Thread.Sleep(1000);
}
catch (Exception ex)
{
logger.LogError(ex, "Message processing failed.");
}
}

private IEnumerable<string> ExtractTraceContextFromBasicProperties(IBasicProperties props, string key)
{
try
{
if (props.Headers.TryGetValue(key, out var value) && value is byte[] bytes)
return
[
Encoding.UTF8.GetString(bytes)
];
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to extract trace context.");
}

return [];
}
}
Loading