From 898c93633f3eb58304e6a0b1ba9b58f07c862e9a Mon Sep 17 00:00:00 2001 From: Reinaldy Rafli Date: Sun, 3 Sep 2023 17:18:33 +0700 Subject: [PATCH] feat(registration): instrument tracing (#137) --- registration/.editorconfig | 9 ++- .../Controllers/RegistrationController.cs | 62 ++++++++++++------- registration/Controllers/StatusController.cs | 5 +- registration/Models/RegisteredUser.cs | 7 ++- registration/Models/User.cs | 2 +- registration/Program.cs | 6 +- registration/README.md | 3 +- registration/Registration.csproj | 8 +-- registration/Services/ApprovalService.cs | 22 +++---- registration/Services/TrialService.cs | 31 +++++----- registration/Services/WaitingListService.cs | 8 +-- 11 files changed, 92 insertions(+), 71 deletions(-) diff --git a/registration/.editorconfig b/registration/.editorconfig index 075702e..5a6d063 100644 --- a/registration/.editorconfig +++ b/registration/.editorconfig @@ -1,5 +1,4 @@ - -[*] +[*] charset = utf-8 end_of_line = crlf trim_trailing_whitespace = false @@ -11,7 +10,7 @@ indent_size = 4 csharp_new_line_before_else = false csharp_new_line_before_finally = false csharp_new_line_before_members_in_object_initializers = false -csharp_new_line_before_open_brace = control_blocks,local_functions,methods,types +csharp_new_line_before_open_brace = control_blocks, local_functions, methods, types csharp_preferred_modifier_order = public, private, protected, internal, file, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async, required:suggestion csharp_style_prefer_utf8_string_literals = true:suggestion csharp_style_var_elsewhere = true:suggestion @@ -25,7 +24,7 @@ dotnet_naming_rule.unity_serialized_field_rule.style = lower_camel_case_style dotnet_naming_rule.unity_serialized_field_rule.symbols = unity_serialized_field_symbols dotnet_naming_style.lower_camel_case_style.capitalization = camel_case dotnet_naming_symbols.unity_serialized_field_symbols.applicable_accessibilities = * -dotnet_naming_symbols.unity_serialized_field_symbols.applicable_kinds = +dotnet_naming_symbols.unity_serialized_field_symbols.applicable_kinds = dotnet_naming_symbols.unity_serialized_field_symbols.resharper_applicable_kinds = unity_serialised_field dotnet_naming_symbols.unity_serialized_field_symbols.resharper_required_modifiers = instance dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none @@ -57,7 +56,7 @@ resharper_csharp_remove_blank_lines_near_braces_in_declarations = false resharper_csharp_wrap_after_invocation_lpar = true resharper_csharp_wrap_arguments_style = chop_if_long resharper_force_attribute_style = join -resharper_instance_members_qualify_declared_in = +resharper_instance_members_qualify_declared_in = resharper_keep_existing_declaration_block_arrangement = true resharper_place_accessorholder_attribute_on_same_line = false resharper_place_simple_embedded_statement_on_same_line = true diff --git a/registration/Controllers/RegistrationController.cs b/registration/Controllers/RegistrationController.cs index 9af5df0..f75dbdd 100644 --- a/registration/Controllers/RegistrationController.cs +++ b/registration/Controllers/RegistrationController.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Mvc; using Registration.Models; using Registration.Services; +using Sentry; using User = Registration.Models.User; namespace Registration.Controllers; @@ -8,34 +9,38 @@ namespace Registration.Controllers; [ApiController] public class RegistrationController : ControllerBase { - private readonly WaitingListService _waitingListService; private readonly ApprovalService _approvalService; + private readonly IHub _sentryHub; private readonly TrialService _trialService; + private readonly WaitingListService _waitingListService; - public RegistrationController(WaitingListService waitingListService, ApprovalService approvalService, TrialService trialService) + public RegistrationController(WaitingListService waitingListService, ApprovalService approvalService, + TrialService trialService, IHub sentryHub) { _waitingListService = waitingListService; _approvalService = approvalService; _trialService = trialService; + _sentryHub = sentryHub; } /// - /// Register a user into waiting list + /// Register a user into waiting list /// /// /// /// Nothing /// User successfully registered /// User already registered, but no worries - [HttpPost] - [Route("/api/register")] + [HttpPost, Route("/api/register")] public async Task RegisterAsync([FromBody] User user, CancellationToken cancellationToken) { try { + var childSpan = _sentryHub.GetSpan()?.StartChild("waiting_list.put_user"); await _waitingListService.PutUserAsync(user, cancellationToken); + childSpan?.Finish(); - return Created(new Uri("/register"), null); + return Created(new Uri("/register"), value: null); } catch (EmailExistsException) { @@ -44,30 +49,33 @@ public async Task RegisterAsync([FromBody] User user, Cancellatio } /// - /// List users on the waiting list + /// List users on the waiting list /// /// - [HttpGet] - [Route("/api/pending")] + [HttpGet, Route("/api/pending")] public async Task PendingAsync(CancellationToken cancellationToken) { + var childSpan = _sentryHub.GetSpan()?.StartChild("waiting_list.get_users"); var waitingList = await _waitingListService.GetUsersAsync(cancellationToken); + childSpan?.Finish(SpanStatus.Ok); return Ok(waitingList); } /// - /// Approve user from the waiting list + /// Approve user from the waiting list /// /// + /// /// /// User does not exists /// Successfully registered a user - [HttpPut] - [Route("/api/approve")] + [HttpPut, Route("/api/approve")] public async Task ApproveAsync([FromBody] UserToken userToken, CancellationToken cancellationToken) { + var childSpan = _sentryHub.GetSpan()?.StartChild("waiting_list.get_users"); var waitingList = await _waitingListService.GetUsersAsync(cancellationToken); + childSpan?.Finish(SpanStatus.Ok); // Check if email exists on the waiting list var user = (from x in waitingList @@ -79,29 +87,40 @@ where x.Email.Equals(userToken.Email) return BadRequest("Email does not exists"); } + childSpan = _sentryHub.GetSpan()?.StartChild("approval.approve_user"); await _approvalService.ApproveUserAsync(userToken, cancellationToken); + childSpan?.Finish(); + + childSpan = _sentryHub.GetSpan()?.StartChild("waiting_list.remove_user"); await _waitingListService.RemoveUserAsync(user, cancellationToken); + childSpan?.Finish(); return Ok(); } /// - /// Revoke registered user + /// Revoke registered user /// /// + /// /// /// User does not exists /// Successfully revoked a user - [HttpPut] - [Route("/api/revoke")] + [HttpPut, Route("/api/revoke")] public async Task RevokeAsync([FromBody] UserToken userToken, CancellationToken cancellationToken) { try { + var childSpan = _sentryHub.GetSpan()?.StartChild("approval.get_user_by_token"); var registeredUser = await _approvalService.GetUserByTokenAsync(userToken.Token, cancellationToken); + childSpan?.Finish(); + + childSpan = _sentryHub.GetSpan()?.StartChild("approval.revoke_user"); await _approvalService.RevokeUserAsync( new UserToken { Email = registeredUser.UserEmail, Token = userToken.Token, Limit = 0 }, - CancellationToken.None); + cancellationToken); + + childSpan?.Finish(); return Ok(); } @@ -110,18 +129,19 @@ await _approvalService.RevokeUserAsync( return BadRequest("User does not exists"); } } - + /// - /// Create a temporary token for a trial user + /// Create a temporary token for a trial user /// /// /// - [HttpPost] - [Route("/api/trial")] + [HttpPost, Route("/api/trial")] public async Task TrialAsync(CancellationToken cancellationToken) { + var childSpan = _sentryHub.GetSpan()?.StartChild("trial.create_token"); var token = await _trialService.CreateTokenAsync(cancellationToken); + childSpan?.Finish(); return Ok(token); } -} +} \ No newline at end of file diff --git a/registration/Controllers/StatusController.cs b/registration/Controllers/StatusController.cs index 697c05f..3df530b 100644 --- a/registration/Controllers/StatusController.cs +++ b/registration/Controllers/StatusController.cs @@ -14,12 +14,11 @@ public StatusController(IConnectionMultiplexer redis) } /// - /// Health check + /// Health check /// /// Empty body /// - [HttpGet] - [Route("/healthz")] + [HttpGet, Route("/healthz")] public async Task Status(CancellationToken cancellationToken) { var db = _redis.GetDatabase(); diff --git a/registration/Models/RegisteredUser.cs b/registration/Models/RegisteredUser.cs index 7598380..ed47abd 100644 --- a/registration/Models/RegisteredUser.cs +++ b/registration/Models/RegisteredUser.cs @@ -2,9 +2,6 @@ public class RegisteredUser { - public string UserEmail { get; set; } - public int MonthlyLimit { get; set; } - public bool Revoked { get; set; } public RegisteredUser(string userEmail, int monthlyLimit, bool revoked) { @@ -12,4 +9,8 @@ public RegisteredUser(string userEmail, int monthlyLimit, bool revoked) MonthlyLimit = monthlyLimit; Revoked = revoked; } + + public string UserEmail { get; set; } + public int MonthlyLimit { get; set; } + public bool Revoked { get; set; } } \ No newline at end of file diff --git a/registration/Models/User.cs b/registration/Models/User.cs index 057cb55..866da93 100644 --- a/registration/Models/User.cs +++ b/registration/Models/User.cs @@ -13,4 +13,4 @@ public class User public string Building { get; set; } [Required, JsonPropertyName("calls")] public int Calls { get; set; } -}; \ No newline at end of file +} \ No newline at end of file diff --git a/registration/Program.cs b/registration/Program.cs index dffa584..7796d8d 100644 --- a/registration/Program.cs +++ b/registration/Program.cs @@ -1,6 +1,6 @@ -using StackExchange.Redis; using Registration.Services; using Sentry; +using StackExchange.Redis; var builder = WebApplication.CreateBuilder(args); @@ -30,10 +30,10 @@ return context.TransactionContext.Name switch { "GET /healthz" => 0, "GET healthz" => 0, - _ => 0.4 + _ => 0.4 }; - }; + options.StackTraceMode = StackTraceMode.Enhanced; }); diff --git a/registration/README.md b/registration/README.md index 406dbac..f5b4099 100644 --- a/registration/README.md +++ b/registration/README.md @@ -9,6 +9,7 @@ In the future, we hope to send the email and token generation process automatica ## Development Assuming you have [.NET 6 SDK](https://dotnet.microsoft.com/en-us/download/visual-studio-sdks) installed manually or -via IDE (either via [Visual Studio 2022 Community](https://visualstudio.microsoft.com/) or [JetBrains Rider](https://www.jetbrains.com/rider/)). +via IDE (either via [Visual Studio 2022 Community](https://visualstudio.microsoft.com/) +or [JetBrains Rider](https://www.jetbrains.com/rider/)). Open `Registration.sln` from current working directory. \ No newline at end of file diff --git a/registration/Registration.csproj b/registration/Registration.csproj index 888bd45..d0d9e84 100644 --- a/registration/Registration.csproj +++ b/registration/Registration.csproj @@ -8,10 +8,10 @@ - - - - + + + + diff --git a/registration/Services/ApprovalService.cs b/registration/Services/ApprovalService.cs index d4bbf3c..57401fe 100644 --- a/registration/Services/ApprovalService.cs +++ b/registration/Services/ApprovalService.cs @@ -14,47 +14,47 @@ public ApprovalService(IConnectionMultiplexer redis) } /// - /// Approve the user from the waiting list into the proper registered user. + /// Approve the user from the waiting list into the proper registered user. /// /// User object including the generated token /// public async Task ApproveUserAsync(UserToken userToken, CancellationToken cancellationToken) { var registeredUser = new RegisteredUser( - userEmail: userToken.Email, - monthlyLimit: userToken.Limit, + userToken.Email, + userToken.Limit, revoked: false); var serializedUser = JsonSerializer.Serialize(registeredUser); var db = _redis.GetDatabase(); await db.StringSetAsync( - key: userToken.Token, - value: serializedUser, + userToken.Token, + serializedUser, expiry: null); } /// - /// Revoke a user from the list of registered users. + /// Revoke a user from the list of registered users. /// /// User object including the generated token /// public async Task RevokeUserAsync(UserToken userToken, CancellationToken cancellationToken) { var registeredUser = new RegisteredUser( - userEmail: userToken.Email, - monthlyLimit: userToken.Limit, + userToken.Email, + userToken.Limit, revoked: true); var serializedUser = JsonSerializer.Serialize(registeredUser); var db = _redis.GetDatabase(); await db.StringSetAsync( - key: userToken.Token, - value: serializedUser, + userToken.Token, + serializedUser, expiry: null); } /// - /// Get a user by their token. + /// Get a user by their token. /// /// /// diff --git a/registration/Services/TrialService.cs b/registration/Services/TrialService.cs index 91c8ba3..f0b62ea 100644 --- a/registration/Services/TrialService.cs +++ b/registration/Services/TrialService.cs @@ -3,23 +3,24 @@ using Registration.Models; using StackExchange.Redis; -namespace Registration.Services; +namespace Registration.Services; public class TrialService { - private readonly IConnectionMultiplexer _redis; - private readonly Random _random = new Random((int)DateTime.Now.Ticks); private readonly char[] _characters = - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray(); + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray(); + + private readonly Random _random = new((int)DateTime.Now.Ticks); + private readonly IConnectionMultiplexer _redis; + public TrialService(IConnectionMultiplexer redis) { _redis = redis; } /// - /// Create a random token string for users to use. - /// - /// The token will expired in 24 hours. + /// Create a random token string for users to use. + /// The token will expired in 24 hours. /// /// /// @@ -28,7 +29,7 @@ public async Task CreateTokenAsync(CancellationToken cancellationToken) var token = "TRIAL-" + RandomString(64 - 6); var userEmail = "trial-" + RandomString(20) + "@pesto.teknologiumum.com"; var registeredUser = new RegisteredUser( - userEmail: userEmail, + userEmail, monthlyLimit: 10, revoked: false); @@ -36,14 +37,14 @@ public async Task CreateTokenAsync(CancellationToken cancellationToken) var db = _redis.GetDatabase(); await db.StringSetAsync( - key: token, - value: serializedUser, - expiry: TimeSpan.FromHours(24), - when: When.Always); - + token, + serializedUser, + TimeSpan.FromHours(24), + When.Always); + return token; } - + private string RandomString(int size) { var builder = new StringBuilder(); @@ -55,4 +56,4 @@ private string RandomString(int size) return builder.ToString(); } -} +} \ No newline at end of file diff --git a/registration/Services/WaitingListService.cs b/registration/Services/WaitingListService.cs index 9c1ffcb..b056191 100644 --- a/registration/Services/WaitingListService.cs +++ b/registration/Services/WaitingListService.cs @@ -14,7 +14,7 @@ public WaitingListService(IConnectionMultiplexer redis) } /// - /// Put a user into the waiting list. + /// Put a user into the waiting list. /// /// User /// @@ -37,14 +37,14 @@ where waitingListUser.Email.Equals(user.Email) } /// - /// Get all users from the waiting list + /// Get all users from the waiting list /// /// /// public async Task> GetUsersAsync(CancellationToken cancellationToken) { var db = _redis.GetDatabase(); - var waitingList = await db.ListRangeAsync("waiting-list", 0, -1); + var waitingList = await db.ListRangeAsync("waiting-list"); var users = new List(); foreach (var value in waitingList) @@ -64,7 +64,7 @@ public async Task> GetUsersAsync(CancellationToken cancellatio } /// - /// Removes a user from the waiting list. + /// Removes a user from the waiting list. /// /// ///