Skip to content

Commit 2017c9f

Browse files
Merge pull request #634 from frankieramirez/frankieramirez/wayfinder-issue-611-4
fix: fold folder scan diagnostics into debug logging
2 parents b1b75fa + 1e04cba commit 2017c9f

10 files changed

Lines changed: 143 additions & 78 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"comicarr": patch
3+
---
4+
5+
Debug logging now includes useful folder-scan diagnostics without a second hidden switch. Comicarr removes the legacy `folder_scan_log_verbose` setting during the configuration upgrade; set the single Log level to `2 · Debug` when diagnosing scan matching. Candidate-heavy scans now summarize their work per input file instead of flooding the log with one line for every comparison.

CONTEXT.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ The MangaDex UUID a manga Series polls for new chapters. MangaDex Series carry i
2424

2525
Comicarr's single verbosity dial, named by the severity it admits: `0` warning, `1` info, `2` debug. Level `0` means warnings and errors, not silence, which is why it is never called "quiet" — `--quiet` and `--verbose` are flag spellings, not level names.
2626

27+
Subsystem diagnostics, including folder-scan diagnostics, are Debug entries rather than independent verbosity controls. High-volume operations summarize their diagnostics instead of introducing another dial.
28+
2729
## Support bundle
2830

2931
A downloadable archive of allowlisted diagnostic data, engineered for public issue attachment after operator review. If its contents appear sensitive, the operator shares it privately with maintainers instead; CarePackage is the legacy implementation name, not the user-facing term.

comicarr/app/config/registry.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ def as_definition(self) -> tuple[type, str, Any]:
114114
# ---------------------------------------------------------------------------
115115

116116
_KEYS: tuple[ConfigKey, ...] = (
117-
ConfigKey("CONFIG_VERSION", int, "General", 17),
117+
ConfigKey("CONFIG_VERSION", int, "General", 18),
118118
ConfigKey("MINIMAL_INI", bool, "General", False),
119119
ConfigKey("CACHE_DIR", str, "General", None, readable=True),
120120
ConfigKey("DYNAMIC_UPDATE", int, "General", 0),
@@ -136,7 +136,6 @@ def as_definition(self) -> tuple[type, str, Any]:
136136
ConfigKey("UPDATE_ENDED", bool, "General", False),
137137
ConfigKey("NEWCOM_DIR", str, "Update", None),
138138
ConfigKey("FFTONEWCOM_DIR", bool, "Update", False),
139-
ConfigKey("FOLDER_SCAN_LOG_VERBOSE", bool, "General", False),
140139
ConfigKey("INTERFACE", str, "General", "carbon"),
141140
ConfigKey("CORRECT_METADATA", bool, "General", False),
142141
ConfigKey("MOVE_FILES", bool, "General", False),

comicarr/config.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ def config_vals(self, update=False):
254254
count = 0
255255

256256
# this is the current version at this particular point in time.
257-
self.newconfig = 17
257+
self.newconfig = 18
258258

259259
OLDCONFIG_VERSION = 0
260260
if count == 0:
@@ -504,6 +504,14 @@ def read(self, startup=False):
504504
"[CONFIG] Removed host_return: the SABnzbd handoff now uploads the "
505505
"nzb directly and no download client is given a Comicarr address."
506506
)
507+
if self.CONFIG_VERSION < 18:
508+
# Folder-scan diagnostics now follow the single LOG_LEVEL dial;
509+
# remove the retired, hidden verbosity switch from old configs.
510+
if config.has_option("General", "folder_scan_log_verbose"):
511+
config.remove_option("General", "folder_scan_log_verbose")
512+
logger.info(
513+
"[CONFIG] Removed folder_scan_log_verbose: folder-scan diagnostics now follow LOG_LEVEL=debug."
514+
)
507515
self.OLDCONFIG_VERSION = str(self.CONFIG_VERSION)
508516
self.CONFIG_VERSION = self.newconfig
509517
config.set("General", "CONFIG_VERSION", str(self.newconfig))

comicarr/filechecker.py

Lines changed: 11 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -310,16 +310,13 @@ def parseit(self, path, filename, subpath=None):
310310
# if it's a story-arc, make sure to remove any leading reading order #'s
311311
if self.sarc and comicarr.CONFIG.READ2FILENAME:
312312
removest = modfilename.find("-") # the - gets removed above so we test for the first blank space...
313-
if comicarr.CONFIG.FOLDER_SCAN_LOG_VERBOSE:
314-
logger.fdebug(
315-
"[SARC] Checking filename for Reading Order sequence - Reading Sequence Order found #: %s"
316-
% modfilename[:removest]
317-
)
313+
logger.fdebug(
314+
"[SARC] Checking filename for Reading Order sequence - candidate: %s" % modfilename[:removest]
315+
)
318316
if modfilename[:removest].isdigit() and removest <= 3:
319317
reading_order = {"reading_sequence": str(modfilename[:removest]), "filename": filename[removest + 1 :]}
320318
modfilename = modfilename[removest + 1 :]
321-
if comicarr.CONFIG.FOLDER_SCAN_LOG_VERBOSE:
322-
logger.fdebug("[SARC] Removed Reading Order sequence from subname. Now set to : %s" % modfilename)
319+
logger.fdebug("[SARC] Validated and removed Reading Order sequence. Filename is now: %s" % modfilename)
323320

