From aa1b5fe8f1d966e65ff35052232bd3a850b30212 Mon Sep 17 00:00:00 2001 From: = <=> Date: Sun, 14 Dec 2025 19:33:33 -0500 Subject: [PATCH] Adding diagnostic logging for purchased tab --- OF DL/Helpers/APIHelper.cs | 57 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/OF DL/Helpers/APIHelper.cs b/OF DL/Helpers/APIHelper.cs index 84ce24e..e0a1903 100644 --- a/OF DL/Helpers/APIHelper.cs +++ b/OF DL/Helpers/APIHelper.cs @@ -33,6 +33,10 @@ public class APIHelper : IAPIHelper private static DynamicRules? cachedDynamicRules; private const int MaxAttempts = 30; private const int DelayBetweenAttempts = 3000; + private static readonly ILogger DiagnosticLog = new LoggerConfiguration() + .MinimumLevel.Debug() + .WriteTo.File("logs/purchasedtab-diagnostics.txt", rollingInterval: RollingInterval.Day, retainedFileCountLimit: 7) + .CreateLogger(); static APIHelper() { @@ -118,16 +122,20 @@ public class APIHelper : IAPIHelper } - private async Task BuildHeaderAndExecuteRequests(Dictionary getParams, string endpoint, HttpClient client) + private async Task BuildHeaderAndExecuteRequests(Dictionary getParams, string endpoint, HttpClient client, ILogger? diagnosticLogger = null) { Log.Debug("Calling BuildHeaderAndExecuteRequests"); HttpRequestMessage request = await BuildHttpRequestMessage(getParams, endpoint); + diagnosticLogger?.Information("Diag request | method={Method} url={Url} headers={Headers}", request.Method, request.RequestUri, FlattenHeaders(request.Headers)); + using var response = await client.SendAsync(request); + diagnosticLogger?.Information("Diag response | status={StatusCode} reason={ReasonPhrase} requestUrl={Url}", response.StatusCode, response.ReasonPhrase, request.RequestUri); response.EnsureSuccessStatusCode(); string body = await response.Content.ReadAsStringAsync(); Log.Debug(body); + diagnosticLogger?.Information("Diag response body (truncated) | length={Length} body={Body}", body?.Length ?? 0, TruncateString(body, 2000)); return body; } @@ -177,6 +185,20 @@ public class APIHelper : IAPIHelper return client; } + private static string FlattenHeaders(System.Net.Http.Headers.HttpRequestHeaders headers) + { + return string.Join("; ", headers.Select(h => $"{h.Key}={string.Join(",", h.Value)}")); + } + + private static string TruncateString(string? value, int maxLength = 1024) + { + if (string.IsNullOrEmpty(value)) + { + return string.Empty; + } + return value.Length <= maxLength ? value : $"{value[..maxLength]}...[truncated]"; + } + /// /// this one is used during initialization only @@ -248,11 +270,13 @@ public class APIHelper : IAPIHelper { Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); Log.Error("Exception caught: {0}\n\nStackTrace: {1}", ex.Message, ex.StackTrace); + DiagnosticLog.Error("PurchasedTab exception | ctx={Context} message={Message} stack={Stack}", diagContext, ex.Message, TruncateString(ex.StackTrace ?? string.Empty, 2000)); if (ex.InnerException != null) { Console.WriteLine("\nInner Exception:"); Console.WriteLine("Exception caught: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); Log.Error("Inner Exception: {0}\n\nStackTrace: {1}", ex.InnerException.Message, ex.InnerException.StackTrace); + DiagnosticLog.Error("PurchasedTab inner exception | ctx={Context} message={Message} stack={Stack}", diagContext, ex.InnerException.Message, TruncateString(ex.InnerException.StackTrace ?? string.Empty, 2000)); } } return null; @@ -2244,6 +2268,8 @@ public class APIHelper : IAPIHelper { Log.Debug($"Calling GetPurchasedTab - {endpoint}"); + string diagContext = $"purchasedtab-{DateTime.UtcNow:yyyyMMddTHHmmssfff}-{Guid.NewGuid():N}"; + try { Dictionary> userPurchases = new Dictionary>(); @@ -2258,8 +2284,18 @@ public class APIHelper : IAPIHelper { "skip_users", "all" } }; - var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config)); + string initialQueryParams = "?" + string.Join("&", getParams.Select(kvp => $"{kvp.Key}={kvp.Value}")); + var initialHeaders = GetDynamicHeaders("/api2/v2" + endpoint, initialQueryParams); + DiagnosticLog.Information("PurchasedTab start | ctx={Context} endpoint={Endpoint} folder={Folder} usersCount={UsersCount} limit={Limit} query={Query} headers={Headers}", diagContext, endpoint, folder, users?.Count ?? 0, post_limit, initialQueryParams, string.Join(", ", initialHeaders.Select(kvp => $"{kvp.Key}={kvp.Value}"))); + + var body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient(config), DiagnosticLog); + DiagnosticLog.Information("PurchasedTab initial response | ctx={Context} bodyLength={Length} bodyPreview={BodyPreview}", diagContext, body?.Length ?? 0, TruncateString(body, 1500)); purchased = JsonConvert.DeserializeObject(body, m_JsonSerializerSettings); + DiagnosticLog.Information("PurchasedTab parsed initial | ctx={Context} items={Items} hasMore={HasMore}", diagContext, purchased?.list?.Count ?? 0, purchased?.hasMore ?? false); + if (purchased == null || purchased.list == null) + { + DiagnosticLog.Warning("PurchasedTab parsed initial null list | ctx={Context} bodyPreview={BodyPreview}", diagContext, TruncateString(body, 1500)); + } if (purchased != null && purchased.hasMore) { getParams["offset"] = purchased.list.Count.ToString(); @@ -2276,11 +2312,14 @@ public class APIHelper : IAPIHelper { looprequest.Headers.Add(keyValuePair.Key, keyValuePair.Value); } + DiagnosticLog.Information("PurchasedTab page request | ctx={Context} url={Url} offset={Offset} limit={Limit} headers={Headers}", diagContext, looprequest.RequestUri, getParams.ContainsKey("offset") ? getParams["offset"] : "0", post_limit, FlattenHeaders(looprequest.Headers)); using (var loopresponse = await loopclient.SendAsync(looprequest)) { + DiagnosticLog.Information("PurchasedTab page response | ctx={Context} status={Status} reason={Reason} url={Url}", diagContext, loopresponse.StatusCode, loopresponse.ReasonPhrase, looprequest.RequestUri); loopresponse.EnsureSuccessStatusCode(); var loopbody = await loopresponse.Content.ReadAsStringAsync(); newPurchased = JsonConvert.DeserializeObject(loopbody, m_JsonSerializerSettings); + DiagnosticLog.Information("PurchasedTab page parsed | ctx={Context} offset={Offset} items={Items} hasMore={HasMore} bodyLength={Length} bodyPreview={BodyPreview}", diagContext, getParams.ContainsKey("offset") ? getParams["offset"] : "0", newPurchased?.list?.Count ?? 0, newPurchased?.hasMore ?? false, loopbody?.Length ?? 0, TruncateString(loopbody, 1500)); } purchased.list.AddRange(newPurchased.list); if (!newPurchased.hasMore) @@ -2318,6 +2357,15 @@ public class APIHelper : IAPIHelper { PurchasedTabCollection purchasedTabCollection = new PurchasedTabCollection(); JObject userObject = await GetUserInfoById($"/users/list?x[]={user.Key}"); + string resolvedUsername = $"Deleted User - {user.Key}"; + if (userObject == null || userObject[user.Key.ToString()] == null || userObject[user.Key.ToString()]["username"] == null) + { + DiagnosticLog.Warning("PurchasedTab user lookup missing username | ctx={Context} userId={UserId} rawUser={RawUser}", diagContext, user.Key, userObject != null ? TruncateString(userObject.ToString(), 500) : "null"); + } + else + { + resolvedUsername = userObject[user.Key.ToString()]["username"].ToString(); + } purchasedTabCollection.UserId = user.Key; purchasedTabCollection.Username = userObject is not null && !string.IsNullOrEmpty(userObject[user.Key.ToString()]["username"].ToString()) ? userObject[user.Key.ToString()]["username"].ToString() : $"Deleted User - {user.Key}"; string path = System.IO.Path.Combine(folder, purchasedTabCollection.Username); @@ -2325,6 +2373,11 @@ public class APIHelper : IAPIHelper { foreach (Purchased.List purchase in user.Value) { + DiagnosticLog.Information("PurchasedTab processing purchase | ctx={Context} userId={UserId} username={Username} purchaseId={PurchaseId} responseType={ResponseType} mediaNull={MediaNull} mediaCount={MediaCount} postedAt={PostedAt} createdAt={CreatedAt} fromUserId={FromUserId} authorId={AuthorId}", diagContext, user.Key, resolvedUsername, purchase.id, purchase.responseType, purchase.media == null, purchase.media?.Count ?? 0, purchase.postedAt, purchase.createdAt, purchase.fromUser?.id, purchase.author?.id); + if (purchase.media == null) + { + DiagnosticLog.Warning("PurchasedTab purchase has null media | ctx={Context} userId={UserId} username={Username} purchaseId={PurchaseId} responseType={ResponseType} rawPurchase={RawPurchase}", diagContext, user.Key, resolvedUsername, purchase.id, purchase.responseType, TruncateString(JsonConvert.SerializeObject(purchase), 1500)); + } switch (purchase.responseType) { case "post":