From 99931879c9687b4e469015871cd07340fe700cd9 Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Mon, 26 Oct 2026 16:11:55 +0000 Subject: [PATCH 1/3] add structured logging --- Directory.Packages.props | 68 +++++++++---------- .../TransactionProcessorManagerTests.cs | 2 +- .../Common/Extensions.cs | 7 +- .../AgencyBanking/AgencyBankingProxy.cs | 67 ++++++++++-------- .../PataPawaPrePay/PataPawaPrePayProxy.cs | 50 ++++++++++---- .../SafaricomPinless/SafaricomPinlessProxy.cs | 23 ++++--- .../VoucherManagementProxy.cs | 2 + .../Services/SettlementDomainService.cs | 5 +- .../Services/TransactionDomainService.cs | 32 +++++---- .../DatabaseTestFixture.cs | 5 +- .../Common/DockerHelper.cs | 3 +- .../Common/GenericSteps.cs | 5 +- ...TransactionProcessorReadModelRepository.cs | 4 +- .../Common/ClientMessageLogger.cs | 26 +++---- .../Controllers/DomainEventController.cs | 25 +++---- TransactionProcessor/Extensions.cs | 30 ++++---- TransactionProcessor/Program.cs | 4 +- 17 files changed, 198 insertions(+), 160 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 6fa73c7c..16c6d821 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -8,44 +8,44 @@ - - + + - - - + + + - - - - - - - + + + + + + + - - - - - - - - - + + + + + + + + + - + @@ -63,28 +63,28 @@ - - + + - - - - - - + + + + + + - + - - + + @@ -103,4 +103,4 @@ - + \ No newline at end of file diff --git a/TransactionProcessor.BusinessLogic.Tests/Manager/TransactionProcessorManagerTests.cs b/TransactionProcessor.BusinessLogic.Tests/Manager/TransactionProcessorManagerTests.cs index 34e15bd5..06f5c8d7 100644 --- a/TransactionProcessor.BusinessLogic.Tests/Manager/TransactionProcessorManagerTests.cs +++ b/TransactionProcessor.BusinessLogic.Tests/Manager/TransactionProcessorManagerTests.cs @@ -34,7 +34,7 @@ public class TransactionProcessorManagerTests public TransactionProcessorManagerTests() { - Logger.Initialise(Microsoft.Extensions.Logging.Abstractions.NullLogger.Instance); + Logger.Initialise(new NullLogger()); this.TransactionProcessorReadModelRepository = new ITransactionProcessorReadModelRepositoryImposter(); StringSerialiser.Initialise(new Shared.Serialisation.SystemTextJsonSerializer(new System.Text.Json.JsonSerializerOptions())); this.AggregateService = new IAggregateServiceImposter(); diff --git a/TransactionProcessor.BusinessLogic/Common/Extensions.cs b/TransactionProcessor.BusinessLogic/Common/Extensions.cs index 6dba322e..c318043a 100644 --- a/TransactionProcessor.BusinessLogic/Common/Extensions.cs +++ b/TransactionProcessor.BusinessLogic/Common/Extensions.cs @@ -30,27 +30,22 @@ public static async Task> GetToken(TokenResponse currentTo // Get a token to talk to the estate service String clientId = ConfigurationReader.GetValue("AppSettings", "ClientId"); String clientSecret = ConfigurationReader.GetValue("AppSettings", "ClientSecret"); - Logger.LogDebug($"Client Id is {clientId}"); - Logger.LogDebug($"Client Secret is {clientSecret}"); - + if (currentToken == null) { Result tokenResult= await securityServiceClient.GetToken(clientId, clientSecret, cancellationToken); if (tokenResult.IsFailed) return ResultHelpers.CreateFailure(tokenResult); TokenResponse token =tokenResult.Data; - Logger.LogDebug($"Token is {token.AccessToken}"); return Result.Success(token); } if (currentToken.Expires.UtcDateTime.Subtract(DateTime.UtcNow) < TimeSpan.FromMinutes(2)) { - Logger.LogDebug($"Token is about to expire at {currentToken.Expires.DateTime:O}"); Result tokenResult = await securityServiceClient.GetToken(clientId, clientSecret, cancellationToken); if (tokenResult.IsFailed) return ResultHelpers.CreateFailure(tokenResult); TokenResponse token = tokenResult.Data; - Logger.LogDebug($"Token is {token.AccessToken}"); return Result.Success(token); } diff --git a/TransactionProcessor.BusinessLogic/OperatorInterfaces/AgencyBanking/AgencyBankingProxy.cs b/TransactionProcessor.BusinessLogic/OperatorInterfaces/AgencyBanking/AgencyBankingProxy.cs index c2ee141e..ebaf21c4 100644 --- a/TransactionProcessor.BusinessLogic/OperatorInterfaces/AgencyBanking/AgencyBankingProxy.cs +++ b/TransactionProcessor.BusinessLogic/OperatorInterfaces/AgencyBanking/AgencyBankingProxy.cs @@ -42,14 +42,15 @@ public async Task> ProcessSaleMessage(Guid transactionI Dictionary additionalTransactionMetadata, CancellationToken cancellationToken) { try { - Logger.LogInformation($"AgencyBanking sale request received. TransactionId=[{transactionId}], OperatorId=[{operatorId}], MerchantId=[{merchant.MerchantId}], TransactionDateTime=[{transactionDateTime:o}], TransactionReference=[{transactionReference}]"); + Logger.LogInformation("AgencyBanking sale request received. TransactionId=[{TransactionId}], OperatorId=[{OperatorId}], MerchantId=[{MerchantId}], TransactionDateTime=[{TransactionDateTime}], TransactionReference=[{TransactionReference}]", + transactionId, operatorId, merchant.MerchantId, transactionDateTime.ToString("O"), transactionReference); // Check the meta data for the message type String messageType = additionalTransactionMetadata.ExtractFieldFromMetadata("AgencyBankingMessageType"); if (String.IsNullOrEmpty(messageType)) { - Logger.LogWarning($"AgencyBanking sale request rejected. TransactionId=[{transactionId}] missing AgencyBankingMessageType"); + Logger.LogWarning("AgencyBanking sale request rejected. TransactionId=[{TransactionId}] missing AgencyBankingMessageType", transactionId); return Result.Invalid("AgencyBankingMessageType - Message Type is a required field for this transaction type"); } @@ -58,11 +59,11 @@ public async Task> ProcessSaleMessage(Guid transactionI if (String.IsNullOrEmpty(accountNumber)) { - Logger.LogWarning($"AgencyBanking sale request rejected. TransactionId=[{transactionId}] missing AgencyBankingAccountNumber"); + Logger.LogWarning("AgencyBanking sale request rejected. TransactionId=[{TransactionId}] missing AgencyBankingAccountNumber", transactionId); return Result.Invalid("AgencyBankingAccountNumber - Account Number is a required field for this transaction type"); } - Logger.LogInformation($"AgencyBanking sale request metadata resolved. TransactionId=[{transactionId}], MessageType=[{messageType}], AccountNumber=[{accountNumber}]"); + Logger.LogInformation("AgencyBanking sale request metadata resolved. TransactionId=[{TransactionId}], MessageType=[{MessageType}], AccountNumber=[{AccountNumber}]", transactionId, messageType, accountNumber); Result result = messageType switch { "balanceenquiry" => await ProcessBalanceEnquiry(transactionId, merchant.MerchantId.ToString(), accountNumber, cancellationToken), @@ -73,7 +74,7 @@ public async Task> ProcessSaleMessage(Guid transactionI }; if (result.Status == ResultStatus.Invalid) { - Logger.LogWarning($"AgencyBanking sale request rejected. TransactionId=[{transactionId}] unsupported message type [{messageType}]"); + Logger.LogWarning("AgencyBanking sale request rejected. TransactionId=[{TransactionId}] unsupported message type [{MessageType}]", transactionId, messageType); } return result; @@ -82,7 +83,7 @@ public async Task> ProcessSaleMessage(Guid transactionI throw; } catch (Exception ex) { - Logger.LogError($"AgencyBanking sale request failed. TransactionId=[{transactionId}]", ex); + Logger.LogError(ex,"AgencyBanking sale request failed. TransactionId=[{TransactionId}]", transactionId); return Result.Failure($"AgencyBanking request failed [{ex.Message}]"); } } @@ -103,13 +104,13 @@ private async Task> ProcessWithdrawal(Guid transactionI if (String.IsNullOrWhiteSpace(amountValue)) { - Logger.LogWarning($"AgencyBanking withdrawal request rejected. TransactionId=[{transactionId}] missing Amount"); + Logger.LogWarning("AgencyBanking withdrawal request rejected. TransactionId=[{TransactionId}] missing Amount", transactionId); return Result.Invalid("Amount - Amount is a required field for this transaction type"); } if (Decimal.TryParse(amountValue, out Decimal amount) == false) { - Logger.LogWarning($"AgencyBanking withdrawal request rejected. TransactionId=[{transactionId}] invalid Amount [{amountValue}]"); + Logger.LogWarning("AgencyBanking withdrawal request rejected. TransactionId=[{TransactionId}] invalid Amount [{Amount}]", amountValue); return Result.Invalid("Amount - Amount is not a valid decimal value"); } @@ -117,21 +118,23 @@ private async Task> ProcessWithdrawal(Guid transactionI request.Content = CreateContent(body); string requestBody = await request.Content.ReadAsStringAsync(cancellationToken); - Logger.LogWarning($"AgencyBanking withdrawal request starting. TransactionId=[{transactionId}], AgentId=[{agentId}], AccountNumber=[{accountNumber}], RequestUri=[{request.RequestUri}], RequestBody=[{requestBody}]"); + Logger.LogWarning("AgencyBanking withdrawal request starting. TransactionId=[{TransactionId}], AgentId=[{AgentId}], AccountNumber=[{AccountNumber}], RequestUri=[{Uri}], RequestBody=[{requestBody}]", + transactionId, agentId, accountNumber, request.RequestUri, requestBody); HttpResponseMessage response = await this.HttpClient.SendAsync(request, cancellationToken); String responseContent = await response.Content.ReadAsStringAsync(cancellationToken); - Logger.LogWarning($"AgencyBanking withdrawal response received. TransactionId=[{transactionId}], StatusCode=[{response.StatusCode}], ContentLength=[{responseContent?.Length ?? 0}]"); + Logger.LogWarning("AgencyBanking withdrawal response received. TransactionId=[{TransactionId}], StatusCode=[{StatusCode}], ContentLength=[{ContentLength}]", + transactionId, response.StatusCode, responseContent?.Length ?? 0); if (response.IsSuccessStatusCode == false) { - Logger.LogWarning($"AgencyBanking withdrawal failed. TransactionId=[{transactionId}], StatusCode=[{response.StatusCode}], ResponseBody=[{responseContent}]"); + Logger.LogWarning("AgencyBanking withdrawal failed. TransactionId=[{TransactionId}], StatusCode=[{StatusCode}], ResponseBody=[{ResponseBody}]",transactionId, response.StatusCode, responseContent); return Result.Invalid($"Failed to process withdrawal {responseContent}"); } - TransactionResult balanceEnquiryResponse = StringSerialiser.Deserialise(responseContent, SerialiserOptions); - Logger.LogWarning($"AgencyBanking withdrawal completed. TransactionId=[{transactionId}], ResponseCode=[{balanceEnquiryResponse.ResponseCode}]"); + TransactionResult withdrawalResponse = StringSerialiser.Deserialise(responseContent, SerialiserOptions); + Logger.LogWarning("AgencyBanking withdrawal completed. TransactionId=[{TransactionId}], ResponseCode=[{ResponseCode}]", transactionId, withdrawalResponse.ResponseCode); OperatorResponse operatorResponse = new() { @@ -153,20 +156,22 @@ private async Task> ProcessMiniStatement(String agentId request.Content = CreateContent(body); string requestBody = await request.Content.ReadAsStringAsync(cancellationToken); - Logger.LogWarning($"AgencyBanking mini-statement request starting. AgentId=[{agentId}], AccountNumber=[{accountNumber}], RequestUri=[{request.RequestUri}], RequestBody=[{requestBody}]"); + Logger.LogWarning("AgencyBanking mini-statement request starting. AgentId=[{AgentId}], AccountNumber=[{AccountNumber}], RequestUri=[{RequestUri}], RequestBody=[{RequestBody}]", agentId, accountNumber, request.RequestUri, requestBody); HttpResponseMessage response = await this.HttpClient.SendAsync(request, cancellationToken); String responseContent = await response.Content.ReadAsStringAsync(cancellationToken); - Logger.LogWarning($"AgencyBanking mini-statement response received. AgentId=[{agentId}], AccountNumber=[{accountNumber}], StatusCode=[{response.StatusCode}], ContentLength=[{responseContent?.Length ?? 0}]"); + Logger.LogWarning("AgencyBanking mini-statement response received. AgentId=[{AgentId}], AccountNumber=[{AccountNumber}], StatusCode=[{StatusCode}], ContentLength=[{ContentLength}]", + agentId, accountNumber, response.StatusCode, responseContent?.Length ?? 0); if (response.IsSuccessStatusCode == false) { - Logger.LogWarning($"AgencyBanking mini-statement failed. AgentId=[{agentId}], AccountNumber=[{accountNumber}], StatusCode=[{response.StatusCode}], ResponseBody=[{responseContent}]"); + Logger.LogWarning("AgencyBanking mini-statement failed. AgentId=[{AgentId}], AccountNumber=[{AccountNumber}], StatusCode=[{StatusCode}], ResponseBody=[{ResponseContent}]", + agentId, accountNumber, response.StatusCode, responseContent); return Result.Invalid($"Failed to process mini-statement {responseContent}"); } MiniStatementResponse miniStatementResponse = StringSerialiser.Deserialise(responseContent, SerialiserOptions); - Logger.LogWarning($"AgencyBanking mini-statement completed. AgentId=[{agentId}], AccountNumber=[{accountNumber}], ResponseCode=[{miniStatementResponse.ResponseCode}]"); + Logger.LogWarning("AgencyBanking mini-statement completed. AgentId=[{AgentId}], AccountNumber=[{AccountNumber}], ResponseCode=[{ResponseCode}]", agentId, accountNumber, miniStatementResponse.ResponseCode); // Transform the mini-statement response into an operator response List statementLines = miniStatementResponse.Transactions.Select(line => new OperatorStatementLine { Amount = line.Amount, TransactionDate = line.TransactionDate, TransactionType = line.TransactionType }).ToList(); @@ -191,34 +196,36 @@ private async Task> ProcessDeposit(Guid transactionId, if (String.IsNullOrWhiteSpace(amountValue)) { - Logger.LogWarning($"AgencyBanking deposit request rejected. TransactionId=[{transactionId}] missing Amount"); + Logger.LogWarning("AgencyBanking deposit request rejected. TransactionId=[{TransactionId}] missing Amount", transactionId); return Result.Invalid("Amount - Amount is a required field for this transaction type"); } if (Decimal.TryParse(amountValue, out Decimal amount) == false) { - Logger.LogWarning($"AgencyBanking deposit request rejected. TransactionId=[{transactionId}] invalid Amount [{amountValue}]"); + Logger.LogWarning("AgencyBanking deposit request rejected. TransactionId=[{TransactionId}] invalid Amount [{Amount}]", transactionId, amountValue); return Result.Invalid("Amount - Amount is not a valid decimal value"); } DepositRequest body = new() { AgentId = agentId, AccountNumber = accountNumber, Amount = amount, TransactionId = transactionId.ToString() }; request.Content = CreateContent(body); string requestBody = await request.Content.ReadAsStringAsync(cancellationToken); - Logger.LogWarning($"AgencyBanking deposit request starting. TransactionId=[{transactionId}], AgentId=[{agentId}], AccountNumber=[{accountNumber}], RequestUri=[{request.RequestUri}], RequestBody=[{requestBody}]"); + Logger.LogWarning("AgencyBanking deposit request starting. TransactionId=[{TransactionId}], AgentId=[{AgentId}], AccountNumber=[{AccountNumber}], RequestUri=[{RequestUri}], RequestBody=[{RequestBody}]", + transactionId, accountNumber, request.RequestUri, requestBody); HttpResponseMessage response = await this.HttpClient.SendAsync(request, cancellationToken); String responseContent = await response.Content.ReadAsStringAsync(cancellationToken); - Logger.LogWarning($"AgencyBanking deposit response received. TransactionId=[{transactionId}], StatusCode=[{response.StatusCode}], ContentLength=[{responseContent?.Length ?? 0}]"); + Logger.LogWarning("AgencyBanking deposit response received. TransactionId=[{TransactionId}], StatusCode=[{StatusCode}], ContentLength=[{ContentLength}]", + transactionId, response.StatusCode, responseContent?.Length ?? 0); if (response.IsSuccessStatusCode == false) { - Logger.LogWarning($"AgencyBanking deposit failed. TransactionId=[{transactionId}], StatusCode=[{response.StatusCode}], ResponseBody=[{responseContent}]"); + Logger.LogWarning("AgencyBanking deposit failed. TransactionId=[{TransactionId}], StatusCode=[{StatusCode}], ResponseBody=[{ResponseContent}]", transactionId, response.StatusCode, responseContent); return Result.Invalid($"Failed to process deposit {responseContent}"); } - TransactionResult balanceEnquiryResponse = StringSerialiser.Deserialise(responseContent, SerialiserOptions); - Logger.LogWarning($"AgencyBanking deposit completed. TransactionId=[{transactionId}], ResponseCode=[{balanceEnquiryResponse.ResponseCode}]"); + TransactionResult depositResponse = StringSerialiser.Deserialise(responseContent, SerialiserOptions); + Logger.LogWarning("AgencyBanking deposit completed. TransactionId=[{TransactionId}], ResponseCode=[{ResponseCode}]", transactionId, depositResponse.ResponseCode); OperatorResponse operatorResponse = new() { @@ -244,20 +251,24 @@ private async Task> ProcessBalanceEnquiry(Guid transact request.Content = CreateContent(body); string requestBody = await request.Content.ReadAsStringAsync(cancellationToken); - Logger.LogWarning($"AgencyBanking balance enquiry request starting. TransactionId=[{transactionId}], AgentId=[{agentId}], AccountNumber=[{accountNumber}], RequestUri=[{request.RequestUri}], RequestBody=[{requestBody}]"); + Logger.LogWarning("AgencyBanking balance enquiry request starting. TransactionId=[{TransactionId}], AgentId=[{AgentId}], AccountNumber=[{AccountNumber}], RequestUri=[{Uri}], RequestBody=[{RequestBody}]", + transactionId, agentId, accountNumber, request.RequestUri, requestBody); HttpResponseMessage response = await this.HttpClient.SendAsync(request, cancellationToken); String responseContent = await response.Content.ReadAsStringAsync(cancellationToken); - Logger.LogWarning($"AgencyBanking balance enquiry response received. TransactionId=[{transactionId}], StatusCode=[{response.StatusCode}], ContentLength=[{responseContent?.Length ?? 0}]"); + Logger.LogWarning("AgencyBanking balance enquiry response received. TransactionId=[{TransactionId}], StatusCode=[{StatusCode}], ContentLength=[{ContentLength}]", + transactionId, response.StatusCode, responseContent?.Length ?? 0); if (response.IsSuccessStatusCode == false) { - Logger.LogWarning($"AgencyBanking balance enquiry failed. TransactionId=[{transactionId}], StatusCode=[{response.StatusCode}], ResponseBody=[{responseContent}]"); + Logger.LogWarning("AgencyBanking balance enquiry failed. TransactionId=[{TransactionId}], StatusCode=[{StatusCode}], ResponseBody=[{ResponseContent}]", + transactionId, response.StatusCode, responseContent); return Result.Invalid($"Failed to process balance enquiry {responseContent}"); } BalanceEnquiryResponse balanceEnquiryResponse = StringSerialiser.Deserialise(responseContent, SerialiserOptions); - Logger.LogWarning($"AgencyBanking balance enquiry completed. TransactionId=[{transactionId}], ResponseMessage=[{balanceEnquiryResponse.ResponseMessage}], AvailableBalance=[{balanceEnquiryResponse.AvailableBalance}]"); + Logger.LogWarning("AgencyBanking balance enquiry completed. TransactionId=[{TransactionId}], ResponseMessage=[{ResponseMessage}], AvailableBalance=[{AvailableBalance}]", + transactionId, balanceEnquiryResponse.ResponseMessage, balanceEnquiryResponse.AvailableBalance); OperatorResponse operatorResponse = new() { IsSuccessful = true, diff --git a/TransactionProcessor.BusinessLogic/OperatorInterfaces/PataPawaPrePay/PataPawaPrePayProxy.cs b/TransactionProcessor.BusinessLogic/OperatorInterfaces/PataPawaPrePay/PataPawaPrePayProxy.cs index d84f50f0..5d7c7c85 100644 --- a/TransactionProcessor.BusinessLogic/OperatorInterfaces/PataPawaPrePay/PataPawaPrePayProxy.cs +++ b/TransactionProcessor.BusinessLogic/OperatorInterfaces/PataPawaPrePay/PataPawaPrePayProxy.cs @@ -46,21 +46,27 @@ public async Task> ProcessLogonMessage(CancellationToke content.Add(new StringContent(this.Configuration.Password), "password"); requestMessage.Content = content; + String bodyForLog = $"request=login&username={Uri.EscapeDataString(this.Configuration.Username)}&password=[REDACTED]"; + + Logger.LogWarning("Sending logon request to PataPawaPrePay {Url}. Form fields: {RequestBody}", requestMessage.RequestUri, bodyForLog); + HttpResponseMessage responseMessage = await this.HttpClient.SendAsync(requestMessage, cancellationToken); // Check the send was successful if (responseMessage.IsSuccessStatusCode == false) { + Logger.LogWarning("Error sending logon request to PataPawaPrePay {Url}. Status Code {StatusCode}", requestMessage.RequestUri, responseMessage.StatusCode); return Result.Failure($"Error sending logon request to Patapawa. Status Code [{responseMessage.StatusCode}]"); } // Get the response String responseContent = await responseMessage.Content.ReadAsStringAsync(cancellationToken); - Logger.LogInformation($"Received response message from Patapawa [{responseContent}]"); + Logger.LogWarning("Received logon response message from Patapawa [{ResponseContent}]", responseContent); return this.CreateFromLogon(responseContent); } catch (Exception ex) { + Logger.LogError(ex, "Error processing logon message for PataPawaPrePay"); return Result.Failure($"Error processing logon message for PataPawaPrePay [{ex.Message}]"); } } @@ -69,22 +75,23 @@ public async Task> ProcessSaleMessage(Guid transactionI Models.Merchant.Merchant merchant, DateTime transactionDateTime, String transactionReference, Dictionary additionalTransactionMetadata, CancellationToken cancellationToken){ // Get the logon response for the operator OperatorResponse logonResponse = this.MemoryCache.Get("PataPawaPrePayLogon"); - if (logonResponse == null) - { + if (logonResponse == null) { + Logger.LogWarning("PataPawaPrePay sale request rejected. TransactionId=[{TransactionId}] - logonResponse is null", transactionId); return Result.Invalid("PataPawaPrePayLogon - logonResponse is null"); } + String apiKey = logonResponse.AdditionalTransactionResponseMetadata.ExtractFieldFromMetadata("PataPawaPrePaidAPIKey"); - if (String.IsNullOrEmpty(apiKey)) - { + if (String.IsNullOrEmpty(apiKey)) { + Logger.LogWarning("PataPawaPrePay sale request rejected. TransactionId=[{TransactionId}] - APIKey is a required field for this transaction type", transactionId); return Result.Invalid("PataPawaPrePayAPIKey - APIKey is a required field for this transaction type"); } // Check the meta data for the sale message type String messageType = additionalTransactionMetadata.ExtractFieldFromMetadata("PataPawaPrePayMessageType"); - if (String.IsNullOrEmpty(messageType)) - { + if (String.IsNullOrEmpty(messageType)) { + Logger.LogWarning("PataPawaPrePay sale request rejected. TransactionId=[{TransactionId}] - Message Type is a required field for this transaction type", transactionId); return Result.Invalid("PataPawaPrePayMessageType - Message Type is a required field for this transaction type"); } @@ -92,13 +99,14 @@ public async Task> ProcessSaleMessage(Guid transactionI if (String.IsNullOrEmpty(meterNumber)) { + Logger.LogWarning("PataPawaPrePay sale request rejected. TransactionId=[{TransactionId}] - Meter Number is a required field for this transaction type", transactionId); return Result.Invalid("MeterNumber - Meter Number is a required field for this transaction type"); } return messageType switch { "meter" => await this.PerformMeterTransaction(meterNumber, apiKey, cancellationToken), - "vend" => await this.PerformVendTransaction(meterNumber, apiKey, additionalTransactionMetadata, cancellationToken), + "vend" => await this.PerformVendTransaction(transactionId, meterNumber, apiKey, additionalTransactionMetadata, cancellationToken), _ => Result.Invalid($"PataPawaPrePayMessageType - Unsupported Message Type {messageType}") }; } @@ -113,12 +121,18 @@ private async Task> PerformMeterTransaction(String mete content.Add(new StringContent(apiKey), "key"); requestMessage.Content = content; + string bodyForLog = + $"request=meter&username={Uri.EscapeDataString(this.Configuration.Username)}" + + $"&meter={Uri.EscapeDataString(meterNumber)}&key=[REDACTED]"; + + Logger.LogWarning("Sending meter request to PataPawaPrePay {Url}. Form fields: {RequestBody}", requestMessage.RequestUri, bodyForLog); + HttpResponseMessage responseMessage = await this.HttpClient.SendAsync(requestMessage, cancellationToken); // Get the response String responseContent = await responseMessage.Content.ReadAsStringAsync(cancellationToken); - Logger.LogInformation($"Received response message from Patapawa [{responseContent}]"); + Logger.LogInformation("Received response message from PataPawaPrePay [{ResponseContent}]", responseContent); return this.CreateFromMeter(responseContent); } @@ -126,17 +140,19 @@ private async Task> PerformMeterTransaction(String mete throw; } catch (Exception ex) { + Logger.LogError(ex, "Error processing meter message for PataPawaPrePay"); return Result.Failure($"Error performing meter transaction for meter number {meterNumber} [{ex.Message}]"); } } - private async Task> PerformVendTransaction(String meterNumber, String apiKey, Dictionary additionalTransactionMetadata, CancellationToken cancellationToken) + private async Task> PerformVendTransaction(Guid transactionId, String meterNumber, String apiKey, Dictionary additionalTransactionMetadata, CancellationToken cancellationToken) { try { String customerName = additionalTransactionMetadata.ExtractFieldFromMetadata("CustomerName"); if (String.IsNullOrEmpty(customerName)) { + Logger.LogWarning("PataPawaPrePay vend request rejected. TransactionId=[{TransactionId}] - CustomerName is a required field for this transaction type", transactionId); return Result.Invalid("CustomerName - Customer Name is a required field for this transaction type"); } @@ -144,10 +160,10 @@ private async Task> PerformVendTransaction(String meter if (String.IsNullOrEmpty(amount)) { + Logger.LogWarning("PataPawaPrePay sale request rejected. TransactionId=[{TransactionId}] - Amount is a required field for this transaction type", transactionId); return Result.Invalid("Amount - Amount is a required field for this transaction type"); } - HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, this.Configuration.Url); MultipartFormDataContent content = new MultipartFormDataContent(); content.Add(new StringContent("vend"), "request"); @@ -158,12 +174,21 @@ private async Task> PerformVendTransaction(String meter content.Add(new StringContent(customerName), "customerName"); requestMessage.Content = content; + string bodyForLog = + $"request=vend&username={Uri.EscapeDataString(this.Configuration.Username)}" + + $"&meter={Uri.EscapeDataString(meterNumber)}" + + $"&key=[REDACTED]" + + $"&amount={Uri.EscapeDataString(amount)}" + + $"&customerName={Uri.EscapeDataString(customerName)}"; + + Logger.LogWarning("Sending vend request to PataPawaPrePay {Url}. Form fields: {RequestBody}", requestMessage.RequestUri, bodyForLog); + HttpResponseMessage responseMessage = await this.HttpClient.SendAsync(requestMessage, cancellationToken); // Get the response String responseContent = await responseMessage.Content.ReadAsStringAsync(cancellationToken); - Logger.LogInformation($"Received response message from Patapawa [{responseContent}]"); + Logger.LogInformation("Received response message from PataPawaPrePay [{ResponseContent}]", responseContent); return this.CreateFromVend(responseContent); } @@ -171,6 +196,7 @@ private async Task> PerformVendTransaction(String meter throw; } catch (Exception ex) { + Logger.LogError(ex, "Error processing meter message for PataPawaPrePay"); return Result.Failure($"Error performing vend transaction for meter number {meterNumber} [{ex.Message}]"); } } diff --git a/TransactionProcessor.BusinessLogic/OperatorInterfaces/SafaricomPinless/SafaricomPinlessProxy.cs b/TransactionProcessor.BusinessLogic/OperatorInterfaces/SafaricomPinless/SafaricomPinlessProxy.cs index 288b3acd..7f49a283 100644 --- a/TransactionProcessor.BusinessLogic/OperatorInterfaces/SafaricomPinless/SafaricomPinlessProxy.cs +++ b/TransactionProcessor.BusinessLogic/OperatorInterfaces/SafaricomPinless/SafaricomPinlessProxy.cs @@ -85,18 +85,20 @@ public async Task> ProcessSaleMessage(Guid transactionI if (String.IsNullOrEmpty(transactionAmount)) { + Logger.LogWarning("Safaricom sale request rejected. TransactionId=[{TransactionId}] - Amount is a required field for this transaction type", transactionId); return Result.Invalid("Amount is a required field for this transaction type"); } if (String.IsNullOrEmpty(customerMsisdn)) { + Logger.LogWarning("Safaricom sale request rejected. TransactionId=[{TransactionId}] - CustomerAccountNumber is a required field for this transaction type", transactionId); return Result.Invalid("CustomerAccountNumber is a required field for this transaction type"); } // Multiply amount before sending // Covert the transaction amount to Decimal and remove decimal places - if (Decimal.TryParse(transactionAmount, out Decimal amountAsDecimal) == false) - { + if (Decimal.TryParse(transactionAmount, out Decimal amountAsDecimal) == false) { + Logger.LogWarning("Safaricom sale request rejected. TransactionId=[{TransactionId}] - Transaction Amount is not a valid decimal value", transactionId); return Result.Invalid("Transaction Amount is not a valid decimal value"); } @@ -104,24 +106,24 @@ public async Task> ProcessSaleMessage(Guid transactionI String requestUrl = this.BuildRequest(transactionDateTime, transactionReference, customerMsisdn, operatorTransactionAmount); - Logger.LogInformation($"Sending message to Safaricom [{requestUrl}]"); + Logger.LogInformation("Sending message to Safaricom [{RequestBody}]", requestUrl); // Concatenate the request message HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Get, new Uri(requestUrl)); // Send the request to Safaricom HttpResponseMessage responseMessage = await this.HttpClient.SendAsync(requestMessage, cancellationToken); + String responseContent = await responseMessage.Content.ReadAsStringAsync(cancellationToken); + Logger.LogWarning("Safaricom response received. TransactionId=[{TransactionId}], StatusCode=[{StatusCode}], ContentLength=[{ContentLength}]", + transactionId, responseMessage.StatusCode, responseContent?.Length ?? 0); // Check the send was successful - if (responseMessage.IsSuccessStatusCode == false) - { + if (responseMessage.IsSuccessStatusCode == false) { + Logger.LogWarning("Safaricom sale failed. TransactionId=[{TransactionId}], StatusCode=[{StatusCode}], ResponseBody=[{ResponseContent}]", transactionId, responseMessage.StatusCode, responseContent); return Result.Failure($"Error sending request [{requestUrl}] to Safaricom. Status Code [{responseMessage.StatusCode}]"); } - - // Get the response - String responseContent = await responseMessage.Content.ReadAsStringAsync(); - - Logger.LogInformation($"Received response message from Safaricom [{responseContent}]"); + + Logger.LogInformation("Safaricom sale completed [{ResponseContent}]", responseContent); return this.CreateFrom(responseContent); } @@ -129,6 +131,7 @@ public async Task> ProcessSaleMessage(Guid transactionI throw; } catch (Exception ex) { + Logger.LogError(ex, "Error sending request to Safaricom"); return Result.Failure($"Error sending request to Safaricom [{ex.Message}]"); } } diff --git a/TransactionProcessor.BusinessLogic/OperatorInterfaces/VoucherManagement/VoucherManagementProxy.cs b/TransactionProcessor.BusinessLogic/OperatorInterfaces/VoucherManagement/VoucherManagementProxy.cs index 8faf5256..97cf19ab 100644 --- a/TransactionProcessor.BusinessLogic/OperatorInterfaces/VoucherManagement/VoucherManagementProxy.cs +++ b/TransactionProcessor.BusinessLogic/OperatorInterfaces/VoucherManagement/VoucherManagementProxy.cs @@ -47,11 +47,13 @@ public async Task> ProcessSaleMessage(Guid transactionI // Covert the transaction amount to Decimal and remove decimal places if (Decimal.TryParse(transactionAmount, out Decimal amountAsDecimal) == false) { + Logger.LogWarning("Voucher sale request rejected. TransactionId=[{TransactionId}] - Transaction Amount is not a valid decimal value", transactionId); return Result.Invalid("Transaction Amount is not a valid decimal value"); } if (String.IsNullOrEmpty(recipientEmail) && String.IsNullOrEmpty(recipientMobile)) { + Logger.LogWarning("Voucher sale request rejected. TransactionId=[{TransactionId}] - Recipient details (either email or mobile) is a required field for this transaction type", transactionId); return Result.Invalid("Recipient details (either email or mobile) is a required field for this transaction type"); } diff --git a/TransactionProcessor.BusinessLogic/Services/SettlementDomainService.cs b/TransactionProcessor.BusinessLogic/Services/SettlementDomainService.cs index 5c33547a..7b7a881f 100644 --- a/TransactionProcessor.BusinessLogic/Services/SettlementDomainService.cs +++ b/TransactionProcessor.BusinessLogic/Services/SettlementDomainService.cs @@ -119,7 +119,7 @@ private async Task> ProcessSettlementFees(List<(Guid transactionId, return ResultHelpers.CreateFailure(saveResult); } catch (Exception ex) { - Logger.LogError($"Failed to process transaction {feeToSettle.transactionId} for settlement {settlementAggregateId}", ex); + Logger.LogError(ex,$"Failed to process transaction {feeToSettle.transactionId} for settlement {settlementAggregateId}"); failedResults.Add(Result.Failure($"Failed to process transaction {feeToSettle.transactionId} for settlement {settlementAggregateId}: {ex.Message}")); } } @@ -145,10 +145,8 @@ public async Task AddMerchantFeePendingSettlement(SettlementCommands.Add SettlementAggregate settlementAggregate = getSettlementResult.Data; - Logger.LogInformation("In ApplySettlementUpdates"); if (settlementAggregate.IsCreated == false) { - Logger.LogInformation("In ApplySettlementUpdates - aggregate not created"); Result createResult = settlementAggregate.Create(command.EstateId, command.MerchantId, command.SettlementDueDate.Date); if (createResult.IsFailed) return ResultHelpers.CreateFailure(createResult); @@ -165,7 +163,6 @@ public async Task AddMerchantFeePendingSettlement(SettlementCommands.Add FeeValue = command.FeeValue, SettlementDueDate = command.SettlementDueDate }; - Logger.LogInformation("In ApplySettlementUpdates - about to add fee"); Result stateResult = settlementAggregate.AddFee(command.MerchantId, command.TransactionId, calculatedFee); if (stateResult.IsFailed) return ResultHelpers.CreateFailure(stateResult); diff --git a/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs b/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs index 731caebc..5ddade9e 100644 --- a/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs +++ b/TransactionProcessor.BusinessLogic/Services/TransactionDomainService.cs @@ -474,8 +474,7 @@ private async Task> ProcessMessageWithOperator(Models.M } catch (Exception e) { // Log out the error - Logger.LogError(e); - Logger.LogWarning(e.StackTrace); + Logger.LogError(e, "Error processing operator message"); return ResultHelpers.CreateFailedResult(new OperatorResponse { IsSuccessful = false, ResponseCode = "9999", ResponseMessage = e.GetCombinedExceptionMessages() }); } } @@ -550,7 +549,7 @@ private async Task ResendEmailMessage(String accessToken, public async Task> ProcessSaleTransaction(TransactionCommands.ProcessSaleTransactionCommand command, CancellationToken cancellationToken) { - Logger.LogInformation($"Starting ProcessSaleTransaction for TransactionId {command.TransactionId}"); + Logger.LogInformation("Starting ProcessSaleTransaction for TransactionId {TransactionId}", command.TransactionId); try { Result transactionResult = await GetTransactionAggregate(command, cancellationToken); @@ -580,7 +579,7 @@ public async Task> ProcessSaleTransaction return ResultHelpers.CreateFailure(saveResult); Transaction transaction = transactionAggregate.GetTransaction(); - Logger.LogInformation($"Transaction {command.TransactionId} completed successfully"); + Logger.LogInformation("Transaction {TransactionId} completed successfully", command.TransactionId); return Result.Success(new ProcessSaleTransactionResponse { ResponseMessage = transaction.ResponseMessage, @@ -592,19 +591,19 @@ public async Task> ProcessSaleTransaction }); } catch (Exception ex) { - Logger.LogError($"Unhandled exception during ProcessSaleTransaction for {command.TransactionId}", ex); + Logger.LogError(ex, "Unhandled exception during ProcessSaleTransaction for {TransactionId}", command.TransactionId); return Result.Failure(ex.GetExceptionMessages()); } } private async Task> GetTransactionAggregate(TransactionCommands.ProcessSaleTransactionCommand command, CancellationToken cancellationToken) { - Logger.LogDebug($"Fetching TransactionAggregate for TransactionId {command.TransactionId}"); + Logger.LogDebug("Fetching TransactionAggregate for TransactionId {TransactionId}", command.TransactionId); Result result = await DomainServiceHelper.GetAggregateOrFailure(ct => AggregateService.GetLatest(command.TransactionId, ct), command.TransactionId, cancellationToken, false); if (result.IsFailed) - Logger.LogWarning($"TransactionAggregate not found for TransactionId {command.TransactionId}"); + Logger.LogWarning("TransactionAggregate not found for TransactionId {TransactionId}", command.TransactionId); return result; } @@ -613,12 +612,13 @@ private async Task> GetTransactionAggregate(Transac private async Task> ValidateTransaction(TransactionCommands.ProcessSaleTransactionCommand command, CancellationToken cancellationToken) { Decimal? amount = command.AdditionalTransactionMetadata.ExtractFieldFromMetadata("Amount"); - Logger.LogInformation($"Validating Sale Transaction for Merchant {command.MerchantId}, Product {command.ProductId}"); + Logger.LogInformation("Validating Sale Transaction Id {TransactionId} for Merchant {MerchantId}, Product {ProductId}", command.TransactionId, command.MerchantId, command.ProductId); TransactionSource transactionSourceValue = (TransactionSource)command.TransactionSource; Result result = await TransactionValidationService.ValidateSaleTransaction(command.EstateId, command.MerchantId, command.ContractId, command.ProductId, command.DeviceIdentifier, command.OperatorId, amount, transactionSourceValue, cancellationToken); - Logger.LogInformation($"Validation completed with ResponseCode {result.Data?.ResponseCode}, Message {result.Data?.ResponseMessage}"); + Logger.LogInformation("Validation completed for Transaction Id {TransactionId} with ResponseCode {ResponseCode}, Message {ResponseMessage}", + command.TransactionId, result.Data?.ResponseCode, result.Data?.ResponseMessage); return result; } @@ -628,7 +628,7 @@ private async Task> ValidateTransaction(Tran CancellationToken cancellationToken) { Guid floatAggregateId = command.OperatorId; - Logger.LogDebug($"Fetching FloatAggregate {floatAggregateId}"); + Logger.LogDebug("Fetching FloatAggregate {FloatId} for TransactionId {TransactionId}", floatAggregateId, command.TransactionId); Result floatAggregateResult = await DomainServiceHelper.GetAggregateOrFailure(ct => AggregateService.GetLatest(floatAggregateId, ct), floatAggregateId, cancellationToken, false); @@ -640,7 +640,7 @@ private async Task> ValidateTransaction(Tran Decimal unitCost = floatAggregate.GetUnitCostPrice(); Decimal totalCost = floatAggregate.GetTotalCostPrice(amount); - Logger.LogInformation($"Float cost calculated: UnitCost={unitCost}, TotalCost={totalCost}"); + Logger.LogInformation("Float cost calculated for TransactionId {TransactionId}: UnitCost={UnitCost}, TotalCost={TotalCost}", command.TransactionId, unitCost, totalCost); return (unitCost, totalCost); } @@ -704,12 +704,14 @@ private static Result ApplyReconciliationValidationResult(ReconciliationAggregat DateTime? start = null, end = null; if (validationResult.Data.ResponseCode != TransactionResponseCode.Success) { - Logger.LogInformation($"Validation failed with ResponseCode {validationResult.Data.ResponseCode}: {validationResult.Data.ResponseMessage}"); + Logger.LogInformation("Validation failed for TransactionId {TransactionId} with ResponseCode {ResponseCode}: {ResponseMessage}", + command.TransactionId, validationResult.Data.ResponseCode, validationResult.Data.ResponseMessage); + transactionAggregate.DeclineTransactionLocally(validationResult.Data.ResponseCode, validationResult.Data.ResponseMessage); return (start, end); } - Logger.LogInformation($"Validation success, processing with Operator {command.OperatorId}"); + Logger.LogInformation("Validation success for TransactionId {TransactionId} processing with Operator {OperatorId}", command.TransactionId, command.OperatorId); transactionAggregate.RecordAdditionalRequestData(command.OperatorId, command.AdditionalTransactionMetadata); Result merchantResult = await GetMerchant(command.MerchantId, cancellationToken); @@ -721,11 +723,11 @@ private static Result ApplyReconciliationValidationResult(ReconciliationAggregat end = DateTime.Now; if (operatorResult.IsSuccess) { - Logger.LogInformation("Operator authorised transaction successfully"); + Logger.LogInformation("Operator {OperatorId} authorised transaction {TransactionId} successfully", command.OperatorId, command.TransactionId); transactionAggregate.AuthoriseTransaction(command.OperatorId, operatorResult.Data.AuthorisationCode, operatorResult.Data.ResponseCode, operatorResult.Data.ResponseMessage, operatorResult.Data.TransactionId, TransactionResponseCode.Success, "SUCCESS"); } else { - Logger.LogWarning($"Operator declined transaction: {operatorResult.Data.ResponseMessage}"); + Logger.LogWarning("Operator {OperatorId} declined transaction {TransactionId}: {ResponseMessage}", command.OperatorId, command.TransactionId, operatorResult.Data.ResponseMessage); transactionAggregate.DeclineTransaction(command.OperatorId, operatorResult.Data.ResponseCode, operatorResult.Data.ResponseMessage, TransactionResponseCode.TransactionDeclinedByOperator, "DECLINED BY OPERATOR"); } diff --git a/TransactionProcessor.DatabaseTests/DatabaseTestFixture.cs b/TransactionProcessor.DatabaseTests/DatabaseTestFixture.cs index 4f827cfb..597e727a 100644 --- a/TransactionProcessor.DatabaseTests/DatabaseTestFixture.cs +++ b/TransactionProcessor.DatabaseTests/DatabaseTestFixture.cs @@ -43,9 +43,10 @@ public string GetLocalConnectionString(string databaseName) private NlogLogger CreateLogger() { - NlogLogger logger = new NlogLogger(); + NlogLogger logger = new(); logger.Initialise(LogManager.GetLogger("Specflow"), "Specflow"); - LogManager.AddHiddenAssembly(typeof(NlogLogger).Assembly); + LogManager.Setup().SetupLogFactory(setup => + setup.AddCallSiteHiddenAssembly(typeof(NlogLogger).Assembly)); return logger; } } diff --git a/TransactionProcessor.IntegrationTests/Common/DockerHelper.cs b/TransactionProcessor.IntegrationTests/Common/DockerHelper.cs index 317d7b72..e61e64f7 100644 --- a/TransactionProcessor.IntegrationTests/Common/DockerHelper.cs +++ b/TransactionProcessor.IntegrationTests/Common/DockerHelper.cs @@ -131,9 +131,8 @@ public override ContainerBuilder SetupTestHostContainer() (String imageName, Boolean useLatest) imageDetails = this.GetImageDetails(ContainerType.TestHost).Data; - ContainerBuilder testHostContainer = new ContainerBuilder() + ContainerBuilder testHostContainer = new ContainerBuilder(imageDetails.imageName) .WithName(this.TestHostContainerName) // similar to WithName() - .WithImage(imageDetails.imageName) .WithEnvironment(environmentVariables) .MountHostFolder(this.HostTraceFolder) .WithPortBinding(DockerPorts.TestHostPort, true); diff --git a/TransactionProcessor.IntegrationTests/Common/GenericSteps.cs b/TransactionProcessor.IntegrationTests/Common/GenericSteps.cs index fd24e23d..f291360d 100644 --- a/TransactionProcessor.IntegrationTests/Common/GenericSteps.cs +++ b/TransactionProcessor.IntegrationTests/Common/GenericSteps.cs @@ -29,9 +29,10 @@ public async Task StartSystem() { // Initialise a logger String scenarioName = this.ScenarioContext.ScenarioInfo.Title.Replace(" ", ""); - NlogLogger logger = new NlogLogger(); + NlogLogger logger = new(); logger.Initialise(LogManager.GetLogger(scenarioName), scenarioName); - LogManager.AddHiddenAssembly(typeof(NlogLogger).Assembly); + LogManager.Setup().SetupLogFactory(setup => + setup.AddCallSiteHiddenAssembly(typeof(NlogLogger).Assembly)); DockerServices dockerServices = DockerServices.SqlServer | DockerServices.CallbackHandler | DockerServices.KurrentDb | DockerServices.FileProcessor | DockerServices.MessagingService | DockerServices.SecurityService | diff --git a/TransactionProcessor.Repository/ITransactionProcessorReadModelRepository.cs b/TransactionProcessor.Repository/ITransactionProcessorReadModelRepository.cs index a9798ae2..c160492c 100644 --- a/TransactionProcessor.Repository/ITransactionProcessorReadModelRepository.cs +++ b/TransactionProcessor.Repository/ITransactionProcessorReadModelRepository.cs @@ -2369,7 +2369,7 @@ public static async Task> ExecuteSafeAsync(Func ExecuteSafeAsync(Func () => Logger.LogWarning(message), - LogLevel.Information => () => Logger.LogInformation(message), - LogLevel.Debug => () => Logger.LogDebug(message), - LogLevel.Trace => () => Logger.LogTrace(message), - _ => () => Logger.LogInformation(message) + LogLevel.Warning => () => Logger.LogWarning(messageTemplate, args), + LogLevel.Information => () => Logger.LogInformation(messageTemplate, args), + LogLevel.Debug => () => Logger.LogDebug(messageTemplate, args), + LogLevel.Trace => () => Logger.LogTrace(messageTemplate, args), + _ => () => Logger.LogInformation(messageTemplate, args) }; action(); diff --git a/TransactionProcessor/Controllers/DomainEventController.cs b/TransactionProcessor/Controllers/DomainEventController.cs index 3d8f62cd..a94ae779 100644 --- a/TransactionProcessor/Controllers/DomainEventController.cs +++ b/TransactionProcessor/Controllers/DomainEventController.cs @@ -88,26 +88,23 @@ public async Task PostEventAsync([FromBody] Object request, try { - Logger.LogWarning($"Processing event - ID [{domainEvent.EventId}], Type[{domainEvent.GetType().Name}]"); + Logger.LogWarning("Processing event - ID [{EventId}], Type[{EventType}]", domainEvent.EventId, domainEvent.GetType().Name); - if (eventHandlers == null || eventHandlers.Any() == false) - { + if (eventHandlers == null || eventHandlers.Any() == false) { // Log a warning out - Logger.LogInformation($"No event handlers configured for Event Type [{domainEvent.GetType().Name}]"); + Logger.LogInformation("No event handlers configured for Event Type [{EventType}]", domainEvent.GetType().Name); return Results.Ok(); } List executions = eventHandlers - .Select(domainEventHandler => this.ExecuteHandler(domainEventHandler, domainEvent, cancellationToken)) - .ToList(); + .Select(domainEventHandler => this.ExecuteHandler(domainEventHandler, domainEvent, cancellationToken)) + .ToList(); - try - { + try { await Task.WhenAll(executions.Select(execution => execution.Task)); } - catch (Exception ex) - { - Logger.LogError(new Exception($"One or more handlers threw while processing event [{domainEvent.EventId}]", ex)); + catch (Exception ex) { + Logger.LogError(ex, "One or more handlers threw while processing event [{EventId}]", domainEvent.EventId); } List failures = executions.SelectMany(this.GetFailures).ToList(); @@ -123,14 +120,14 @@ public async Task PostEventAsync([FromBody] Object request, }); } - Logger.LogWarning($"Finished processing event - ID [{domainEvent.EventId}]"); + Logger.LogWarning("Finished processing event - ID [{EventId}]", domainEvent.EventId); return Results.Ok(); } catch (Exception ex) { String domainEventData = StringSerialiser.Serialise(domainEvent); - Logger.LogError(new Exception($" Failed to Process Event, Event Data received [{domainEventData}]", ex)); + Logger.LogError(ex,"Failed to Process Event Id {EventId}, Event Data received [{EventData}]", domainEvent.EventId, domainEventData); throw; } @@ -157,7 +154,7 @@ private void Callback(CancellationToken cancellationToken, { if (cancellationToken.IsCancellationRequested) //I think this would always be true anyway { - Logger.LogInformation($"Cancel request for EventId {eventId}"); + Logger.LogInformation("Cancel request for EventId {EventId}", eventId); cancellationToken.ThrowIfCancellationRequested(); } } diff --git a/TransactionProcessor/Extensions.cs b/TransactionProcessor/Extensions.cs index be00b29c..0c71934d 100644 --- a/TransactionProcessor/Extensions.cs +++ b/TransactionProcessor/Extensions.cs @@ -1,5 +1,6 @@ using JasperFx.Core; using MediatR; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal; using SimpleResults; using System.IO; using TransactionProcessor.Aggregates; @@ -24,6 +25,7 @@ namespace TransactionProcessor using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; + using System.IO.Abstractions; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -38,10 +40,10 @@ public static class Extensions { String logMessage = $"{subType} - {message}"; switch (tt) { case TraceEventType.Critical: - Logger.LogCritical(new Exception(logMessage)); + Logger.LogCritical(logMessage); break; case TraceEventType.Error: - Logger.LogError(new Exception(logMessage)); + Logger.LogError(logMessage); break; case TraceEventType.Warning: Logger.LogWarning(logMessage); @@ -124,7 +126,8 @@ public StatementFilePollerService(IMediator mediator, IConfiguration configurati public void Start() { foreach (FileProfile fileProfile in this.FileProfiles) { - Logger.LogWarning($"File Profile: {fileProfile.Name} Listening on Folder: [{fileProfile.ListeningDirectory}] IsEnabled: {fileProfile.IsEnabled}"); + String logMessage = $"Listening on Folder: [{fileProfile.ListeningDirectory}] IsEnabled: {fileProfile.IsEnabled}"; + Logger.LogWarning("File Profile: {FileProfileName} " + logMessage, fileProfile.Name); Directory.CreateDirectory(fileProfile.ListeningDirectory); FileSystemWatcher fileSystemWatcher = new FileSystemWatcher @@ -148,7 +151,7 @@ private void FileHandler(FileSystemEventArgs e, FileProfile fileProfile) String failedFolder = Path.Combine(fileProfile.ListeningDirectory, "failed"); try { - Logger.LogInformation($"File detected on profile {fileProfile.Name} File name [{e.Name}]"); + Logger.LogInformation("File detected on profile {FileProfileName} File name [{FileName}]", fileProfile.Name, e.Name); // Make sure the file is not locked by another process await using (FileStream stream = new FileStream(e.FullPath, FileMode.Open, FileAccess.Read, FileShare.None)) { @@ -186,14 +189,14 @@ private void FileHandler(FileSystemEventArgs e, FileProfile fileProfile) String failedFilePath = Path.Combine(failedFolder, e.Name); File.Move(e.FullPath, failedFilePath); // Log the error - Logger.LogWarning($"Failed to process file [{e.FullPath}]: {result.Message}"); + Logger.LogWarning("Failed to process file [{FileName}]: " + result.Message, e.FullPath); } else { // Move the file to the processed folder String processedFilePath = Path.Combine(processedFolder, e.Name); File.Move(e.FullPath, processedFilePath); // Log success - Logger.LogInformation($"Successfully processed file [{e.FullPath}]"); + Logger.LogInformation("Successfully processed file [{FileName}]", e.FullPath); } } catch (Exception ex) { @@ -202,8 +205,7 @@ private void FileHandler(FileSystemEventArgs e, FileProfile fileProfile) String failedFilePath = Path.Combine(failedFolder, e.Name); File.Move(e.FullPath, failedFilePath); // Log the error - Console.Error.WriteLine($"Failed to process file [{e.FullPath}]: {ex.Message}"); - Logger.LogError($"Failed to process file [{e.FullPath}]", ex); + Logger.LogError(ex, "Failed to process file [{FileName}]", e.FullPath); } }); } @@ -243,30 +245,30 @@ internal async Task OperatorLogonAsync(String operatorId, CancellationToken canc try { Func resolver = this.OperatorProxyResolver; if (resolver == null) { - Logger.LogWarning($"Auto logon for operator Id [{operatorId}] skipped because no operator proxy resolver was registered"); + Logger.LogWarning("Auto logon for operator Id [{OperatorId}] skipped because no operator proxy resolver was registered", operatorId); return; } IOperatorProxy proxy = resolver(operatorId); if (proxy == null) { - Logger.LogWarning($"Auto logon for operator Id [{operatorId}] skipped because no operator proxy was resolved"); + Logger.LogWarning("Auto logon for operator Id [{OperatorId}] skipped because no operator proxy was resolved", operatorId); return; } Result logonResult = await proxy.ProcessLogonMessage(cancellationToken); if (logonResult.IsSuccess) { - Logger.LogInformation($"Auto logon for operator Id [{operatorId}] status [{logonResult.Data.IsSuccessful}]"); + Logger.LogInformation("Auto logon for operator Id [{OperatorId}] status [{" + logonResult.Data.IsSuccessful + "}]", operatorId); return; } - Logger.LogWarning($"Auto logon for operator Id [{operatorId}] status [{logonResult.Message}]"); + Logger.LogWarning("Auto logon for operator Id [{OperatorId}] status [{" + logonResult.Message + "}]", operatorId); } catch (RpcException ex) when (ex.StatusCode == StatusCode.Unavailable) { - Logger.LogWarning($"Auto logon for operator Id [{operatorId}] deferred because the operator gRPC service is unavailable: {ex.Status.Detail}"); + Logger.LogWarning("Auto logon for operator Id [{operatorId}] deferred because the operator gRPC service is unavailable: " + ex.Status.Detail, operatorId); } catch (Exception ex) { - Logger.LogError(new Exception($"Auto logon for operator Id [{operatorId}] failed", ex)); + Logger.LogError(ex, "Auto logon for operator Id [{OperatorId}] failed", operatorId); } } } diff --git a/TransactionProcessor/Program.cs b/TransactionProcessor/Program.cs index c91ef077..8ce9d0f7 100644 --- a/TransactionProcessor/Program.cs +++ b/TransactionProcessor/Program.cs @@ -77,7 +77,9 @@ public static WebApplication BuildApplication(string[] args) }); WebApplication app = builder.Build(); - Logger.Initialise(app.Services.GetRequiredService().CreateLogger("TransactionProcessor")); + LogManager.Setup().LoadConfigurationFromFile("NLog.config"); + NLog.Logger logger = LogManager.GetLogger("TransactionProcessor"); + Shared.Logger.Logger.Initialise(logger, "TransactionProcessor"); configuration.LogConfiguration(Logger.LogWarning); StringSerialiser.Initialise(app.Services.GetRequiredService()); TypeProvider.LoadDomainEventsTypeDynamically(); From 685a8d8897b010d668eec5933752e2b4520a9c3b Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Mon, 26 Oct 2026 16:32:39 +0000 Subject: [PATCH 2/3] fix from review --- .../OperatorInterfaces/AgencyBanking/AgencyBankingProxy.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TransactionProcessor.BusinessLogic/OperatorInterfaces/AgencyBanking/AgencyBankingProxy.cs b/TransactionProcessor.BusinessLogic/OperatorInterfaces/AgencyBanking/AgencyBankingProxy.cs index ebaf21c4..7a5ce771 100644 --- a/TransactionProcessor.BusinessLogic/OperatorInterfaces/AgencyBanking/AgencyBankingProxy.cs +++ b/TransactionProcessor.BusinessLogic/OperatorInterfaces/AgencyBanking/AgencyBankingProxy.cs @@ -210,7 +210,7 @@ private async Task> ProcessDeposit(Guid transactionId, request.Content = CreateContent(body); string requestBody = await request.Content.ReadAsStringAsync(cancellationToken); Logger.LogWarning("AgencyBanking deposit request starting. TransactionId=[{TransactionId}], AgentId=[{AgentId}], AccountNumber=[{AccountNumber}], RequestUri=[{RequestUri}], RequestBody=[{RequestBody}]", - transactionId, accountNumber, request.RequestUri, requestBody); + transactionId, agentId, accountNumber, request.RequestUri, requestBody); HttpResponseMessage response = await this.HttpClient.SendAsync(request, cancellationToken); String responseContent = await response.Content.ReadAsStringAsync(cancellationToken); From e14386b814aef76c557f35cdfabb27abbe7b0529 Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Mon, 26 Oct 2026 17:50:51 +0000 Subject: [PATCH 3/3] fix logging --- TransactionProcessor/nlog.config | 34 ++++++++++++++++---------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/TransactionProcessor/nlog.config b/TransactionProcessor/nlog.config index 96828f11..4ab07db7 100644 --- a/TransactionProcessor/nlog.config +++ b/TransactionProcessor/nlog.config @@ -11,14 +11,22 @@ archiveNumbering="Sequence" concurrentWrites="true" maxArchiveFiles="2"/> - + + + + + + + + @@ -33,15 +41,7 @@ s - - - - - - - - - + \ No newline at end of file