324321
# make sure all the brackets are properly spaced apart
325322
if modfilename.find(r"\s") == -1:
@@ -1846,11 +1843,7 @@ def matchIT(self, series_info):
18461843
== re.sub(r"[\|\s]", "", nspace_seriesname.lower()).strip()
18471844
]
18481845
if len(loopchk) > 0 and loopchk[0] != "":
1849-
if comicarr.CONFIG.FOLDER_SCAN_LOG_VERBOSE:
1850-
logger.fdebug("[FILECHECKER] This should be an alternate: %s" % loopchk)
18511846
if any(["annual" in series_name.lower(), "special" in series_name.lower()]):
1852-
if comicarr.CONFIG.FOLDER_SCAN_LOG_VERBOSE:
1853-
logger.fdebug("[FILECHECKER] Annual/Special detected - proceeding")
18541847
enable_annual = True
18551848

18561849
else:
@@ -1866,64 +1859,32 @@ def matchIT(self, series_info):
18661859
loopchk.append(nspace_watchcomic)
18671860
if any(["annual" in nspace_seriesname.lower(), "special" in nspace_seriesname.lower()]):
18681861
if "biannual" in nspace_seriesname.lower():
1869-
if comicarr.CONFIG.FOLDER_SCAN_LOG_VERBOSE:
1870-
logger.fdebug("[FILECHECKER] BiAnnual detected - wouldn't Deadpool be proud?")
18711862
nspace_seriesname = re.sub("biannual", "", nspace_seriesname).strip()
18721863
enable_annual = True
18731864
elif "annual" in nspace_seriesname.lower():
1874-
if comicarr.CONFIG.FOLDER_SCAN_LOG_VERBOSE:
1875-
logger.fdebug("[FILECHECKER] Annual detected - proceeding cautiously.")
18761865
off_year_check = re.findall(r"(\d{4})(?=[\s]|annual\b|$)", self.watchcomic, flags=re.I)
18771866
if off_year_check:
18781867
n_name = "%s%s" % (off_year_check[0], "annual")
18791868
nspace_seriesname = re.sub(n_name, "", nspace_seriesname.lower()).strip()
18801869
nspace_seriesname = re.sub("annual", "", nspace_seriesname.lower()).strip()
18811870
enable_annual = False
18821871
elif "special" in nspace_seriesname.lower():
1883-
if comicarr.CONFIG.FOLDER_SCAN_LOG_VERBOSE:
1884-
logger.fdebug("[FILECHECKER] Special detected - proceeding cautiously.")
18851872
nspace_seriesname = re.sub("special", "", nspace_seriesname).strip()
18861873
enable_annual = False
18871874

1888-
if comicarr.CONFIG.FOLDER_SCAN_LOG_VERBOSE:
1889-
logger.fdebug(
1890-
"[FILECHECKER] Complete matching list of names to this file [%s] : %s" % (len(loopchk), loopchk)
1891-
)
1892-
1893-
for loopit in loopchk:
1894-
# now that we have the list of all possible matches for the watchcomic + alternate search names, we go through the list until we find a match.
1895-
modseries_name = loopit
1896-
if comicarr.CONFIG.FOLDER_SCAN_LOG_VERBOSE:
1897-
logger.fdebug("[FILECHECKER] AS_Tuple : %s" % self.AS_Tuple)
1898-
for ATS in self.AS_Tuple:
1899-
if comicarr.CONFIG.FOLDER_SCAN_LOG_VERBOSE:
1900-
logger.fdebug(
1901-
"[FILECHECKER] %s comparing to %s" % (ATS["AS_Alternate"], nspace_seriesname)
1902-
)
1903-
if (
1904-
re.sub(r"\|", "", ATS["AS_Alternate"].lower()).strip()
1905-
== re.sub(r"\|", "", nspace_seriesname.lower()).strip()
1906-
):
1907-
if comicarr.CONFIG.FOLDER_SCAN_LOG_VERBOSE:
1908-
logger.fdebug("[FILECHECKER] Associating ComiciD : %s" % ATS["ComicID"])
1909-
annual_comicid = str(ATS["ComicID"])
1910-
modseries_name = ATS["AS_Alternate"]
1911-
break
1912-
1913-
logger.fdebug("[FILECHECKER] %s - watchlist match on : %s" % (modseries_name, filename))
1875+
for ATS in self.AS_Tuple:
1876+
if (
1877+
re.sub(r"\|", "", ATS["AS_Alternate"].lower()).strip()
1878+
== re.sub(r"\|", "", nspace_seriesname.lower()).strip()
1879+
):
1880+
annual_comicid = str(ATS["ComicID"])
1881+
break
19141882

