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); + } + } } }