diff --git a/frappe/core/doctype/error_log/error_log.json b/frappe/core/doctype/error_log/error_log.json index 4218689cc8..3a320d9e63 100644 --- a/frappe/core/doctype/error_log/error_log.json +++ b/frappe/core/doctype/error_log/error_log.json @@ -12,7 +12,8 @@ "section_break_5", "method", "error", - "trace_id" + "trace_id", + "metadata" ], "fields": [ { @@ -66,13 +67,19 @@ "fieldtype": "Data", "label": "Trace ID", "read_only": 1 + }, + { + "fieldname": "metadata", + "fieldtype": "Code", + "label": "Metadata", + "read_only": 1 } ], "icon": "fa fa-warning-sign", "idx": 1, "in_create": 1, "links": [], - "modified": "2024-12-09 14:22:44.819718", + "modified": "2025-11-27 18:04:21.399557", "modified_by": "Administrator", "module": "Core", "name": "Error Log", @@ -90,8 +97,9 @@ "write": 1 } ], + "row_format": "Dynamic", "sort_field": "creation", "sort_order": "DESC", "states": [], "title_field": "method" -} \ No newline at end of file +} diff --git a/frappe/core/doctype/error_log/error_log.py b/frappe/core/doctype/error_log/error_log.py index 7f672e3593..dffb41241a 100644 --- a/frappe/core/doctype/error_log/error_log.py +++ b/frappe/core/doctype/error_log/error_log.py @@ -17,6 +17,7 @@ class ErrorLog(Document): from frappe.types import DF error: DF.Code | None + metadata: DF.Code | None method: DF.Data | None reference_doctype: DF.Link | None reference_name: DF.Data | None diff --git a/frappe/utils/error.py b/frappe/utils/error.py index 15bb9e8a47..deed1deb34 100644 --- a/frappe/utils/error.py +++ b/frappe/utils/error.py @@ -59,6 +59,8 @@ def log_error(title=None, message=None, reference_doctype=None, reference_name=N return trace_id = get_trace_id() + metadata = get_error_metadata() + error_log = frappe.get_doc( doctype="Error Log", error=traceback, @@ -66,6 +68,7 @@ def log_error(title=None, message=None, reference_doctype=None, reference_name=N reference_doctype=reference_doctype, reference_name=reference_name, trace_id=trace_id, + metadata=metadata, ) # Capture exception data if telemetry is enabled @@ -77,6 +80,40 @@ def log_error(title=None, message=None, reference_doctype=None, reference_name=N return error_log.insert(ignore_permissions=True) +def get_error_metadata() -> str: + """ + Returns request/job metadata to store in Error Log for easier debugging + """ + import rq + + from frappe.utils.logger import sanitized_dict + + metadata = {} + + try: + if job := rq.get_current_job(): + metadata["type"] = "background_job" + metadata["job_id"] = job.id + metadata["job_name"] = frappe.cstr(job.kwargs.get("method")) + metadata["queue"] = job.origin + metadata["kwargs"] = sanitized_dict(job.kwargs) + + if "run_scheduled_job" in metadata["job_name"]: + metadata["scheduled"] = True + metadata["job_type"] = job.kwargs.get("kwargs", {}).get("job_type", "") + + else: + metadata["type"] = "http_request" + for key in ("method", "path", "referrer"): + metadata[key] = getattr(frappe.local.request, key) + metadata["form_dict"] = sanitized_dict(frappe.form_dict) + + metadata["user"] = getattr(frappe.session, "user", "Unidentified") + finally: + # We don't want to bother with exception handling *while* gathering some error's metadata + return frappe.as_json(metadata) # noqa: B012 + + def log_error_snapshot(exception: Exception): if isinstance(exception, EXCLUDE_EXCEPTIONS) or _is_ldap_exception(exception): return