19151883
if enable_annual:
19161884
if annual_comicid is not None:
1917-
if comicarr.CONFIG.FOLDER_SCAN_LOG_VERBOSE:
1918-
logger.fdebug("enable annual is on")
1919-
logger.fdebug("annual comicid is %s" % annual_comicid)
19201885
if "biannual" in nspace_watchcomic.lower():
1921-
if comicarr.CONFIG.FOLDER_SCAN_LOG_VERBOSE:
1922-
logger.fdebug("bi annual detected")
19231886
justthedigits = "BiAnnual %s" % justthedigits
19241887
elif "annual" in nspace_watchcomic.lower():
1925-
if comicarr.CONFIG.FOLDER_SCAN_LOG_VERBOSE:
1926-
logger.fdebug("annual detected")
19271888
justthedigits = "Annual %s" % justthedigits
19281889
elif "special" in nspace_watchcomic.lower():
19291890
justthedigits = "Special %s" % justthedigits
@@ -2152,16 +2113,10 @@ def altcheck(self):
21522113
# if it's !! present, it's the comicid associated with the series as an added annual.
21532114
# extract the !!, store it and then remove it so things will continue.
21542115
as_start = AS_Alternate.find("!!")
2155-
if comicarr.CONFIG.FOLDER_SCAN_LOG_VERBOSE:
2156-
logger.fdebug("as_start: %s --- %s" % (as_start, AS_Alternate[as_start:]))
21572116
as_end = AS_Alternate.find("##", as_start)
21582117
if as_end == -1:
21592118
as_end = len(AS_Alternate)
2160-
if comicarr.CONFIG.FOLDER_SCAN_LOG_VERBOSE:
2161-
logger.fdebug("as_start: %s --- %s" % (as_end, AS_Alternate[as_start:as_end]))
21622119
AS_ComicID = AS_Alternate[as_start + 2 : as_end]
2163-
if comicarr.CONFIG.FOLDER_SCAN_LOG_VERBOSE:
2164-
logger.fdebug("[FILECHECKER] Extracted comicid for given annual : %s" % AS_ComicID)
21652120
AS_Alternate = re.sub("!!" + str(AS_ComicID), "", AS_Alternate)
21662121
AS_tupled = True
21672122
as_dyninfo = self.dynamic_replace(AS_Alternate)
@@ -2318,10 +2273,6 @@ def normalize(s):
23182273
ratio = difflib.SequenceMatcher(None, parsed_name, comic_name).ratio()
23192274
name_score = int(ratio * 40)
23202275
score += name_score
2321-
if comicarr.CONFIG.FOLDER_SCAN_LOG_VERBOSE:
2322-
logger.fdebug(
2323-
'[CONFIDENCE] Name match: "%s" vs "%s" = %.2f (%d pts)' % (parsed_name, comic_name, ratio, name_score)
2324-
)
23252276

23262277
# 2. Year Match (15 pts)
23272278
parsed_year = parsed_info.get("issue_year") or parsed_info.get("ComicYear")

comicarr/postprocessor.py

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,34 @@
5757
_POSTPROCESS_INPUT_STAGE = PostProcessInputStage()
5858

5959

