using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Nest; using Newtonsoft.Json; using System.Diagnostics; using System.Net.Http.Headers; using System.Security.Claims; using System.Text.Encodings.Web; using System.Text.Json; using JsonSerializer = System.Text.Json.JsonSerializer; namespace BMA.EHR.Domain.Middlewares { public class RequestLoggingMiddleware { private readonly RequestDelegate _next; private readonly IConfiguration _configuration; private string Uri = ""; private string IndexFormat = ""; private string SystemName = ""; private string APIKey = ""; public RequestLoggingMiddleware(RequestDelegate next, IConfiguration configuration) { _next = next; _configuration = configuration; Uri = _configuration["ElasticConfiguration:Uri"] ?? "http://192.168.1.40:9200"; IndexFormat = _configuration["ElasticConfiguration:IndexFormat"] ?? "bma-ehr-log-index"; SystemName = _configuration["ElasticConfiguration:SystemName"] ?? "Unknown"; } protected async Task GetExternalAPIAsync(string apiPath, string accessToken, string apiKey) { try { using (var client = new HttpClient()) { client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.Replace("Bearer ", "")); client.DefaultRequestHeaders.Add("api_key", apiKey); var _res = await client.GetAsync(apiPath); if (_res.IsSuccessStatusCode) { var _result = await _res.Content.ReadAsStringAsync(); return _result; } return string.Empty; } } catch { throw; } } public async Task GetProfileByKeycloakIdAsync(Guid keycloakId, string? accessToken) { try { var apiPath = $"{_configuration["API"]}/org/dotnet/keycloak/{keycloakId}"; var apiKey = _configuration["API_KEY"]; var apiResult = await GetExternalAPIAsync(apiPath, accessToken ?? "", apiKey); if (apiResult != null) { var raw = JsonConvert.DeserializeObject(apiResult); if (raw != null) return raw.Result; } return null; } catch { throw; } } public async Task Invoke(HttpContext context) { var settings = new ConnectionSettings(new Uri(Uri)) .DefaultIndex(IndexFormat); var client = new ElasticClient(settings); var startTime = DateTime.UtcNow; var stopwatch = Stopwatch.StartNew(); string? responseBodyJson = null; string? requestBodyJson = null; string requestBody = await ReadRequestBodyAsync(context); if (requestBody != "") { if (context.Request.HasFormContentType) { var form = await context.Request.ReadFormAsync(); // อ่าน form-data var formData = new Dictionary(); foreach (var field in form) { formData[field.Key] = field.Value.ToString(); } // อ่านไฟล์ที่ถูกส่งมา (ถ้ามี) if (form.Files.Count > 0) { var fileDataList = new List(); foreach (var file in form.Files) { fileDataList.Add(new { FileName = file.FileName, ContentType = file.ContentType, Size = file.Length }); } formData["Files"] = fileDataList; } requestBodyJson = JsonSerializer.Serialize(formData, new JsonSerializerOptions { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, WriteIndented = true, Converters = { new DateTimeFixConverter() } }); } else { requestBodyJson = JsonSerializer.Serialize(JsonSerializer.Deserialize(requestBody), new JsonSerializerOptions { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, WriteIndented = true, Converters = { new DateTimeFixConverter() } }); } } var originalBodyStream = context.Response.Body; using (var memoryStream = new MemoryStream()) { // เปลี่ยน stream ของ Response เพื่อให้สามารถอ่านได้ context.Response.Body = memoryStream; var keycloakId = context.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? Guid.Empty.ToString("D"); var token = context.Request.Headers["Authorization"]; var pf = await GetProfileByKeycloakIdAsync(Guid.Parse(keycloakId), token); await _next(context); // ดำเนินการต่อไปยัง Middleware อื่น ๆ stopwatch.Stop(); var contentType = context.Response.ContentType; var isFileResponse = contentType != null && ( contentType.StartsWith("application/") || contentType.StartsWith("image/") || contentType.StartsWith("audio/") || context.Response.Headers.ContainsKey("Content-Disposition") ); var processTime = stopwatch.ElapsedMilliseconds; var endTime = DateTime.UtcNow; var logType = context.Response.StatusCode switch { >= 500 => "error", >= 400 => "warning", _ => "info" }; string? message = null; // อ่านข้อมูลจาก Response หลังจากที่ได้ถูกส่งออกไป memoryStream.Seek(0, SeekOrigin.Begin); var responseBody = new StreamReader(memoryStream).ReadToEnd(); if (responseBody != "") { if (isFileResponse) { responseBodyJson = ""; message = "success"; } else { responseBodyJson = JsonSerializer.Serialize(JsonSerializer.Deserialize(responseBody), new JsonSerializerOptions { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, WriteIndented = true, Converters = { new DateTimeFixConverter() } }); var json = JsonSerializer.Deserialize(responseBody); if (json.ValueKind == JsonValueKind.Array) { message = "success"; } else { if (json.TryGetProperty("message", out var messageElement)) { message = messageElement.GetString(); } } } } var logData = new { logType = logType, ip = context.Connection.RemoteIpAddress?.ToString(), rootId = pf == null ? null : pf.RootId, systemName = SystemName, startTimeStamp = startTime.ToString("o"), endTimeStamp = endTime.ToString("o"), processTime = processTime, host = context.Request.Host.Value, method = context.Request.Method, endpoint = context.Request.Path + context.Request.QueryString, responseCode = context.Response.StatusCode == 304 ? "200" : context.Response.StatusCode.ToString(), responseDescription = message, input = requestBodyJson, output = responseBodyJson, userId = keycloakId, userName = $"{pf?.Prefix ?? ""}{pf?.FirstName ?? ""} {pf?.LastName ?? ""}", user = pf?.CitizenId ?? "" }; // เขียนข้อมูลกลับไปยัง original Response body memoryStream.Seek(0, SeekOrigin.Begin); await memoryStream.CopyToAsync(originalBodyStream); client.IndexDocument(logData); } //Log.Information("API Request Log: {@LogData}", logData); } private async Task ReadRequestBodyAsync(HttpContext context) { context.Request.EnableBuffering(); using var reader = new StreamReader(context.Request.Body, leaveOpen: true); var body = await reader.ReadToEndAsync(); context.Request.Body.Position = 0; return body; } } // public class GetProfileByKeycloakIdLocal // { // public Guid Id { get; set; } // // public string? Prefix { get; set; } // public string? FirstName { get; set; } // public string? LastName { get; set; } // public string? CitizenId { get; set; } // // public string? Root { get; set; } // public string? Child1 { get; set; } // public string? Child2 { get; set; } // public string? Child3 { get; set; } // public string? Child4 { get; set; } // public Guid? RootId { get; set; } // public Guid? Child1Id { get; set; } // public Guid? Child2Id { get; set; } // public Guid? Child3Id { get; set; } // public Guid? Child4Id { get; set; } // public Guid? RootDnaId { get; set; } // public Guid? Child1DnaId { get; set; } // public Guid? Child2DnaId { get; set; } // public Guid? Child3DnaId { get; set; } // public Guid? Child4DnaId { get; set; } // public double? Amount { get; set; } // public double? PositionSalaryAmount { get; set; } // public string? Commander { get; set; } // // public Guid? CommanderId { get; set; } // // public Guid? CommanderKeycloak { get; set; } // // } // // public class GetProfileByKeycloakIdResultLocal // { // public string Message { get; set; } = string.Empty; // // public int Status { get; set; } = -1; // // public GetProfileByKeycloakIdLocal? Result { get; set; } // } }