using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Nest; using System.Diagnostics; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text.Encodings.Web; using System.Text.Json; using JsonSerializer = System.Text.Json.JsonSerializer; namespace BMA.EHR.Recruit.Service.Core { public class RequestLoggingMiddleware { private readonly RequestDelegate _next; private readonly IConfiguration _configuration; private string Uri = ""; private string IndexFormat = ""; private string SystemName = ""; 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 = "recruiting"; } /// /// แกะ JWT token เพื่อดึง claims ต่างๆ /// private JwtSecurityToken? ParseToken(string token) { try { var tokenHandler = new JwtSecurityTokenHandler(); var jwtToken = tokenHandler.ReadJwtToken(token.Replace("Bearer ", "")); return jwtToken; } catch { return null; } } /// /// ดึงค่า claim จาก token โดยลองชื่อหลายแบบ /// private string? GetClaimValue(JwtSecurityToken? token, params string[] claimNames) { if (token == null) return null; foreach (var name in claimNames) { var claim = token.Claims.FirstOrDefault(c => c.Type == name); if (claim != null) return claim.Value; } return null; } /// /// ดึงค่า Guid claim จาก token /// private Guid? GetGuidClaim(JwtSecurityToken? token, params string[] claimNames) { var value = GetClaimValue(token, claimNames); if (Guid.TryParse(value, out var guid)) return guid; return null; } 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(); 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()) { context.Response.Body = memoryStream; var keycloakId = context.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? Guid.Empty.ToString("D"); var tokenHeader = context.Request.Headers["Authorization"].ToString(); // แกะ JWT token เพื่อดึง claims ต่างๆ var jwtToken = ParseToken(tokenHeader); // ดึงข้อมูลจาก claims โดยลองชื่อหลายแบบ (camelCase, snake_case, ฯลฯ) var prefix = GetClaimValue(jwtToken, "prefix", "Prefix", "PREFIX"); var firstName = GetClaimValue(jwtToken, "given_name", "firstname", "firstName", "FirstName", "FIRSTNAME"); var lastName = GetClaimValue(jwtToken, "family_name", "lastname", "lastName", "LastName", "LASTNAME"); var preferredUsername = GetClaimValue(jwtToken, "preferred_username", "preferred_username", "PreferredUsername"); var orgRootDnaId = GetGuidClaim(jwtToken, "orgRootDnaId", "org_root_dna_id", "OrgRootDnaId", "rootDnaId"); var orgChild1Dna = GetGuidClaim(jwtToken, "orgChild1DnaId", "org_child1_dna", "OrgChild1Dna", "child1DnaId"); var orgChild2Dna = GetGuidClaim(jwtToken, "orgChild2DnaId", "org_child2_dna", "OrgChild2Dna", "child2DnaId"); var orgChild3Dna = GetGuidClaim(jwtToken, "orgChild3DnaId", "org_child3_dna", "OrgChild3Dna", "child3DnaId"); var orgChild4Dna = GetGuidClaim(jwtToken, "orgChild4DnaId", "org_child4_dna", "OrgChild4Dna", "child4DnaId"); await _next(context); stopwatch.Stop(); var processTime = stopwatch.ElapsedMilliseconds; var endTime = DateTime.UtcNow; var logType = context.Response.StatusCode switch { >= 500 => "error", >= 400 => "warning", _ => "info" }; string? message = null; memoryStream.Seek(0, SeekOrigin.Begin); var responseBody = new StreamReader(memoryStream).ReadToEnd(); if (!string.IsNullOrEmpty(responseBody)) { var contentType = context.Response.ContentType ?? ""; if (contentType.Equals( "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", StringComparison.OrdinalIgnoreCase)) { responseBodyJson = $"Excel file (Length={memoryStream.Length} bytes)"; message = "success"; } else if (contentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase)) { try { 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(); } catch { responseBodyJson = responseBody; message = "success"; } } else { responseBodyJson = responseBody; message = "success"; } } var logData = new { logType = logType, ip = context.Connection.RemoteIpAddress?.ToString(), rootId = orgRootDnaId?.ToString("D"), 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 = $"{prefix ?? ""}{firstName ?? ""} {lastName ?? ""}", user = preferredUsername ?? "" //user = GetClaimValue(jwtToken, "citizen_id", "citizenId", "CitizenId") ?? "" }; memoryStream.Seek(0, SeekOrigin.Begin); await memoryStream.CopyToAsync(originalBodyStream); client.IndexDocument(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; } } }