Improve harvester log grouping and error reporting#831
Conversation
| def format_timestamp(value): | ||
| """Format timestamps for display.""" | ||
| if value is None or value == "": | ||
| if value in (None, ""): |
There was a problem hiding this comment.
| if value in (None, ""): | |
| if not value: |
|
|
||
| def _compact(value): | ||
| """Collapse whitespace while keeping the original text readable.""" | ||
| text = str(value or "").replace("\\n", " ").replace("\n", " ") |
There was a problem hiding this comment.
where are the whitespaces originally added?
| return " ".join(text.split()).strip() | ||
|
|
||
|
|
||
| def _remove_prefix(text, prefix): |
There was a problem hiding this comment.
which prefixes are removed?
| return None | ||
|
|
||
|
|
||
| def _strip_leading_bracket_prefixes(message): |
There was a problem hiding this comment.
there is a lot of things happening here which I need some narration from your side to understand. Please book (on calendar) some time for us to go through the code together
1e2134c to
47666b0
Compare
| return match.group("id") if match else None | ||
|
|
||
|
|
||
| def _unwrap_skipped_entry_error(reason): |
| ) | ||
|
|
||
|
|
||
| def _safe_strip_wrapper_prefixes(reason): |
There was a problem hiding this comment.
check if this can be fixed while logging in the first place - are the classes added in the harvester task? if yes we can reorganise there instead of parsing. If these classes are added in the jobs - let's discuss
| return "Unknown error" | ||
|
|
||
| if _PLACEHOLDER_ONLY_TITLE.match(stripped): | ||
| return "Record validation failed." if stripped == "<payload>" else "Log message" |
There was a problem hiding this comment.
please check how to fix .. errors and display the error correctly instead of stripping the payload
|
|
||
|
|
||
| def _known_error_stem(reason): | ||
| """Normalize legacy and current variants of recurring harvester errors.""" |
There was a problem hiding this comment.
is this normalisation needed?
f07326b to
40ede92
Compare
6851e19 to
52262d7
Compare
| return None | ||
|
|
||
|
|
||
| def _peel_string_list(text): |
There was a problem hiding this comment.
what is the case for which we are returning a list in the log? maybe the log is incorrect?
There was a problem hiding this comment.
Good question. The screenshot shows where this comes from, in invenio-vocabularies, when a record fails it logs result.errors directly, and that's a list. So what ends up in the log looks like ['error message here'] instead of plain text. _peel_string_list just strips that wrapper off so the report can group errors properly. For example, without it we'd group on something like skipped entry with errors: ['more than 1 doi was found.'], brackets, quotes and all. After peeling we group on just more than 1 doi was found., so records that failed for the same reason actually end up in the same group.
It would be nicer if invenio-vocabularies wrote it as plain text in the first place. I handled it in the report for now since past harvester runs already have logs saved this way, but if you'd rather fix it at the source I can do that instead.
| """INSPIRE writer.""" | ||
|
|
||
| @staticmethod | ||
| def _format_validation_error(error): |
There was a problem hiding this comment.
these methods would fit better in the logger.py, as logging is not the responsibility of this class. What do you think?
There was a problem hiding this comment.
You're right — moved them to logger.py.
| @@ -16,7 +16,7 @@ | |||
| from cds_rdm.harvester_runs.logs import ( | |||
There was a problem hiding this comment.
feedback not connected with this file but with the directory:
could you move harvester_download and harvester_runs into inspire_harvester/reports/download and inspire_harvester/reports/runs - to package the whole functionality of the harvester together
| if search_after: | ||
| params["search_after"] = search_after | ||
|
|
||
| result = current_jobs_logs_service.search(system_identity, params=params) |
There was a problem hiding this comment.
.search function will return only 10k results max, even with the pagination
There was a problem hiding this comment.
| return " ".join(text.split()).strip() | ||
|
|
||
|
|
||
| def flatten_error_payload(value, prefix=""): |
There was a problem hiding this comment.
I think there is a function which does something very similar: search for validation_error_to_list_errors/_iter_errors_dict in invenio_records_resources.errors
There was a problem hiding this comment.
Yep, switched to the invenio helper and dropped the duplicate.
e3a6f16 to
3548734
Compare
Move harvester run/download reports under inspire_harvester/reports, stabilise writer and transformer error messages for grouping, and add lean grouped error reporting in the admin harvester run view.
5f504a8 to
167c385
Compare

Closes #813
This PR improves INSPIRE harvester run error reporting by turning errors and warning into clear grouped errors and warnings, making log output much more useful for curators, and ensuring the report highlights the real failure reasons instead of vague or repetitive raw lines. It adds structured grouping for repeated failures and warnings, improves the plain-text download output to match that grouped view, and updates harvester writer messages so record-level failures explain what went wrong in a more actionable way, including which records were affected and why they failed.
Screen.Recording.2026-06-26.at.12.00.16.1.mp4
This is what a downloaded file of harvester logs looks like now:
harvester_logs_4a7fc391-5b9a-45b3-873f-bd2d2a297aaa_20260626_120105.log