From bb76047605c7d8d0db8bcc27c145438e2c85b607 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Mon, 23 Jun 2025 17:09:39 +0530 Subject: [PATCH 1/4] Implemeted an API to store logs from mobile Application --- .../Controllers/LogController.cs | 130 ++++++++++++++++++ Marco.Pms.Services/Service/ILoggingService.cs | 5 +- Marco.Pms.Services/Service/LoggingServices.cs | 18 ++- 3 files changed, 148 insertions(+), 5 deletions(-) create mode 100644 Marco.Pms.Services/Controllers/LogController.cs diff --git a/Marco.Pms.Services/Controllers/LogController.cs b/Marco.Pms.Services/Controllers/LogController.cs new file mode 100644 index 0000000..6047642 --- /dev/null +++ b/Marco.Pms.Services/Controllers/LogController.cs @@ -0,0 +1,130 @@ +using System.Text.Json; +using Marco.Pms.Model.Logs; +using MarcoBMS.Services.Service; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Serilog.Context; + +namespace Marco.Pms.Services.Controllers +{ + [Authorize] + [Route("api/[controller]")] + [ApiController] + public class LogController : ControllerBase + { + private readonly ILoggingService _logger; + public LogController(ILoggingService logger) + { + _logger = logger; + } + [HttpPost] + /// + /// Receives a batch of log entries from mobile applications and processes them. + /// Logs are enriched with context properties like UserAgent, Timestamp, and IpAddress. + /// + /// A list of LogStructure objects containing log details. + /// An indicating the success of the operation. + public IActionResult MobileLogging([FromBody] List logs) + { + var demoDetails = new + { + name = "Pooja", + job = "Tester", + org = "MarcoAiot" + }; + // Check if logs are provided to avoid processing empty requests + if (logs == null || !logs.Any()) + { + _logger.LogWarning("MobileLogging endpoint received an empty or null log list."); + return BadRequest("No log entries provided."); + } + + // Iterate through each log entry received from the client + foreach (var log in logs) + { + object? detailsObject = null; + + // Attempt to deserialize the 'Details' string into a dynamic object + // This allows structured logging of the details payload. + log.Details = JsonSerializer.Serialize(demoDetails); + if (!string.IsNullOrWhiteSpace(log.Details)) + { + try + { + // Use JsonDocument for potentially more efficient parsing if you just need the raw JSON or a dynamic object + // For simply logging it as a structured property, object works. + detailsObject = JsonSerializer.Deserialize>(log.Details); + + } + catch (JsonException ex) + { + // Log a warning if deserialization fails, but continue processing other logs + _logger.LogWarning("Failed to deserialize 'Details' for a log entry. Raw details: '{RawDetails}'. Error: {ErrorMessage}", log.Details, ex.Message); + } + } + + // Push common properties to the Serilog LogContext for the current log operation. + // These properties will be attached to the log event itself. + using (LogContext.PushProperty("UserAgent", log.UserAgent)) + using (LogContext.PushProperty("Timestamp", log.TimeStamp)) + using (LogContext.PushProperty("IpAddress", log.IpAddress)) + { + // If details were successfully parsed, push them as a structured property. + if (detailsObject != null) + { + using (LogContext.PushProperty("Details", detailsObject, true)) // 'true' for destructuring + { + LogByLevel(log.LogLevel, log.Message); + } + } + else + { + // If details were null or failed to parse, log without the details property + LogByLevel(log.LogLevel, log.Message); + } + } + } + + // Return 200 OK indicating successful receipt of logs. + // As logging is typically a "fire and forget" operation from the client's perspective, + // we don't need to await individual log writes here. + // The logger provider handles the actual writing asynchronously in the background. + _logger.LogInfo("Successfully processed {LogCount} mobile log entries.", logs.Count); + return Ok("Logs received successfully."); + } + + /// + /// Logs a message at the specified log level using the injected ILogger. + /// + /// The string representation of the log level (e.g., "info", "warning"). + /// The log message. + private void LogByLevel(string? logLevel, string? message) + { + // Default message if null or empty + message = string.IsNullOrWhiteSpace(message) ? "No message provided." : message; + + // Use a switch expression or simple if/else for clarity based on log level + switch (logLevel?.ToLowerInvariant()) // Use ToLowerInvariant for case-insensitive comparison + { + case "info": + case "information": // Common alias for info + _logger.LogInfo(message); + break; + case "warn": + case "warning": + _logger.LogWarning(message); + break; + case "error": + _logger.LogError(message); + break; + case "critical": + _logger.LogCritical(message); + break; + default: + // Log unknown levels as information to ensure they are captured + _logger.LogInfo("[Unknown Level] {Message}", message); + break; + } + } + } +} diff --git a/Marco.Pms.Services/Service/ILoggingService.cs b/Marco.Pms.Services/Service/ILoggingService.cs index 39dbb00..bf97e35 100644 --- a/Marco.Pms.Services/Service/ILoggingService.cs +++ b/Marco.Pms.Services/Service/ILoggingService.cs @@ -1,12 +1,11 @@ -using Serilog.Context; - -namespace MarcoBMS.Services.Service +namespace MarcoBMS.Services.Service { public interface ILoggingService { void LogInfo(string? message, params object[]? args); void LogWarning(string? message, params object[]? args); void LogError(string? message, params object[]? args); + void LogCritical(string? message, params object[]? args); } } diff --git a/Marco.Pms.Services/Service/LoggingServices.cs b/Marco.Pms.Services/Service/LoggingServices.cs index 4328a2a..c6a7577 100644 --- a/Marco.Pms.Services/Service/LoggingServices.cs +++ b/Marco.Pms.Services/Service/LoggingServices.cs @@ -18,10 +18,11 @@ namespace MarcoBMS.Services.Service { _logger.LogError(message, args); } - else { + else + { _logger.LogError(message); } - } + } public void LogInfo(string? message, params object[]? args) { @@ -48,6 +49,19 @@ namespace MarcoBMS.Services.Service _logger.LogWarning(message); } } + + public void LogCritical(string? message, params object[]? args) + { + using (LogContext.PushProperty("LogLevel", "Critical")) + if (args != null) + { + _logger.LogCritical(message, args); + } + else + { + _logger.LogCritical(message); + } + } } } -- 2.43.0 From 373d80260fd13ff6e3277fb062eef426feadb1c7 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Tue, 24 Jun 2025 10:10:03 +0530 Subject: [PATCH 2/4] Removed demo objects for which is used for testing purpose --- Marco.Pms.Services/Controllers/LogController.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Marco.Pms.Services/Controllers/LogController.cs b/Marco.Pms.Services/Controllers/LogController.cs index 6047642..15a956b 100644 --- a/Marco.Pms.Services/Controllers/LogController.cs +++ b/Marco.Pms.Services/Controllers/LogController.cs @@ -26,12 +26,6 @@ namespace Marco.Pms.Services.Controllers /// An indicating the success of the operation. public IActionResult MobileLogging([FromBody] List logs) { - var demoDetails = new - { - name = "Pooja", - job = "Tester", - org = "MarcoAiot" - }; // Check if logs are provided to avoid processing empty requests if (logs == null || !logs.Any()) { @@ -46,7 +40,6 @@ namespace Marco.Pms.Services.Controllers // Attempt to deserialize the 'Details' string into a dynamic object // This allows structured logging of the details payload. - log.Details = JsonSerializer.Serialize(demoDetails); if (!string.IsNullOrWhiteSpace(log.Details)) { try -- 2.43.0 From 4f774315416157f8ead83439976586b132035e54 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Mon, 8 Sep 2025 10:32:32 +0530 Subject: [PATCH 3/4] Changed the folder name --- Marco.Pms.Model/MobileLogs/LogStructure.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 Marco.Pms.Model/MobileLogs/LogStructure.cs diff --git a/Marco.Pms.Model/MobileLogs/LogStructure.cs b/Marco.Pms.Model/MobileLogs/LogStructure.cs new file mode 100644 index 0000000..01fe9a8 --- /dev/null +++ b/Marco.Pms.Model/MobileLogs/LogStructure.cs @@ -0,0 +1,12 @@ +namespace Marco.Pms.Model.Logs +{ + public class LogStructure + { + public string LogLevel { get; set; } = "Info"; + public string Message { get; set; } = string.Empty; + public DateTime TimeStamp { get; set; } + public string? IpAddress { get; set; } + public string? UserAgent { get; set; } + public string? Details { get; set; } // json serialized string + } +} -- 2.43.0 From 06db1adc5237670282108f3eff0d30caaf7d21e6 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Mon, 8 Sep 2025 11:47:53 +0530 Subject: [PATCH 4/4] Added the debug logs in log controller --- Marco.Pms.Services/Controllers/LogController.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Marco.Pms.Services/Controllers/LogController.cs b/Marco.Pms.Services/Controllers/LogController.cs index 4efaf85..c206d0b 100644 --- a/Marco.Pms.Services/Controllers/LogController.cs +++ b/Marco.Pms.Services/Controllers/LogController.cs @@ -101,6 +101,9 @@ namespace Marco.Pms.Services.Controllers // Use a switch expression or simple if/else for clarity based on log level switch (logLevel?.ToLowerInvariant()) // Use ToLowerInvariant for case-insensitive comparison { + case "debug": + _logger.LogDebug(message); + break; case "info": case "information": // Common alias for info _logger.LogInformation(message); -- 2.43.0