60+
def format_scan_summary(filename, candidate_count, selected_items, annual_count, story_arc):
61+
"""Format the bounded Debug summary emitted once for each scanned file."""
62+
selected = (
63+
", ".join("%s/%s" % (item.get("ComicID", "?"), item.get("ComicName", "?")) for item in selected_items) or "none"
64+
)
65+
return "[SCAN SUMMARY] %s: candidates=%s, selected=%s, annual_or_special=%s, story_arc=%s" % (
66+
filename,
67+
candidate_count,
68+
selected,
69+
annual_count,
70+
story_arc,
71+
)
72+
73+
74+
def log_scan_summary(module, filename, candidate_count, selected_items, annual_count, story_arc):
75+
logger.fdebug(
76+
"%s%s" % (module, format_scan_summary(filename, candidate_count, selected_items, annual_count, story_arc))
77+
)
78+
79+
80+
def summarize_scan_matches(normal_items, arc_items):
81+
"""Build summary fields from the matches selected for one input file."""
82+
selected_items = normal_items + arc_items
83+
annual_count = sum(1 for item in selected_items if item.get("AnnualType"))
84+
story_arc = bool(arc_items or any(item.get("IssueArcID") for item in normal_items))
85+
return selected_items, annual_count, story_arc
86+
87+
6088
class PostProcessor(object):
6189
"""
6290
A class which will process a media file according to the post processing settings in the config.
@@ -666,7 +694,6 @@ def Process(self):
666694
self.comicid = cid["ComicID"]
667695
else:
668696
if "_" in self.issueid:
669-
logger.fdebug("Story Arc post-processing request detected.")
670697
self.issuearcid = self.issueid
671698
self.issueid = None
672699
logger.fdebug(
@@ -713,6 +740,8 @@ def Process(self):
713740
oneoff_issuelist = []
714741
manual_list = []
715742
for fl in filelist["comiclist"]:
743+
manual_list_start = len(manual_list)
744+
manual_arclist_start = len(manual_arclist)
716745
if (
717746
all([fl["series_name"] is not None, fl["series_name"] != ""])
718747
and comicarr.CONFIG.IGNORE_COVERS is True
@@ -1132,7 +1161,6 @@ def Process(self):
11321161
# continue
11331162
watchvals = []
11341163
for wv in comicseries:
1135-
logger.info("Now checking: %s [%s]" % (wv["ComicName"], wv["ComicID"]))
11361164
# do some extra checks in here to ignore these types:
11371165
# check for valid issue number - if not, don't even bother checking it
11381166
try:
@@ -1201,11 +1229,6 @@ def Process(self):
12011229
wv_latestissue = wv["LatestIssue"]
12021230
wv_intlatestissue = wv["intLatestIssue"]
12031231
wv_forcecontinuing = bool(wv["ForceContinuing"])
1204-
if comicarr.CONFIG.FOLDER_SCAN_LOG_VERBOSE:
1205-
logger.fdebug(
1206-
"Queuing to Check: %s [%s] -- %s" % (wv["ComicName"], wv["ComicYear"], wv["ComicID"])
1207-
)
1208-
12091232
# force it to use the Publication Date of the latest issue instead of the Latest Date (which could be anything)
12101233
ld_check = db.select_one(
12111234
select(issues.c.ReleaseDate, issues.c.Issue_Number, issues.c.Int_IssueNumber)
@@ -2898,6 +2921,19 @@ def Process(self):
28982921
self.matched = True
28992922
break
29002923

2924+
selected_items, annual_count, story_arc_matched = summarize_scan_matches(
2925+
manual_list[manual_list_start:],
2926+
manual_arclist[manual_arclist_start:],
2927+
)
2928+
log_scan_summary(
2929+
module,
2930+
fl["comicfilename"],
2931+
len(watchvals),
2932+
selected_items,
2933+
annual_count,
2934+
story_arc_matched,
2935+
)
2936+
29012937
if filelist["comiccount"] > 0:
29022938
logger.fdebug(
29032939
"%s There are %s files found that match on your watchlist, %s files are considered one-off's, and %s files do not match anything"

docs/architecture/logging-levels.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,14 @@ choose by what they are actually asking.
184184

185185
## Two rules that fall out of it
186186

187+
**Subsystem diagnostics are entries, not dials.** Folder scanning used to put
188+
some of its `DEBUG` entries behind `FOLDER_SCAN_LOG_VERBOSE`, a hidden legacy
189+
`config.ini` key. That made level `2` an incomplete promise: an operator could
190+
ask for everything and still miss the matching details they needed. Folder-scan
191+
diagnostics now follow the log level like every other entry. High-cardinality
192+
matching work is summarized per input file instead of requiring a second switch
193+
to keep candidate-by-candidate chatter manageable.
194+
187195
**Level 0 is "warnings and errors", not silence.** An operator who turns the
188196
dial down is asking for less noise, not for failures to be hidden. This is why
189197
the setup-token announcement in `app/system/service.py` echoes to stdout at

frontend/tests/e2e/support-bundle.smoke.spec.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ test("Settings → About creates a Support bundle ZIP", async ({
3737
);
3838

3939
await page.goto("/settings?section=about");
40-
await expect(page.getByText("Support bundle")).toBeVisible();
40+
await expect(page.getByText("Support bundle", { exact: true })).toBeVisible();
4141
await expect(page.getByText("1. Create")).toBeVisible();
4242
await expect(page.getByText("2. Inspect")).toBeVisible();
4343
await expect(page.getByText("3. Share")).toBeVisible();
@@ -87,7 +87,12 @@ test("Settings → About creates a Support bundle ZIP", async ({
8787
}
8888

8989
// Unauthenticated request cannot invoke the endpoint.
90-
const bare = await page.context().browser()!.newContext();
90+
const bare = await page
91+
.context()
92+
.browser()!
93+
.newContext({
94+
storageState: { cookies: [], origins: [] },
95+
});
9196
const bareResp = await bare.request.post("/api/system/support-bundle", {
9297
headers: { "X-Requested-With": "ComicarrFrontend" },
9398
});

0 commit comments

Comments
 (0)