From cc08c46886cc5b07461541a4d938ef75d2fc0beb Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Sun, 17 Aug 2025 20:12:38 +0900 Subject: [PATCH 1/5] Improve logging and async handling in ResourceServer ResourceServer now defaults to using TraceLogger if no logger is provided and properly awaits FetchBlobConfig. FetchBlobConfig is refactored to return a Task and handle missing configuration more gracefully. TraceLogger now writes logs to a file named after its namespace and sets up a TextWriterTraceListener for persistent logging. --- .../WelsonJS.Launcher/ResourceServer.cs | 25 +++++++++++++------ .../WelsonJS.Launcher/TraceLogger.cs | 11 ++++++++ 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs index 8f9241b..8c55717 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs @@ -44,13 +44,13 @@ namespace WelsonJS.Launcher public ResourceServer(string prefix, string resourceName, ICompatibleLogger logger = null) { - _logger = logger; + _logger = logger ?? new TraceLogger(); _prefix = prefix; _listener = new HttpListener(); _resourceName = resourceName; // Fetch a blob config from Internet - FetchBlobConfig(); + FetchBlobConfig().ConfigureAwait(false); // Add resource tools _tools.Add(new ResourceTools.Completion(this, _httpClient)); @@ -167,7 +167,11 @@ namespace WelsonJS.Launcher using (var client = new HttpClient()) { HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url); - request.Headers.UserAgent.ParseAdd(context.Request.UserAgent); + var ua = context?.Request?.UserAgent; + if (!string.IsNullOrEmpty(ua)) + { + request.Headers.UserAgent.ParseAdd(ua); + } HttpResponseMessage response = await client.SendAsync(request); if (!response.IsSuccessStatusCode) @@ -430,24 +434,29 @@ namespace WelsonJS.Launcher } } - private async void FetchBlobConfig() + private async Task FetchBlobConfig() { try { string url = Program.GetAppConfig("BlobConfigUrl"); - var response = await _httpClient.GetStreamAsync(url); + if (string.IsNullOrWhiteSpace(url)) + { + _logger?.Warn("BlobConfigUrl is not configured."); + return; + } - var serializer = new XmlSerializer(typeof(BlobConfig)); + using (var response = await _httpClient.GetStreamAsync(url)) using (var reader = new StreamReader(response)) { - _blobConfig = (BlobConfig)serializer.Deserialize(reader); + var serializer = new XmlSerializer(typeof(BlobConfig)); + _blobConfig = (BlobConfig)serializer.Deserialize(reader); } _blobConfig?.Compile(); } catch (Exception ex) { - _logger?.Error($"Failed to fetch a blob config. Exception: {ex.Message}"); + _logger?.Error($"Failed to fetch a blob config. Exception: {ex}"); } } } diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/TraceLogger.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/TraceLogger.cs index 8b7e34f..b60d42a 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/TraceLogger.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/TraceLogger.cs @@ -12,6 +12,17 @@ namespace WelsonJS.Launcher { public class TraceLogger : ICompatibleLogger { + private static readonly string _logFileName; + + static TraceLogger() + { + _logFileName = typeof(TraceLogger).Namespace + ".log"; + + var textWriterTraceListener = new TextWriterTraceListener(_logFileName); + Trace.Listeners.Add(textWriterTraceListener); + Trace.AutoFlush = true; + } + public void Info(string message) => Trace.TraceInformation(message); public void Warn(string message) => Trace.TraceWarning(message); public void Error(string message) => Trace.TraceError(message); From f44f56ea2cd7fa8148fd84dd9cfa77744a250c11 Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Sun, 17 Aug 2025 20:43:33 +0900 Subject: [PATCH 2/5] Refactor HTTP client usage in ResourceServer Replaced local HttpClient instantiation with the class-level _httpClient for sending requests. This improves resource management and consistency in HTTP operations. --- .../WelsonJS.Launcher/ResourceServer.cs | 41 +++++++++---------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs index 8c55717..f77a842 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs @@ -164,30 +164,27 @@ namespace WelsonJS.Launcher try { - using (var client = new HttpClient()) + HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url); + var ua = context?.Request?.UserAgent; + if (!string.IsNullOrEmpty(ua)) { - HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url); - var ua = context?.Request?.UserAgent; - if (!string.IsNullOrEmpty(ua)) - { - request.Headers.UserAgent.ParseAdd(ua); - } - HttpResponseMessage response = await client.SendAsync(request); - - if (!response.IsSuccessStatusCode) - { - _logger?.Error($"Failed to serve blob. URL: {url}, Status: {response.StatusCode}"); - return false; - } - - data = await response.Content.ReadAsByteArrayAsync(); - mimeType = response.Content.Headers.ContentType?.MediaType ?? _defaultMimeType; - - ServeResource(context, data, mimeType); - _ = TrySaveCachedBlob(path, data, mimeType); - - return true; + request.Headers.UserAgent.ParseAdd(ua); } + HttpResponseMessage response = await _httpClient.SendAsync(request); + + if (!response.IsSuccessStatusCode) + { + _logger?.Error($"Failed to serve blob. URL: {url}, Status: {response.StatusCode}"); + return false; + } + + data = await response.Content.ReadAsByteArrayAsync(); + mimeType = response.Content.Headers.ContentType?.MediaType ?? _defaultMimeType; + + ServeResource(context, data, mimeType); + _ = TrySaveCachedBlob(path, data, mimeType); + + return true; } catch (Exception ex) { From 6896c272ef6031eb02400864b7a8a9821c5acdb8 Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Sun, 17 Aug 2025 20:58:08 +0900 Subject: [PATCH 3/5] Update WelsonJS.Toolkit/WelsonJS.Launcher/TraceLogger.cs Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- WelsonJS.Toolkit/WelsonJS.Launcher/TraceLogger.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/TraceLogger.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/TraceLogger.cs index b60d42a..5f86237 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/TraceLogger.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/TraceLogger.cs @@ -16,10 +16,17 @@ namespace WelsonJS.Launcher static TraceLogger() { - _logFileName = typeof(TraceLogger).Namespace + ".log"; - - var textWriterTraceListener = new TextWriterTraceListener(_logFileName); - Trace.Listeners.Add(textWriterTraceListener); + try + { + _logFileName = (typeof(TraceLogger).Namespace ?? "WelsonJS.Launcher") + ".log"; + Trace.Listeners.Add(new TextWriterTraceListener(_logFileName)); + } + catch (System.Exception ex) + { + // Fallback when the process cannot write to the working directory + Trace.Listeners.Add(new ConsoleTraceListener()); + Trace.TraceWarning($"TraceLogger: failed to initialize file listener '{_logFileName}'. Falling back to ConsoleTraceListener. Error: {ex.Message}"); + } Trace.AutoFlush = true; } From d0c031fe3296ccc203a80cc943561f5d68a0d403 Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Sun, 17 Aug 2025 21:22:17 +0900 Subject: [PATCH 4/5] Fix async context handling in blob config loading Added ConfigureAwait(false) to async HTTP call and moved Compile() invocation to immediately after deserialization. This improves async context handling and ensures blob config is compiled before assignment. --- WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs index f77a842..6237a24 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs @@ -442,14 +442,14 @@ namespace WelsonJS.Launcher return; } - using (var response = await _httpClient.GetStreamAsync(url)) + using (var response = await _httpClient.GetStreamAsync(url).ConfigureAwait(false)) using (var reader = new StreamReader(response)) { var serializer = new XmlSerializer(typeof(BlobConfig)); - _blobConfig = (BlobConfig)serializer.Deserialize(reader); + var cfg = (BlobConfig)serializer.Deserialize(reader); + cfg?.Compile(); + _blobConfig = cfg; } - - _blobConfig?.Compile(); } catch (Exception ex) { From a022383d630bcc470cf1aed086d0f3c4a2a4ecd1 Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Sun, 17 Aug 2025 22:34:31 +0900 Subject: [PATCH 5/5] Improve error handling for FetchBlobConfig Replaces fire-and-forget async call to FetchBlobConfig with a safe pattern that logs errors if the task fails. This ensures exceptions are not silently ignored and improves reliability. --- WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs index 6237a24..b9907ca 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs @@ -49,8 +49,12 @@ namespace WelsonJS.Launcher _listener = new HttpListener(); _resourceName = resourceName; - // Fetch a blob config from Internet - FetchBlobConfig().ConfigureAwait(false); + // Fetch a blob config from Internet (safe fire-and-forget with logging) + _ = FetchBlobConfig().ContinueWith(t => + { + if (t.IsFaulted) + _logger?.Error($"FetchBlobConfig failed: {t.Exception}"); + }, TaskScheduler.Default); // Add resource tools _tools.Add(new ResourceTools.Completion(this, _httpClient));