This is an automated email from the ASF dual-hosted git repository.

brbzull0 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new f83807869d Improve config reload error reporting with severity-aware 
task logs (#13090)
f83807869d is described below

commit f83807869d69b84113e66661cd4c0c39a99cd85a
Author: Damian Meden <[email protected]>
AuthorDate: Tue May 5 14:07:25 2026 +0200

    Improve config reload error reporting with severity-aware task logs (#13090)
    
    * Add severity-aware logging to config reload framework
    
    Unify diagnostic logging (diags.log) with ConfigContext task logs
    (traffic_ctl config status) via CfgLoad* macros. Each task log
    entry now carries a DiagsLevel severity, enabling --min-level
    filtering in traffic_ctl and severity tags in output ([Note],
    [Err], etc). Reload summaries are logged to diags.log after a
    grace period, with detailed per-subtask dumps under the
    config.reload debug tag.
    
    Fixes: #12963
---
 doc/appendices/command-line/traffic_ctl.en.rst     |  90 +++++++-
 doc/developer-guide/config-reload-framework.en.rst | 229 ++++++++++++++++++++-
 include/iocore/net/SSLSNIConfig.h                  |   5 +-
 include/iocore/net/quic/QUICConfig.h               |   3 +-
 include/mgmt/config/ConfigContext.h                |   3 +
 include/mgmt/config/ConfigContextDiags.h           | 185 +++++++++++++++++
 include/mgmt/config/ConfigReloadTrace.h            |  20 +-
 include/proxy/ControlMatcher.h                     |   9 +-
 include/proxy/http/remap/RemapConfig.h             |   5 +-
 include/proxy/http/remap/RemapYamlConfig.h         |   5 +-
 include/proxy/http/remap/UrlRewrite.h              |   6 +-
 include/records/YAMLConfigReloadTaskEncoder.h      |   7 +-
 src/iocore/cache/Cache.cc                          |   5 +-
 src/iocore/cache/CacheHosting.cc                   |  37 ++--
 src/iocore/cache/P_CacheHosting.h                  |   7 +-
 src/iocore/dns/SplitDNS.cc                         |  15 +-
 src/iocore/net/P_SSLConfig.h                       |   5 +-
 src/iocore/net/QUICMultiCertConfigLoader.cc        |  15 +-
 src/iocore/net/SSLConfig.cc                        |  44 ++--
 src/iocore/net/SSLSNIConfig.cc                     |  32 ++-
 src/iocore/net/SSLUtils.cc                         |   3 +-
 src/iocore/net/quic/QUICConfig.cc                  |  11 +-
 src/mgmt/config/ConfigContext.cc                   |  28 ++-
 src/mgmt/config/ConfigReloadTrace.cc               |  82 +++++++-
 src/proxy/CacheControl.cc                          |  13 +-
 src/proxy/ControlMatcher.cc                        |  21 +-
 src/proxy/IPAllow.cc                               |  30 +--
 src/proxy/ParentSelection.cc                       |  15 +-
 src/proxy/ReverseProxy.cc                          |  14 +-
 src/proxy/http/PreWarmConfig.cc                    |   2 +-
 src/proxy/http/remap/RemapConfig.cc                |  13 +-
 src/proxy/http/remap/RemapYamlConfig.cc            |  20 +-
 src/proxy/http/remap/UrlRewrite.cc                 |  27 +--
 src/proxy/logging/LogConfig.cc                     |  13 +-
 src/proxy/logging/YamlLogConfig.cc                 |   7 +-
 src/records/unit_tests/test_ConfigReloadTask.cc    |   6 +-
 src/traffic_ctl/CtrlCommands.cc                    |  28 ++-
 src/traffic_ctl/CtrlPrinters.cc                    |  23 ++-
 src/traffic_ctl/CtrlPrinters.h                     |  13 ++
 src/traffic_ctl/jsonrpc/CtrlRPCRequests.h          |  18 +-
 src/traffic_ctl/jsonrpc/ctrl_yaml_codecs.h         |   9 +-
 src/traffic_ctl/traffic_ctl.cc                     |   1 +
 src/traffic_server/traffic_server.cc               |   6 +-
 43 files changed, 903 insertions(+), 227 deletions(-)

diff --git a/doc/appendices/command-line/traffic_ctl.en.rst 
b/doc/appendices/command-line/traffic_ctl.en.rst
index dc560fe9cc..44f698714e 100644
--- a/doc/appendices/command-line/traffic_ctl.en.rst
+++ b/doc/appendices/command-line/traffic_ctl.en.rst
@@ -227,7 +227,7 @@ Display the current value of a configuration record.
 
    - **Monitor** a reload in real-time: ``traffic_ctl config reload -t <token> 
-m``
    - **Query** the final status: ``traffic_ctl config status -t <token>``
-   - **Get detailed logs**: ``traffic_ctl config status -t <token> -l``
+   - **Get detailed logs**: ``traffic_ctl config status -t <token>``
 
    The timestamp of the last reconfiguration event (in seconds since epoch) is 
published in the
    ``proxy.process.proxy.reconfigure_time`` metric.
@@ -659,7 +659,8 @@ Display the current value of a configuration record.
    **Failed reload report:**
 
    When a reload has failed handlers, the output shows which handlers 
succeeded and which failed,
-   along with durations for each:
+   along with durations and per-handler log entries. Log entries carry 
severity tags when a
+   severity level was recorded:
 
    .. code-block:: bash
 
@@ -673,10 +674,17 @@ Display the current value of a configuration record.
 
         Tasks:
          ✔ ip_allow.yaml ·························· 18ms
-         ✗ logging.yaml ·························· 120ms  ✗ FAIL
          ✗ ssl_client_coordinator ················· 85ms  ✗ FAIL
-         ├─ ✔ sni.yaml ··························· 20ms
-         └─ ✗ ssl_multicert.config ··············· 65ms  ✗ FAIL
+         │  [Note]  SSL configs reloaded
+         ├─ ✔ SSLConfig ·························· 10ms
+         │     [Note]  SSLConfig loading ...
+         │     [Note]  SSLConfig reloaded
+         ├─ ✗ SNIConfig ·························· 12ms  ✗ FAIL
+         │     [Note]  sni.yaml loading ...
+         │     [Err]   sni.yaml failed to load: yaml-cpp error ...
+         └─ ✔ SSLCertificateConfig ·············· 13ms
+               [Note]  (ssl) ssl_multicert.yaml loading ...
+               [Note]  (ssl) ssl_multicert.yaml finished loading
          ...
 
    Supports the following options:
@@ -704,6 +712,78 @@ Display the current value of a configuration record.
          # Show last 5 reloads
          $ traffic_ctl config status -c 5
 
+   .. option:: --min-level <level>
+
+      Filter task log entries by minimum severity level. Only entries at or 
above the specified
+      level are displayed. State-transition messages carry implicit severity:
+      ``in_progress()`` and ``complete()`` produce ``[Note]`` entries, 
``fail()`` produces
+      ``[Err]`` entries. Entries without a severity (``DL_Undefined``) — 
typically those logged
+      via the one-argument ``ctx.log(text)`` — are always shown regardless of 
this filter.
+
+      Valid levels (case-insensitive): ``debug``, ``note``, ``warning``, 
``error``.
+
+      .. code-block:: bash
+
+         # Show only warnings and errors
+         $ traffic_ctl config status -t my-token --min-level warning
+
+         # Show only errors
+         $ traffic_ctl config status -t my-token --min-level error
+
+      **Example — all logs (no filter):**
+
+      .. code-block:: text
+
+         ✗ ssl_client_coordinator ·······················    2ms  ✗ FAIL
+         │  [Note]  SSL configs reloaded
+         ├─ ✔ SSLConfig ·································    1ms
+         │     [Note]  SSLConfig loading ...
+         │     [Note]  SSLConfig reloaded
+         ├─ ✗ SNIConfig ·································    1ms  ✗ FAIL
+         │     [Note]  sni.yaml loading ...
+         │     [Err]   sni.yaml failed to load
+         └─ ✔ SSLCertificateConfig ······················    0ms
+               [Note]  (ssl) ssl_multicert.yaml loading ...
+               [Warn]  Cannot open SSL certificate configuration 
"ssl_multicert.yaml" - No such file or directory
+               [Note]  (ssl) ssl_multicert.yaml finished loading
+
+      **Example — --min-level warning (note and debug entries filtered out):**
+
+      .. code-block:: text
+
+         ✗ ssl_client_coordinator ·······················    2ms  ✗ FAIL
+         ├─ ✗ SNIConfig ·································    1ms  ✗ FAIL
+         │     [Err]   sni.yaml failed to load
+         └─ ✔ SSLCertificateConfig ······················    0ms
+               [Warn]  Cannot open SSL certificate configuration 
"ssl_multicert.yaml" - No such file or directory
+
+      All entries from state transitions and ``CfgLoad*`` macros carry a 
severity tag
+      (e.g. ``[Dbg]``, ``[Note]``, ``[Warn]``, ``[Err]``). Entries without a 
tag are
+      "unleveled" (from the one-argument ``ctx.log(text)``) and always pass 
the filter.
+
+   .. tip::
+
+      For deeper investigation beyond what ``traffic_ctl config status`` 
shows, enable the
+      ``config.reload`` debug tag. This writes a full dump of every subtask 
and its log entries
+      (with severity tags) to ``diags.log`` after each reload completes.
+      See :ref:`config-reload-diags-log` in the developer guide for details 
and examples.
+
+      Enable at runtime without restarting:
+
+      .. code-block:: bash
+
+         $ traffic_ctl server debug enable --tags "config.reload"
+
+      Or persistently in ``records.yaml``:
+
+      .. code-block:: yaml
+
+         records:
+           diags:
+             debug:
+               enabled: 1
+               tags: config.reload
+
    **JSON output:**
 
    All ``config status`` commands support the global ``--format json`` option 
to output the raw
diff --git a/doc/developer-guide/config-reload-framework.en.rst 
b/doc/developer-guide/config-reload-framework.en.rst
index 5b11507894..550795be1b 100644
--- a/doc/developer-guide/config-reload-framework.en.rst
+++ b/doc/developer-guide/config-reload-framework.en.rst
@@ -663,7 +663,7 @@ Logging Best Practices
 ======================
 
 - Use ``ctx.log()`` for operational messages that appear in
-  ``traffic_ctl config status -l`` and :ref:`get_reload_config_status` 
responses.
+  ``traffic_ctl config status`` and :ref:`get_reload_config_status` responses.
 - Use ``ctx.fail(errata, summary)`` when you have a ``swoc::Errata`` with 
detailed error context.
 - Use ``ctx.fail(reason)`` for simple error strings.
 - Keep log messages concise — they are stored in memory and included in 
JSONRPC responses.
@@ -672,6 +672,228 @@ See the :ref:`get_reload_config_status` response examples 
for how log messages a
 task tree output.
 
 
+.. _config-reload-unified-macros:
+
+Unified Diagnostic Macros (``CfgLoad*``)
+=========================================
+
+Config handlers often need the same message in two places: the ATS diagnostic 
log
+(``diags.log`` / ``error.log``) **and** the reload task log (visible via
+:option:`traffic_ctl config status`). The ``CfgLoad*`` macros in
+``mgmt/config/ConfigContextDiags.h`` format the message once and dispatch to 
both destinations.
+
+Include the header in any source file that uses these macros:
+
+.. code-block:: cpp
+
+   #include "mgmt/config/ConfigContextDiags.h"
+
+Quick Reference
+---------------
+
+.. list-table::
+   :header-rows: 1
+   :widths: 15 15 40
+
+   * - Want in diags?
+     - Want in task log?
+     - Use
+   * - Note
+     - yes + in_progress
+     - ``CfgLoadInProgress(ctx, ...)`` (subtasks)
+   * - Note
+     - yes + complete
+     - ``CfgLoadComplete(ctx, ...)``
+   * - Error
+     - yes + fail
+     - ``CfgLoadFail(ctx, ...)``
+   * - Error + Errata
+     - yes + fail
+     - ``CfgLoadFailWithErrata(ctx, errata, ...)``
+   * - Note / Warning
+     - yes (no state change)
+     - ``CfgLoadLog(ctx, DL_Note|DL_Warning, ...)``
+   * - Dbg (conditional on tag)
+     - yes
+     - ``CfgLoadDbg(ctx, ctl, ...)``
+   * - no
+     - yes
+     - ``ctx.log(...)``
+   * - no
+     - yes + state
+     - ``ctx.complete()`` / ``ctx.fail()``
+   * - yes
+     - no
+     - ``Note()`` / ``Warning()`` / ``Error()`` / ``Dbg()`` directly
+
+Macro Details
+-------------
+
+``CfgLoadInProgress(ctx, fmt, ...)``
+   Emits a ``Note()`` to ``diags.log`` and calls ``ctx.in_progress(msg)``. The 
framework
+   sets ``IN_PROGRESS`` on handler tasks automatically, so this macro is 
primarily useful
+   for subtasks created via ``add_dependent_ctx()``:
+
+   .. code-block:: cpp
+
+      CfgLoadInProgress(ctx, "%s loading ...", filename);
+
+``CfgLoadComplete(ctx, fmt, ...)``
+   Emits a ``Note()`` to ``diags.log`` and calls ``ctx.complete(msg)``. Use 
when a config
+   operation finishes successfully:
+
+   .. code-block:: cpp
+
+      CfgLoadComplete(ctx, "%s finished loading", filename);
+
+``CfgLoadFail(ctx, fmt, ...)``
+   Emits an ``Error()`` to ``diags.log`` and the task log, then marks the task 
as FAIL.
+   Fail always implies ``DL_Error`` — if the condition is merely degraded (not 
fatal to
+   the load), use ``CfgLoadLog(ctx, DL_Warning, ...)`` + ``CfgLoadComplete()`` 
instead:
+
+   .. code-block:: cpp
+
+      CfgLoadFail(ctx, "%s failed to load", filename);
+
+``CfgLoadFailWithErrata(ctx, errata, fmt, ...)``
+   Like ``CfgLoadFail`` but also appends ``swoc::Errata`` annotations to the 
task log.
+   Combines ``CfgLoadFail`` + ``ctx.fail(errata)`` in one call — see
+   :ref:`config-reload-errata-handling` below.
+
+``CfgLoadLog(ctx, level, fmt, ...)``
+   Emits at the given ``DiagsLevel`` and calls ``ctx.log(level, msg)`` 
**without changing
+   task state**. Use for intermediate informational messages:
+
+   .. code-block:: cpp
+
+      CfgLoadLog(ctx, DL_Warning, "ControlMatcher - Cannot open config file: 
%s", path);
+      CfgLoadLog(ctx, DL_Note, "loaded %d categories from %s", count, 
filename);
+
+``CfgLoadDbg(ctx, dbg_ctl, fmt, ...)``
+   Emits via ``Dbg()`` (conditional on the tag being enabled) and always adds 
to the task log
+   at ``DL_Debug``. Use for debug-level messages that should also appear in 
reload status:
+
+   .. code-block:: cpp
+
+      CfgLoadDbg(ctx, dbg_ctl_ssl, "Reload SNI file");
+
+.. _config-reload-errata-handling:
+
+Errata Handling
+---------------
+
+For failures with ``swoc::Errata`` detail, use ``CfgLoadFailWithErrata`` to 
combine
+the diags summary, errata detail, and state change in a single call:
+
+.. code-block:: cpp
+
+   CfgLoadFailWithErrata(ctx, errata, "%s failed to load", filename);
+
+This logs the formatted message to ``diags.log`` at ``DL_Error``, appends it to
+the task log, then calls ``ctx.fail(errata)`` which stores each errata 
annotation
+(with its own severity) in the task log and marks the task as FAIL.
+
+For errors that should not change state, pair ``CfgLoadLog`` with 
``ctx.log(errata)``:
+
+.. code-block:: cpp
+
+   CfgLoadLog(ctx, DL_Error, "Cannot open %s", path);
+   ctx.log(errata);  // errata detail -> task log only
+
+When NOT to Use Macros
+-----------------------
+
+- **Task-log-only messages** — use ``ctx.log()`` directly when the message is 
only useful in
+  ``traffic_ctl`` output and should not appear in ``diags.log``.
+- **State-only transitions** — use ``ctx.in_progress()`` / ``ctx.complete()`` 
/ ``ctx.fail()``
+  directly when there is no message to emit to ``diags.log``.
+- **Fatal errors** — ``Fatal()`` terminates the process; reload status is 
irrelevant.
+  Call ``Fatal()`` directly.
+
+
+Severity-Aware Task Logs
+=========================
+
+Each task log entry carries a ``DiagsLevel`` severity. State-transition 
methods carry implicit
+severity: ``in_progress(text)`` and ``complete(text)`` store ``DL_Note``, 
``fail(text)`` stores
+``DL_Error``. The ``CfgLoad*`` macros and ``ctx.log(level, text)`` store the 
caller-specified
+level. Only the one-argument ``ctx.log(text)`` (no level) stores 
``DL_Undefined`` — these
+entries are always shown regardless of ``--min-level`` filtering.
+
+In :option:`traffic_ctl config status` output, entries with a defined severity 
are prefixed
+with a tag:
+
+.. code-block:: text
+
+   ✗ ssl_client_coordinator ·······················    2ms  ✗ FAIL
+   │  [Note]  SSL configs reloaded
+   ├─ ✔ SSLConfig ·································    1ms
+   │     [Note]  SSLConfig loading ...
+   │     [Note]  SSLConfig reloaded
+   ├─ ✗ SNIConfig ·································    1ms  ✗ FAIL
+   │     [Note]  sni.yaml loading ...
+   │     [Err]   sni.yaml failed to load
+   └─ ✔ SSLCertificateConfig ······················    0ms
+         [Note]  (ssl) ssl_multicert.yaml loading ...
+         [Warn]  Cannot open SSL certificate configuration 
"ssl_multicert.yaml" - No such file or directory
+         [Note]  (ssl) ssl_multicert.yaml finished loading
+
+The ``--min-level`` option on :option:`traffic_ctl config status` filters log 
entries
+by severity — see :option:`traffic_ctl config status` for details.
+
+The severity is also available in JSON output (``--format json``) as an 
integer ``level``
+field on each log entry, where the value maps to the ``DiagsLevel`` enum (e.g. 
``1`` = Debug,
+``3`` = Note, ``4`` = Warning, ``5`` = Error).
+
+
+.. _config-reload-diags-log:
+
+Reload Summary in ``diags.log``
+================================
+
+After a reload reaches a terminal state (confirmed after a 5-second grace 
period), a summary
+line is logged to ``diags.log``:
+
+**Success:**
+
+.. code-block:: text
+
+   NOTE: Config reload [my-token] completed: 3/3 tasks succeeded
+
+**Failure:**
+
+.. code-block:: text
+
+   WARNING: Config reload [my-token] finished with failures: 1 succeeded, 1 
failed (3 total) — run: traffic_ctl config status -t my-token
+
+When the ``config.reload`` debug tag is enabled, a detailed dump of all 
subtasks and their
+log entries is written to ``traffic.out`` / ``diags.log``:
+
+.. code-block:: text
+
+   DIAG: (config.reload)   [fail] ssl_client_coordinator
+   DIAG: (config.reload)     [Note] SSL configs reloaded
+   DIAG: (config.reload)     [success] SSLConfig
+   DIAG: (config.reload)       [Note] SSLConfig loading ...
+   DIAG: (config.reload)       [Note] SSLConfig reloaded
+   DIAG: (config.reload)     [fail] SNIConfig
+   DIAG: (config.reload)       [Note] sni.yaml loading ...
+   DIAG: (config.reload)       [Err] sni.yaml failed to load
+   DIAG: (config.reload)   [success] ssl_ticket_key
+   DIAG: (config.reload)     [Note] SSL ticket key loading ...
+   DIAG: (config.reload)     [Note] SSL ticket key reloaded
+
+Enable this tag for troubleshooting:
+
+.. code-block:: yaml
+
+   records:
+     diags:
+       debug:
+         enabled: 1
+         tags: config.reload
+
+
 Testing
 ========
 
@@ -683,7 +905,10 @@ After registering a new handler:
 3. Run :option:`traffic_ctl config status` to verify the handler appears in 
the task tree with
    the correct status.
 4. Introduce a parse error in the config file and reload — verify the handler 
reports ``FAIL``.
-5. Use :option:`traffic_ctl config status` ``--format json`` to inspect the raw
+5. Check that severity tags (``[Dbg]``, ``[Err]``, etc.) appear correctly in
+   :option:`traffic_ctl config status` output and that ``--min-level`` 
filtering works.
+6. Enable the ``config.reload`` debug tag and verify the detailed dump appears 
in ``diags.log``.
+7. Use :option:`traffic_ctl config status` ``--format json`` to inspect the raw
    :ref:`get_reload_config_status` response for automation testing.
 
 **Autests** — the project includes autest helpers for config reload testing. 
Use
diff --git a/include/iocore/net/SSLSNIConfig.h 
b/include/iocore/net/SSLSNIConfig.h
index b71502b6e3..64dd23a7c5 100644
--- a/include/iocore/net/SSLSNIConfig.h
+++ b/include/iocore/net/SSLSNIConfig.h
@@ -43,6 +43,7 @@
 #include "iocore/eventsystem/ConfigProcessor.h"
 #include "iocore/net/SNIActionItem.h"
 #include "iocore/net/YamlSNIConfig.h"
+#include "mgmt/config/ConfigContext.h"
 
 #include <functional>
 
@@ -90,8 +91,8 @@ public:
   ~SNIConfigParams() override;
 
   const NextHopProperty *get_property_config(const std::string &servername) 
const;
-  bool                   initialize();
-  bool                   initialize(const std::string &sni_filename);
+  bool                   initialize(ConfigContext ctx = {});
+  bool                   initialize(const std::string &sni_filename, 
ConfigContext ctx = {});
   /** Walk sni.yaml config and populate sni_action_list
       @return 0 for success, 1 is failure
    */
diff --git a/include/iocore/net/quic/QUICConfig.h 
b/include/iocore/net/quic/QUICConfig.h
index 3bc871091a..1ebec4f6aa 100644
--- a/include/iocore/net/quic/QUICConfig.h
+++ b/include/iocore/net/quic/QUICConfig.h
@@ -28,6 +28,7 @@
 
 #include "iocore/eventsystem/ConfigProcessor.h"
 #include "iocore/net/SSLTypes.h"
+#include "mgmt/config/ConfigContext.h"
 
 class QUICConfigParams : public ConfigInfo
 {
@@ -35,7 +36,7 @@ public:
   QUICConfigParams(){};
   ~QUICConfigParams();
 
-  void initialize();
+  void initialize(ConfigContext ctx = {});
 
   uint32_t instance_id() const;
   uint32_t stateless_retry() const;
diff --git a/include/mgmt/config/ConfigContext.h 
b/include/mgmt/config/ConfigContext.h
index 0dbce853a1..49807c02dd 100644
--- a/include/mgmt/config/ConfigContext.h
+++ b/include/mgmt/config/ConfigContext.h
@@ -33,6 +33,7 @@
 
 #include "swoc/Errata.h"
 #include "swoc/BufferWriter.h"
+#include "tsutil/ts_diag_levels.h"
 #include "yaml-cpp/node/node.h"
 
 // Forward declarations
@@ -118,6 +119,8 @@ public:
   }
 
   void log(std::string_view text);
+  void log(DiagsLevel level, std::string_view text);
+  void log(swoc::Errata const &errata);
   template <typename... Args>
   void
   log(swoc::TextView fmt, Args &&...args)
diff --git a/include/mgmt/config/ConfigContextDiags.h 
b/include/mgmt/config/ConfigContextDiags.h
new file mode 100644
index 0000000000..96c09c5df5
--- /dev/null
+++ b/include/mgmt/config/ConfigContextDiags.h
@@ -0,0 +1,185 @@
+/** @file
+
+  ConfigContextDiags.h — Convenience macros for config handler logging.
+
+  These macros combine diags output (Note/Warning/Error) with ConfigContext
+  task tracking in a single call. They format the message once and send it
+  to both destinations:
+    1. The ATS diagnostics system (diags.log / error.log)
+    2. The ConfigContext reload task log (visible via traffic_ctl config 
status)
+
+  This ensures operators see the same information whether they look at
+  diags.log or query reload status via traffic_ctl / JSONRPC.
+
+  @section when When to use these macros
+
+  Use a macro when you want the message in BOTH diags and the reload task log.
+  This is the common case for operational messages in config handlers:
+
+  @code
+    CfgLoadInProgress(ctx, "%s loading ...", filename);  // subtasks
+    CfgLoadLog(ctx, DL_Note, "%s loading ...", filename); // top-level handlers
+    CfgLoadComplete(ctx, "%s finished loading", filename);
+    CfgLoadFail(ctx, "%s failed to load", filename);
+  @endcode
+
+  Use ctx methods directly when you only want the reload task log (no diags):
+
+  @code
+    ctx.log("parsed %d rules", count);         // task log only
+    ctx.in_progress();                          // state change only, no 
message
+    ctx.complete();                             // state change only, no 
message
+  @endcode
+
+  Use Dbg() directly when you only want debug output (no task log):
+
+  @code
+    Dbg(dbg_ctl_ssl, "internal detail ...");    // diags only, not in reload 
status
+  @endcode
+
+  Use CfgLoadDbg when you want BOTH debug output and the task log:
+
+  @code
+    CfgLoadDbg(ctx, dbg_ctl_ssl, "Reload SNI file");
+  @endcode
+
+  @section summary Quick reference
+
+  | Want diags? | Want task log? | Use                               |
+  |-------------|----------------|-----------------------------------|
+  | Note        | yes + in_progress| CfgLoadInProgress(ctx, ...)     |
+  | Note        | yes + complete  | CfgLoadComplete(ctx, ...)       |
+  | Error       | yes + fail      | CfgLoadFail(ctx, ...)           |
+  | Err + Errata| yes + fail      | CfgLoadFailWithErrata(...)      |
+  | Note/Warn   | yes (no state)  | CfgLoadLog(ctx, DL_xxx, ...)    |
+  | Dbg(tag)    | yes            | CfgLoadDbg(ctx, ctl, ...)       |
+  | no          | yes            | ctx.log(...)                      |
+  | no          | yes + state    | ctx.complete() / ctx.fail()       |
+  | yes         | no             | Note/Warning/Error/Dbg directly   |
+
+  @section errata Errata handling
+
+  For failures with swoc::Errata detail, use CfgLoadFailWithErrata to
+  combine the diags summary, errata detail, and state change in one call:
+
+  @code
+    CfgLoadFailWithErrata(ctx, errata, "%s failed to load", filename);
+  @endcode
+
+  This logs the formatted message to diags and the task log at the given
+  severity, appends each errata annotation (with its own severity) to the
+  task log, and marks the task as FAIL.
+
+  @section fatal Fatal errors
+
+  Fatal/Emergency terminate the process — reload status is irrelevant.
+  Call Fatal() directly; do not use these macros for it.
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+*/
+
+#pragma once
+
+#include "mgmt/config/ConfigContext.h"
+#include "tscore/Diags.h"
+
+/// Log a Note and mark the context as IN_PROGRESS.
+/// The framework sets IN_PROGRESS on handler tasks automatically, so
+/// CfgLoadLog(ctx, DL_Note, ...) is preferred for top-level "loading..."
+/// messages. Use this macro for subtasks created via add_dependent_ctx().
+///
+///   CfgLoadInProgress(ctx, "%s loading ...", ts::filename::IP_ALLOW);
+///
+#define CfgLoadInProgress(CTX, FMT, ...)                            \
+  do {                                                              \
+    char _cfgctx_buf[1024];                                         \
+    snprintf(_cfgctx_buf, sizeof(_cfgctx_buf), FMT, ##__VA_ARGS__); \
+    Note("%s", _cfgctx_buf);                                        \
+    (CTX).in_progress(_cfgctx_buf);                                 \
+  } while (false)
+
+/// Log a Note and mark the context as SUCCESS.
+/// Use when a config load/reload operation finishes successfully.
+///
+///   CfgLoadComplete(ctx, "%s finished loading", ts::filename::IP_ALLOW);
+///
+#define CfgLoadComplete(CTX, FMT, ...)                              \
+  do {                                                              \
+    char _cfgctx_buf[1024];                                         \
+    snprintf(_cfgctx_buf, sizeof(_cfgctx_buf), FMT, ##__VA_ARGS__); \
+    Note("%s", _cfgctx_buf);                                        \
+    (CTX).complete(_cfgctx_buf);                                    \
+  } while (false)
+
+/// Log an Error and mark the context as FAIL.
+/// Use when a config load/reload operation fails.  Fail always implies
+/// DL_Error — if the condition is merely degraded (not fatal to the load),
+/// use CfgLoadLog(ctx, DL_Warning, ...) + CfgLoadComplete() instead.
+///
+///   CfgLoadFail(ctx, "%s failed to load", ts::filename::IP_ALLOW);
+///
+#define CfgLoadFail(CTX, FMT, ...)                                  \
+  do {                                                              \
+    char _cfgctx_buf[1024];                                         \
+    snprintf(_cfgctx_buf, sizeof(_cfgctx_buf), FMT, ##__VA_ARGS__); \
+    DiagsError(DL_Error, "%s", _cfgctx_buf);                        \
+    (CTX).log(DL_Error, _cfgctx_buf);                               \
+    (CTX).fail();                                                   \
+  } while (false)
+
+/// Log an Error, append errata detail to the task log, and mark the context
+/// as FAIL.  Combines CfgLoadFail + ctx.fail(errata) in one call.
+///
+///   CfgLoadFailWithErrata(ctx, errata, "%s failed to load", filename);
+///
+#define CfgLoadFailWithErrata(CTX, ERRATA, FMT, ...)                \
+  do {                                                              \
+    char _cfgctx_buf[1024];                                         \
+    snprintf(_cfgctx_buf, sizeof(_cfgctx_buf), FMT, ##__VA_ARGS__); \
+    DiagsError(DL_Error, "%s", _cfgctx_buf);                        \
+    (CTX).log(DL_Error, _cfgctx_buf);                               \
+    (CTX).fail(ERRATA);                                             \
+  } while (false)
+
+/// Log at the given DiagsLevel and add to the task log, without changing 
state.
+/// Use for intermediate informational messages during load/reload.
+///
+///   CfgLoadLog(ctx, DL_Note, "loaded %d categories from %s", count, 
filename);
+///
+#define CfgLoadLog(CTX, LEVEL, FMT, ...)                            \
+  do {                                                              \
+    char _cfgctx_buf[1024];                                         \
+    snprintf(_cfgctx_buf, sizeof(_cfgctx_buf), FMT, ##__VA_ARGS__); \
+    DiagsError(LEVEL, "%s", _cfgctx_buf);                           \
+    (CTX).log(LEVEL, _cfgctx_buf);                                  \
+  } while (false)
+
+/// Log via a DbgCtl (debug-level, conditional on the tag) and add to the task
+/// log. The debug output only appears when the tag is enabled; the task log
+/// always receives the message.
+///
+///   CfgLoadDbg(ctx, dbg_ctl_ssl, "Reload SNI file");
+///
+#define CfgLoadDbg(CTX, CTL, FMT, ...)                              \
+  do {                                                              \
+    char _cfgctx_buf[1024];                                         \
+    snprintf(_cfgctx_buf, sizeof(_cfgctx_buf), FMT, ##__VA_ARGS__); \
+    Dbg((CTL), "%s", _cfgctx_buf);                                  \
+    (CTX).log(DL_Debug, _cfgctx_buf);                               \
+  } while (false)
diff --git a/include/mgmt/config/ConfigReloadTrace.h 
b/include/mgmt/config/ConfigReloadTrace.h
index e52078e0b9..7d9dadbca0 100644
--- a/include/mgmt/config/ConfigReloadTrace.h
+++ b/include/mgmt/config/ConfigReloadTrace.h
@@ -171,6 +171,16 @@ public:
     return 
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
   }
 
+  /// A single log entry with optional severity.
+  /// Entries from log(level, text) carry the supplied DiagsLevel. State-change
+  /// convenience methods attach an implicit level: in_progress() and 
complete()
+  /// log at DL_Note, fail() logs at DL_Error. The one-argument log(text) form
+  /// stores DL_Undefined and is always displayed (never filtered by 
--min-level).
+  struct LogEntry {
+    DiagsLevel  level{DL_Undefined}; ///< DL_Undefined = always shown (filter 
bypass)
+    std::string text;
+  };
+
   struct Info {
     friend class ConfigReloadTask;
     /// Grant friendship to the specific YAML::convert specialization.
@@ -184,7 +194,7 @@ public:
   protected:
     int64_t                          created_time_ms{now_ms()};      ///< 
milliseconds since epoch
     int64_t                          last_updated_time_ms{now_ms()}; ///< last 
time this task was updated (ms)
-    std::vector<std::string>         logs;                           ///< log 
messages from handler
+    std::vector<LogEntry>            logs;                           ///< log 
messages from handler
     State                            state{State::CREATED};
     std::string                      token;
     std::string                      description;
@@ -214,6 +224,7 @@ public:
   [[nodiscard]] ConfigContext add_child(std::string_view description = "");
 
   self_type &log(std::string const &text);
+  self_type &log(DiagsLevel level, std::string const &text);
   void       set_completed();
   void       set_failed();
   void       set_in_progress();
@@ -297,7 +308,7 @@ public:
   /// Mark task as TIMEOUT with an optional reason logged
   void mark_as_bad_state(std::string_view reason = "");
 
-  [[nodiscard]] std::vector<std::string>
+  [[nodiscard]] std::vector<LogEntry>
   get_logs() const
   {
     std::shared_lock<std::shared_mutex> lock(_mutex);
@@ -356,8 +367,13 @@ private:
   void notify_parent();
   void set_state_and_notify(State state);
 
+  friend struct ConfigReloadProgress;
+  void        log_reload_summary(State final_state);
+  static void dump_subtask_tree(const std::vector<ConfigReloadTaskPtr> &tasks, 
int indent);
+
   mutable std::shared_mutex _mutex;
   bool                      _reload_progress_checker_started{false};
+  bool                      _summary_logged{false};
   Info                      _info;
   ConfigReloadTaskPtr       _parent; ///< parent task, if any
 
diff --git a/include/proxy/ControlMatcher.h b/include/proxy/ControlMatcher.h
index 96e9c36e96..505f09e343 100644
--- a/include/proxy/ControlMatcher.h
+++ b/include/proxy/ControlMatcher.h
@@ -99,6 +99,8 @@
 
 #include <swoc/swoc_ip.h>
 
+#include "mgmt/config/ConfigContext.h"
+
 #ifdef HAVE_CTYPE_H
 #include <cctype>
 #endif
@@ -302,11 +304,12 @@ template <class Data, class MatchResult> class 
ControlMatcher
 public:
   // Parameter name must not be deallocated before this object is
   ControlMatcher(const char *file_var, const char *name, const matcher_tags 
*tags,
-                 int flags_in = (ALLOW_HOST_TABLE | ALLOW_IP_TABLE | 
ALLOW_REGEX_TABLE | ALLOW_HOST_REGEX_TABLE | ALLOW_URL_TABLE));
+                 int flags_in = (ALLOW_HOST_TABLE | ALLOW_IP_TABLE | 
ALLOW_REGEX_TABLE | ALLOW_HOST_REGEX_TABLE | ALLOW_URL_TABLE),
+                 ConfigContext ctx = {});
   ~ControlMatcher();
 
-  int BuildTable();
-  int BuildTableFromString(char *str);
+  int BuildTable(ConfigContext ctx = {});
+  int BuildTableFromString(char *str, ConfigContext ctx = {});
 
   void Match(RequestData *rdata, MatchResult *result) const;
   void Print() const;
diff --git a/include/proxy/http/remap/RemapConfig.h 
b/include/proxy/http/remap/RemapConfig.h
index 129d619f26..d745a4d93b 100644
--- a/include/proxy/http/remap/RemapConfig.h
+++ b/include/proxy/http/remap/RemapConfig.h
@@ -23,6 +23,7 @@
 
 #pragma once
 
+#include "mgmt/config/ConfigContext.h"
 #include "proxy/http/remap/AclFiltering.h"
 
 class UrlRewrite;
@@ -80,7 +81,7 @@ struct BUILD_TABLE_INFO {
 };
 
 const char *remap_parse_directive(BUILD_TABLE_INFO *bti, char *errbuf, size_t 
errbufsize);
-bool        remap_parse_config_bti(const char *path, BUILD_TABLE_INFO *bti);
+bool        remap_parse_config_bti(const char *path, BUILD_TABLE_INFO *bti, 
ConfigContext ctx = {});
 
 const char *remap_validate_filter_args(acl_filter_rule **rule_pp, const char 
*const *argv, int argc, char *errStrBuf,
                                        size_t errStrBufSize, ACLBehaviorPolicy 
behavior_policy);
@@ -88,7 +89,7 @@ const char *remap_validate_filter_args(acl_filter_rule 
**rule_pp, const char *co
 unsigned long remap_check_option(const char *const *argv, int argc, unsigned 
long findmode = 0, int *_ret_idx = nullptr,
                                  const char **argptr = nullptr);
 
-bool remap_parse_config(const char *path, UrlRewrite *rewrite);
+bool remap_parse_config(const char *path, UrlRewrite *rewrite, ConfigContext 
ctx = {});
 
 using load_remap_file_func = void (*)(const char *, const char *);
 
diff --git a/include/proxy/http/remap/RemapYamlConfig.h 
b/include/proxy/http/remap/RemapYamlConfig.h
index a5ec4919c0..a93ee9d8b1 100644
--- a/include/proxy/http/remap/RemapYamlConfig.h
+++ b/include/proxy/http/remap/RemapYamlConfig.h
@@ -30,6 +30,7 @@
 #include <yaml-cpp/yaml.h>
 
 #include "swoc/Errata.h"
+#include "mgmt/config/ConfigContext.h"
 #include "proxy/http/remap/RemapConfig.h"
 #include "proxy/hdrs/URL.h"
 
@@ -68,6 +69,6 @@ swoc::Errata parse_yaml_include_directive(const std::string 
&include_path, BUILD
 swoc::Errata parse_yaml_remap_rule(const YAML::Node &node, BUILD_TABLE_INFO 
*bti);
 
 // Parse remap YAML node
-bool remap_parse_yaml_bti(const char *path, BUILD_TABLE_INFO *bti);
+bool remap_parse_yaml_bti(const char *path, BUILD_TABLE_INFO *bti, 
ConfigContext ctx = {});
 
-bool remap_parse_yaml(const char *path, UrlRewrite *rewrite);
+bool remap_parse_yaml(const char *path, UrlRewrite *rewrite, ConfigContext ctx 
= {});
diff --git a/include/proxy/http/remap/UrlRewrite.h 
b/include/proxy/http/remap/UrlRewrite.h
index 2f4be91b47..2ef85b9ed7 100644
--- a/include/proxy/http/remap/UrlRewrite.h
+++ b/include/proxy/http/remap/UrlRewrite.h
@@ -25,6 +25,7 @@
 #pragma once
 
 #include "iocore/eventsystem/Freer.h"
+#include "mgmt/config/ConfigContext.h"
 #include "proxy/http/remap/UrlMapping.h"
 #include "proxy/http/remap/UrlMappingPathIndex.h"
 #include "proxy/http/HttpTransact.h"
@@ -77,14 +78,15 @@ public:
    *
    * @return @c true if the instance state is valid, @c false if not.
    */
-  bool load();
+  bool load(ConfigContext ctx = {});
 
   /** Build the internal url write tables.
    *
    * @param path Path to configuration file.
+   * @param ctx  ConfigContext for reload status tracking.
    * @return 0 on success, non-zero error code on failure.
    */
-  int BuildTable(const char *path);
+  int BuildTable(const char *path, ConfigContext ctx = {});
 
   mapping_type Remap_redirect(HTTPHdr *request_header, URL *redirect_url);
   bool         ReverseMap(HTTPHdr *response_header);
diff --git a/include/records/YAMLConfigReloadTaskEncoder.h 
b/include/records/YAMLConfigReloadTaskEncoder.h
index 5aee5a7bca..a86b863ba9 100644
--- a/include/records/YAMLConfigReloadTaskEncoder.h
+++ b/include/records/YAMLConfigReloadTaskEncoder.h
@@ -54,8 +54,11 @@ template <> struct convert<ConfigReloadTask::Info> {
 
     node["logs"] = YAML::Node(YAML::NodeType::Sequence);
     // if no logs, it will be empty sequence.
-    for (const auto &log : info.logs) {
-      node["logs"].push_back(log);
+    for (const auto &entry : info.logs) {
+      YAML::Node log_node;
+      log_node["level"] = static_cast<int>(entry.level);
+      log_node["text"]  = entry.text;
+      node["logs"].push_back(log_node);
     }
 
     node["sub_tasks"] = YAML::Node(YAML::NodeType::Sequence);
diff --git a/src/iocore/cache/Cache.cc b/src/iocore/cache/Cache.cc
index f7e302daa9..69692d890d 100644
--- a/src/iocore/cache/Cache.cc
+++ b/src/iocore/cache/Cache.cc
@@ -31,6 +31,7 @@
 #include "Stripe.h"
 #include "StripeSM.h"
 #include "iocore/cache/Cache.h"
+#include "mgmt/config/ConfigContextDiags.h"
 #include "mgmt/config/ConfigRegistry.h"
 #include "tscore/Filenames.h"
 #include "tscore/InkErrno.h"
@@ -244,8 +245,8 @@ Cache::open_done()
           type  = ht->getType();
           cache = ht->getCache();
         }
-        ppt->reset(new CacheHostTable(cache, type));
-        ctx.complete("Finished loading");
+        ppt->reset(new CacheHostTable(cache, type, ctx));
+        CfgLoadComplete(ctx, "%s finished loading", ts::filename::HOSTING);
       },
       config::ConfigSource::FileOnly,           // no RPC content source. 
Legacy for now.
       {"proxy.config.cache.hosting_filename"}); // trigger records
diff --git a/src/iocore/cache/CacheHosting.cc b/src/iocore/cache/CacheHosting.cc
index 41df08db99..ce4f9a990b 100644
--- a/src/iocore/cache/CacheHosting.cc
+++ b/src/iocore/cache/CacheHosting.cc
@@ -24,6 +24,7 @@
 #include "P_CacheHosting.h"
 #include "Stripe.h"
 #include "iocore/cache/CacheDefs.h"
+#include "mgmt/config/ConfigContextDiags.h"
 #include "swoc/swoc_file.h"
 
 #include "tscore/HostLookup.h"
@@ -188,7 +189,7 @@ CacheHostMatcher::NewEntry(matcher_line *line_info)
  *   End class HostMatcher
  *************************************************************/
 
-CacheHostTable::CacheHostTable(Cache *c, CacheType typ)
+CacheHostTable::CacheHostTable(Cache *c, CacheType typ, ConfigContext ctx)
 {
   ats_scoped_str config_path;
 
@@ -199,7 +200,7 @@ CacheHostTable::CacheHostTable(Cache *c, CacheType typ)
   config_path = RecConfigReadConfigPath("proxy.config.cache.hosting_filename");
   ink_release_assert(config_path);
 
-  m_numEntries = this->BuildTable(config_path);
+  m_numEntries = this->BuildTable(config_path, ctx);
 }
 
 CacheHostTable::~CacheHostTable() {}
@@ -236,9 +237,9 @@ int fstat_wrapper(int fd, struct stat *s);
 //      from it
 //
 int
-CacheHostTable::BuildTableFromString(const char *config_file_path, char 
*file_buf)
+CacheHostTable::BuildTableFromString(const char *config_file_path, char 
*file_buf, ConfigContext ctx)
 {
-  Note("%s loading ...", ts::filename::HOSTING);
+  CfgLoadLog(ctx, DL_Note, "%s loading ...", ts::filename::HOSTING);
 
   // Table build locals
   Tokenizer      bufTok("\n");
@@ -260,7 +261,7 @@ CacheHostTable::BuildTableFromString(const char 
*config_file_path, char *file_bu
     /* no hosting customers -- put all the volumes in the
        generic table */
     if (gen_host_rec.Init(type)) {
-      Warning("Problems encountered while initializing the Generic Volume");
+      CfgLoadLog(ctx, DL_Warning, "Problems encountered while initializing the 
Generic Volume");
     }
     return 0;
   }
@@ -280,7 +281,7 @@ CacheHostTable::BuildTableFromString(const char 
*config_file_path, char *file_bu
       errPtr  = parseConfigLine(const_cast<char *>(tmp), current, 
&config_tags);
 
       if (errPtr != nullptr) {
-        Warning("%s discarding %s entry at line %d : %s", matcher_name, 
config_file_path, line_num, errPtr);
+        CfgLoadLog(ctx, DL_Warning, "%s discarding %s entry at line %d : %s", 
matcher_name, config_file_path, line_num, errPtr);
         ats_free(current);
       } else {
         // Line parsed ok.  Figure out what the destination
@@ -317,9 +318,9 @@ CacheHostTable::BuildTableFromString(const char 
*config_file_path, char *file_bu
        generic table */
 
     if (gen_host_rec.Init(type)) {
-      Warning("Problems encountered while initializing the Generic Volume");
+      CfgLoadLog(ctx, DL_Warning, "Problems encountered while initializing the 
Generic Volume");
     }
-    Note("%s finished loading", ts::filename::HOSTING);
+    CfgLoadLog(ctx, DL_Note, "%s finished loading", ts::filename::HOSTING);
     return 0;
   }
 
@@ -345,21 +346,22 @@ CacheHostTable::BuildTableFromString(const char 
*config_file_path, char *file_bu
         if (current->dest_entry < MATCHER_MAX_TOKENS) {
           current->line[0][current->dest_entry] = nullptr;
         } else {
-          Warning("Problems encountered while initializing the Generic 
Volume");
+          CfgLoadLog(ctx, DL_Warning, "Problems encountered while initializing 
the Generic Volume");
         }
 
         current->num_el--;
         if (!gen_host_rec.Init(current, type)) {
           generic_rec_initd = 1;
         } else {
-          Warning("Problems encountered while initializing the Generic 
Volume");
+          CfgLoadLog(ctx, DL_Warning, "Problems encountered while initializing 
the Generic Volume");
         }
 
       } else {
         hostMatch->NewEntry(current);
       }
     } else {
-      Warning("%s discarding %s entry with unknown type at line %d", 
matcher_name, config_file_path, current->line_num);
+      CfgLoadLog(ctx, DL_Warning, "%s discarding %s entry with unknown type at 
line %d", matcher_name, config_file_path,
+                 current->line_num);
     }
 
     // Deallocate the parsing structure
@@ -368,11 +370,12 @@ CacheHostTable::BuildTableFromString(const char 
*config_file_path, char *file_bu
     ats_free(last);
   }
 
-  Note("%s finished loading", ts::filename::HOSTING);
+  CfgLoadLog(ctx, DL_Note, "%s finished loading", ts::filename::HOSTING);
 
   if (!generic_rec_initd) {
     const char *cache_type = (type == CacheType::HTTP) ? "http" : "mixt";
-    Warning("No Volumes specified for Generic Hostnames for %s documents: %s 
cache will be disabled", cache_type, cache_type);
+    CfgLoadLog(ctx, DL_Warning, "No Volumes specified for Generic Hostnames 
for %s documents: %s cache will be disabled",
+               cache_type, cache_type);
   }
 
   ink_assert(second_pass == numEntries);
@@ -384,7 +387,7 @@ CacheHostTable::BuildTableFromString(const char 
*config_file_path, char *file_bu
 }
 
 int
-CacheHostTable::BuildTable(const char *config_file_path)
+CacheHostTable::BuildTable(const char *config_file_path, ConfigContext ctx)
 {
   std::error_code ec;
   std::string     content{swoc::file::load(swoc::file::path{config_file_path}, 
ec)};
@@ -392,16 +395,16 @@ CacheHostTable::BuildTable(const char *config_file_path)
   if (ec) {
     switch (ec.value()) {
     case ENOENT:
-      Warning("Cannot open the config file: %s - %s", config_file_path, 
strerror(ec.value()));
+      CfgLoadLog(ctx, DL_Warning, "Cannot open the config file: %s - %s", 
config_file_path, strerror(ec.value()));
       break;
     default:
-      Error("%s failed to load: %s", config_file_path, strerror(ec.value()));
+      CfgLoadFail(ctx, "%s failed to load: %s", config_file_path, 
strerror(ec.value()));
       gen_host_rec.Init(type);
       return 0;
     }
   }
 
-  return BuildTableFromString(config_file_path, content.data());
+  return BuildTableFromString(config_file_path, content.data(), ctx);
 }
 
 int
diff --git a/src/iocore/cache/P_CacheHosting.h 
b/src/iocore/cache/P_CacheHosting.h
index 69b104a5f1..3605ff95b6 100644
--- a/src/iocore/cache/P_CacheHosting.h
+++ b/src/iocore/cache/P_CacheHosting.h
@@ -23,6 +23,7 @@
 
 #pragma once
 #include "iocore/cache/CacheDefs.h"
+#include "mgmt/config/ConfigContext.h"
 #include "records/RecCore.h"
 #include "tscore/MatcherUtils.h"
 #include "tscore/HostLookup.h"
@@ -225,11 +226,11 @@ class CacheHostTable
 public:
   // Parameter name must not be deallocated before this
   //  object is
-  CacheHostTable(Cache *c, CacheType typ);
+  CacheHostTable(Cache *c, CacheType typ, ConfigContext ctx = {});
   ~CacheHostTable();
 
-  int BuildTable(const char *config_file_path);
-  int BuildTableFromString(const char *config_file_path, char *str);
+  int BuildTable(const char *config_file_path, ConfigContext ctx = {});
+  int BuildTableFromString(const char *config_file_path, char *str, 
ConfigContext ctx = {});
 
   void Match(std::string_view rdata, CacheHostResult *result) const;
   void Print() const;
diff --git a/src/iocore/dns/SplitDNS.cc b/src/iocore/dns/SplitDNS.cc
index d136161989..1302b587ac 100644
--- a/src/iocore/dns/SplitDNS.cc
+++ b/src/iocore/dns/SplitDNS.cc
@@ -31,6 +31,7 @@
 #include "P_SplitDNSProcessor.h"
 #include "tscore/Tokenizer.h"
 #include "tscore/Filenames.h"
+#include "mgmt/config/ConfigContextDiags.h"
 #include "mgmt/config/ConfigRegistry.h"
 
 #include <sys/types.h>
@@ -132,22 +133,23 @@ void
 SplitDNSConfig::reconfigure(ConfigContext ctx)
 {
   if (0 == gsplit_dns_enabled) {
-    ctx.complete("SplitDNS disabled, skipping reload");
+    CfgLoadComplete(ctx, "SplitDNS disabled, skipping reload");
     return;
   }
 
-  Note("%s loading ...", ts::filename::SPLITDNS);
+  CfgLoadLog(ctx, DL_Note, "%s loading ...", ts::filename::SPLITDNS);
 
   SplitDNS *params = new SplitDNS;
 
   params->m_SplitDNSlEnable = gsplit_dns_enabled;
-  params->m_DNSSrvrTable    = 
std::make_unique<DNS_table>("proxy.config.dns.splitdns.filename", modulePrefix, 
&sdns_dest_tags);
+  params->m_DNSSrvrTable    = std::make_unique<DNS_table>(
+    "proxy.config.dns.splitdns.filename", modulePrefix, &sdns_dest_tags,
+    ALLOW_HOST_TABLE | ALLOW_IP_TABLE | ALLOW_REGEX_TABLE | 
ALLOW_HOST_REGEX_TABLE | ALLOW_URL_TABLE, ctx);
 
   if (nullptr == params->m_DNSSrvrTable || (0 == 
params->m_DNSSrvrTable->getEntryCount())) {
-    Warning("Failed to load %s - No NAMEDs provided! Disabling SplitDNS", 
ts::filename::SPLITDNS);
     gsplit_dns_enabled = 0;
     delete params;
-    ctx.fail("No NAMEDs provided, disabling SplitDNS");
+    CfgLoadFail(ctx, "Failed to load %s - No NAMEDs provided! Disabling 
SplitDNS", ts::filename::SPLITDNS);
     return;
   }
   params->m_numEle = params->m_DNSSrvrTable->getEntryCount();
@@ -165,8 +167,7 @@ SplitDNSConfig::reconfigure(ConfigContext ctx)
     SplitDNSConfig::print();
   }
 
-  Note("%s finished loading", ts::filename::SPLITDNS);
-  ctx.complete("Finished loading");
+  CfgLoadComplete(ctx, "%s finished loading", ts::filename::SPLITDNS);
 }
 
 /* --------------------------------------------------------------
diff --git a/src/iocore/net/P_SSLConfig.h b/src/iocore/net/P_SSLConfig.h
index 6a6d5a24e4..62f3baf4a6 100644
--- a/src/iocore/net/P_SSLConfig.h
+++ b/src/iocore/net/P_SSLConfig.h
@@ -34,6 +34,7 @@
 #include "SSLSessionCache.h"
 #include "iocore/eventsystem/ConfigProcessor.h"
 #include "iocore/net/YamlSNIConfig.h"
+#include "mgmt/config/ConfigContext.h"
 
 #include <openssl/rand.h>
 #include <atomic>
@@ -155,7 +156,7 @@ struct SSLConfigParams : public ConfigInfo {
 
   void cleanupCTXTable();
 
-  void initialize();
+  void initialize(ConfigContext ctx = {});
   void cleanup();
   void reset();
   void SSLConfigInit(swoc::IPRangeSet *global);
@@ -212,7 +213,7 @@ struct SSLTicketParams : public ConfigInfo {
   ssl_ticket_key_block *default_global_keyblock = nullptr;
   time_t                load_time               = 0;
   char                 *ticket_key_filename     = nullptr;
-  bool                  LoadTicket(bool &nochange);
+  bool                  LoadTicket(bool &nochange, ConfigContext ctx = {});
   bool                  LoadTicketData(char *ticket_data, int ticket_data_len);
   void                  cleanup();
 
diff --git a/src/iocore/net/QUICMultiCertConfigLoader.cc 
b/src/iocore/net/QUICMultiCertConfigLoader.cc
index 8e5bbe1dca..2e7049643e 100644
--- a/src/iocore/net/QUICMultiCertConfigLoader.cc
+++ b/src/iocore/net/QUICMultiCertConfigLoader.cc
@@ -25,6 +25,7 @@
 #include "P_SSLConfig.h"
 #include "iocore/net/QUICMultiCertConfigLoader.h"
 #include "iocore/net/quic/QUICConfig.h"
+#include "mgmt/config/ConfigContextDiags.h"
 
 int QUICCertConfig::_config_id = 0;
 
@@ -44,6 +45,8 @@ QUICCertConfig::reconfigure(ConfigContext ctx)
   SSLConfig::scoped_config params;
   SSLCertLookup           *lookup = new SSLCertLookup();
 
+  CfgLoadInProgress(ctx, "(quic) %s loading ...", params->configFilePath);
+
   QUICMultiCertConfigLoader loader(params);
   auto                      errata = loader.load(lookup, _config_id == 0);
   if (!lookup->is_valid || (errata.has_severity() && errata.severity() >= 
ERRATA_ERROR)) {
@@ -57,19 +60,13 @@ QUICCertConfig::reconfigure(ConfigContext ctx)
   }
 
   if (!errata.empty()) {
-    errata.assign_annotation_glue_text("\n  ");
-    errata.assign_severity_glue_text(" -> \n  ");
-    bwprint(ts::bw_dbg, "\n{}", errata);
-  } else {
-    ts::bw_dbg = "";
+    ctx.log(errata);
   }
 
   if (retStatus) {
-    Note("(quic) %s finished loading%s", params->configFilePath, 
ts::bw_dbg.c_str());
-    ctx.complete("QUICCertConfig loaded");
+    CfgLoadComplete(ctx, "(quic) %s finished loading", params->configFilePath);
   } else {
-    Error("(quic) %s failed to load%s", params->configFilePath, 
ts::bw_dbg.c_str());
-    ctx.fail("QUICCertConfig failed to load");
+    CfgLoadFail(ctx, "(quic) %s failed to load", params->configFilePath);
   }
 }
 
diff --git a/src/iocore/net/SSLConfig.cc b/src/iocore/net/SSLConfig.cc
index 4480acef51..95ab59891f 100644
--- a/src/iocore/net/SSLConfig.cc
+++ b/src/iocore/net/SSLConfig.cc
@@ -43,6 +43,7 @@
 #include "tscore/Layout.h"
 #include "records/RecHttp.h"
 #include "records/RecCore.h"
+#include "mgmt/config/ConfigContextDiags.h"
 #include "mgmt/config/ConfigRegistry.h"
 
 #include <openssl/pem.h>
@@ -255,7 +256,7 @@ SSLConfigParams::SetServerPolicy(const char *verify_server)
 }
 
 void
-SSLConfigParams::initialize()
+SSLConfigParams::initialize(ConfigContext ctx)
 {
   cleanup();
 
@@ -564,7 +565,7 @@ SSLConfigParams::initialize()
   ssl_ktls_enabled = 
RecGetRecordInt("proxy.config.ssl.ktls.enabled").value_or(0);
 #ifndef SSL_OP_ENABLE_KTLS
   if (ssl_ktls_enabled) {
-    Error("kTLS configured but not supported by OpenSSL library");
+    CfgLoadLog(ctx, DL_Error, "kTLS configured but not supported by OpenSSL 
library");
   }
 #endif
 
@@ -584,6 +585,7 @@ SSLConfigParams::initialize()
     Emergency("Can't initialize the SSL client, HTTPS in remap rules will not 
function");
   } else {
     SSLError("Can't initialize the SSL client, HTTPS in remap rules will not 
function");
+    ctx.log(DL_Warning, "Can't initialize the SSL client, HTTPS in remap rules 
will not function");
   }
 }
 
@@ -626,13 +628,14 @@ SSLConfig::startup()
 void
 SSLConfig::reconfigure(ConfigContext ctx)
 {
-  Dbg(dbg_ctl_ssl_load, "Reload SSLConfig");
-  SSLConfigParams *params;
-  params = new SSLConfigParams;
+  CfgLoadInProgress(ctx, "SSLConfig loading ...");
+
+  SSLConfigParams *params = new SSLConfigParams;
   // start loading the next config
-  int loading_config_index        = get_loading_config_index();
+  int loading_config_index = get_loading_config_index();
+
   configids[loading_config_index] = 
configProcessor.set(configids[loading_config_index], params);
-  params->initialize(); // re-read configuration
+  params->initialize(ctx); // re-read configuration
   // Make the new config available for use.
   commit_config_id();
   ctx.complete("SSLConfig reloaded");
@@ -682,6 +685,8 @@ SSLCertificateConfig::reconfigure(ConfigContext ctx)
   SSLConfig::scoped_config params;
   SSLCertLookup           *lookup = new SSLCertLookup();
 
+  CfgLoadInProgress(ctx, "(ssl) %s loading ...", params->configFilePath);
+
   // Test SSL certificate loading startup. With large numbers of certificates, 
reloading can take time, so delay
   // twice the healthcheck period to simulate a loading a large certificate 
set.
   if (is_action_tag_set("test.multicert.delay")) {
@@ -704,19 +709,13 @@ SSLCertificateConfig::reconfigure(ConfigContext ctx)
   }
 
   if (!errata.empty()) {
-    errata.assign_annotation_glue_text("\n  ");
-    errata.assign_severity_glue_text(" -> \n  ");
-    bwprint(ts::bw_dbg, "\n{}", errata);
-  } else {
-    ts::bw_dbg = "";
+    ctx.log(errata);
   }
 
   if (retStatus) {
-    Note("(ssl) %s finished loading%s", params->configFilePath, 
ts::bw_dbg.c_str());
-    ctx.complete("SSLCertificateConfig loaded {}", ts::bw_dbg.c_str());
+    CfgLoadComplete(ctx, "(ssl) %s finished loading", params->configFilePath);
   } else {
-    Error("(ssl) %s failed to load%s", params->configFilePath, 
ts::bw_dbg.c_str());
-    ctx.fail("SSLCertificateConfig failed to load {}", ts::bw_dbg.c_str());
+    CfgLoadFail(ctx, "(ssl) %s failed to load", params->configFilePath);
   }
 
   return retStatus;
@@ -738,7 +737,7 @@ SSLCertificateConfig::release(SSLCertLookup *lookup)
 }
 
 bool
-SSLTicketParams::LoadTicket(bool &nochange)
+SSLTicketParams::LoadTicket(bool &nochange, ConfigContext ctx)
 {
   cleanup();
   nochange = true;
@@ -784,13 +783,13 @@ SSLTicketParams::LoadTicket(bool &nochange)
     return true;
   }
   if (!keyblock) {
-    Error("Could not load ticket key from %s", ticket_key_filename);
+    CfgLoadFail(ctx, "Could not load ticket key from %s", ticket_key_filename);
     return false;
   }
   default_global_keyblock = keyblock;
   load_time               = time(nullptr);
 
-  Dbg(dbg_ctl_ssl_load, "ticket key reloaded from %s", ticket_key_filename);
+  CfgLoadDbg(ctx, dbg_ctl_ssl_load, "ticket key reloaded from %s", 
ticket_key_filename);
 #endif
   return true;
 }
@@ -819,8 +818,7 @@ SSLTicketKeyConfig::startup()
 {
   
config::ConfigRegistry::Get_Instance().register_record_config("ssl_ticket_key", 
      // key
                                                                 
[](ConfigContext ctx) { // handler callback
-                                                                  // 
eventually ctx should be passed through to the reconfigure fn
-                                                                  // and the 
loaders so it can show more details.
+                                                                  
CfgLoadLog(ctx, DL_Note, "SSL ticket key loading ...");
                                                                   if 
(SSLTicketKeyConfig::reconfigure(ctx)) {
                                                                     
ctx.complete("SSL ticket key reloaded");
                                                                   } else {
@@ -836,13 +834,13 @@ SSLTicketKeyConfig::startup()
 }
 
 bool
-SSLTicketKeyConfig::reconfigure([[maybe_unused]] ConfigContext ctx)
+SSLTicketKeyConfig::reconfigure(ConfigContext ctx)
 {
   SSLTicketParams *ticketKey = new SSLTicketParams();
 
   if (ticketKey) {
     bool nochange = false;
-    if (!ticketKey->LoadTicket(nochange)) {
+    if (!ticketKey->LoadTicket(nochange, ctx)) {
       delete ticketKey;
       return false;
     }
diff --git a/src/iocore/net/SSLSNIConfig.cc b/src/iocore/net/SSLSNIConfig.cc
index 341b489f22..ce72e9d4ba 100644
--- a/src/iocore/net/SSLSNIConfig.cc
+++ b/src/iocore/net/SSLSNIConfig.cc
@@ -33,6 +33,7 @@
 #include "P_SSLConfig.h"
 #include "iocore/net/SSLSNIConfig.h"
 #include "iocore/net/SNIActionItem.h"
+#include "mgmt/config/ConfigContextDiags.h"
 #include "tscore/Diags.h"
 #include "tscore/Layout.h"
 #include "tscore/TSSystemState.h"
@@ -262,22 +263,20 @@ SNIConfigParams::get(std::string_view servername, 
in_port_t dest_incoming_port)
 }
 
 bool
-SNIConfigParams::initialize()
+SNIConfigParams::initialize(ConfigContext ctx)
 {
   std::string sni_filename = 
RecConfigReadConfigPath("proxy.config.ssl.servername.filename");
-  return initialize(sni_filename);
+  return initialize(sni_filename, ctx);
 }
 
 bool
-SNIConfigParams::initialize(std::string const &sni_filename)
+SNIConfigParams::initialize(std::string const &sni_filename, ConfigContext ctx)
 {
-  Note("%s loading ...", sni_filename.c_str());
+  CfgLoadInProgress(ctx, "%s loading ...", sni_filename.c_str());
 
   struct stat sbuf;
   if (stat(sni_filename.c_str(), &sbuf) == -1 && errno == ENOENT) {
-    Note("%s failed to load", sni_filename.c_str());
-    Warning("Loading SNI configuration - filename: %s doesn't exist", 
sni_filename.c_str());
-
+    CfgLoadLog(ctx, DL_Warning, "Loading SNI configuration - %s doesn't 
exist", sni_filename.c_str());
     return true;
   }
 
@@ -289,7 +288,7 @@ SNIConfigParams::initialize(std::string const &sni_filename)
     if (TSSystemState::is_initializing()) {
       Emergency("%s failed to load: %s", sni_filename.c_str(), 
errMsg.str().c_str());
     } else {
-      Error("%s failed to load: %s", sni_filename.c_str(), 
errMsg.str().c_str());
+      CfgLoadFail(ctx, "%s failed to load: %s", sni_filename.c_str(), 
errMsg.str().c_str());
     }
     return false;
   }
@@ -321,27 +320,20 @@ SNIConfig::startup()
 int
 SNIConfig::reconfigure(ConfigContext ctx)
 {
-  Dbg(dbg_ctl_ssl, "Reload SNI file");
-
   SNIConfigParams *params = new SNIConfigParams;
 
-  bool retStatus = params->initialize();
+  std::string sni_filename = 
RecConfigReadConfigPath("proxy.config.ssl.servername.filename");
+  bool        retStatus    = params->initialize(sni_filename, ctx);
+
   if (retStatus) {
     _configid = configProcessor.set(_configid, params);
     if (SNIConfig::on_reconfigure) {
       SNIConfig::on_reconfigure();
     }
+    CfgLoadComplete(ctx, "%s finished loading", sni_filename.c_str());
   } else {
     delete params;
-  }
-
-  std::string sni_filename = 
RecConfigReadConfigPath("proxy.config.ssl.servername.filename");
-  if (retStatus || TSSystemState::is_initializing()) {
-    Note("%s finished loading", sni_filename.c_str());
-    ctx.complete("Loading finished");
-  } else {
-    Error("%s failed to load", sni_filename.c_str());
-    ctx.fail("Failed to load");
+    CfgLoadFail(ctx, "%s failed to load", sni_filename.c_str());
   }
 
   return retStatus ? 1 : 0;
diff --git a/src/iocore/net/SSLUtils.cc b/src/iocore/net/SSLUtils.cc
index 6f230334e2..397ca0b0b8 100644
--- a/src/iocore/net/SSLUtils.cc
+++ b/src/iocore/net/SSLUtils.cc
@@ -1942,7 +1942,8 @@ SSLMultiCertConfigLoader::_load_items(SSLCertLookup 
*lookup, config::SSLMultiCer
     if (sslMultiCertSettings->cert || sslMultiCertSettings->opt == 
SSLCertContextOption::OPT_TUNNEL) {
       if (!this->_store_ssl_ctx(lookup, sslMultiCertSettings)) {
         std::lock_guard<std::mutex> lock(_loader_mutex);
-        errata.note(ERRATA_ERROR, "Failed to load certificate at item {}", 
item_num);
+        errata.note(ERRATA_ERROR, "Failed to load certificate '{}' at item {}",
+                    sslMultiCertSettings->cert ? sslMultiCertSettings->cert : 
"(unnamed)", item_num);
       }
     } else {
       std::lock_guard<std::mutex> lock(_loader_mutex);
diff --git a/src/iocore/net/quic/QUICConfig.cc 
b/src/iocore/net/quic/QUICConfig.cc
index 6790e25b20..9905ae0688 100644
--- a/src/iocore/net/quic/QUICConfig.cc
+++ b/src/iocore/net/quic/QUICConfig.cc
@@ -25,6 +25,7 @@
 
 #include <openssl/ssl.h>
 
+#include "mgmt/config/ConfigContextDiags.h"
 #include "records/RecHttp.h"
 
 #include "../P_SSLConfig.h"
@@ -57,7 +58,7 @@ quic_new_ssl_ctx()
    ALPN and SNI should be set to SSL object with NETVC_OPTIONS
  **/
 static shared_SSL_CTX
-quic_init_client_ssl_ctx(const QUICConfigParams *params)
+quic_init_client_ssl_ctx(const QUICConfigParams *params, [[maybe_unused]] 
ConfigContext ctx)
 {
   std::unique_ptr<SSL_CTX, decltype(&SSL_CTX_free)> ssl_ctx(nullptr, 
&SSL_CTX_free);
   ssl_ctx.reset(quic_new_ssl_ctx());
@@ -69,7 +70,7 @@ quic_init_client_ssl_ctx(const QUICConfigParams *params)
 #else
     if (SSL_CTX_set1_curves_list(ssl_ctx.get(), 
params->client_supported_groups()) != 1) {
 #endif
-      Error("SSL_CTX_set1_groups_list failed");
+      CfgLoadLog(ctx, DL_Error, "SSL_CTX_set1_groups_list failed");
     }
   }
 #endif
@@ -99,7 +100,7 @@ QUICConfigParams::~QUICConfigParams()
 };
 
 void
-QUICConfigParams::initialize()
+QUICConfigParams::initialize(ConfigContext ctx)
 {
   RecEstablishStaticConfigUInt32(this->_instance_id, 
"proxy.config.quic.instance_id");
   RecEstablishStaticConfigInt32(this->_connection_table_size, 
"proxy.config.quic.connection_table.size");
@@ -171,7 +172,7 @@ QUICConfigParams::initialize()
   RecEstablishStaticConfigUInt32(this->_disable_http_0_9, 
"proxy.config.quic.disable_http_0_9");
   RecEstablishStaticConfigUInt32(this->_cc_algorithm, 
"proxy.config.quic.cc_algorithm");
 
-  this->_client_ssl_ctx = quic_init_client_ssl_ctx(this);
+  this->_client_ssl_ctx = quic_init_client_ssl_ctx(this, ctx);
 }
 
 uint32_t
@@ -456,7 +457,7 @@ QUICConfig::reconfigure(ConfigContext ctx)
   QUICConfigParams *params;
   params = new QUICConfigParams;
   // re-read configuration
-  params->initialize();
+  params->initialize(ctx);
   _config_id = configProcessor.set(_config_id, params);
 
   QUICConnectionId::SCID_LEN = params->scid_len();
diff --git a/src/mgmt/config/ConfigContext.cc b/src/mgmt/config/ConfigContext.cc
index c23f3e2760..7dd81a58ca 100644
--- a/src/mgmt/config/ConfigContext.cc
+++ b/src/mgmt/config/ConfigContext.cc
@@ -63,7 +63,7 @@ ConfigContext::in_progress(std::string_view text)
   if (auto p = _task.lock()) {
     p->set_in_progress();
     if (!text.empty()) {
-      p->log(std::string{text});
+      p->log(DL_Note, std::string{text});
     }
   }
 }
@@ -76,13 +76,31 @@ ConfigContext::log(std::string_view text)
   }
 }
 
+void
+ConfigContext::log(DiagsLevel level, std::string_view text)
+{
+  if (auto p = _task.lock()) {
+    p->log(level, std::string{text});
+  }
+}
+
+void
+ConfigContext::log(swoc::Errata const &errata)
+{
+  if (auto p = _task.lock()) {
+    for (auto const &annotation : errata) {
+      p->log(static_cast<DiagsLevel>(static_cast<int>(annotation.severity())), 
std::string{annotation.text()});
+    }
+  }
+}
+
 void
 ConfigContext::complete(std::string_view text)
 {
   if (auto p = _task.lock()) {
     p->set_completed();
     if (!text.empty()) {
-      p->log(std::string{text});
+      p->log(DL_Note, std::string{text});
     }
   }
 }
@@ -93,7 +111,7 @@ ConfigContext::fail(std::string_view reason)
   if (auto p = _task.lock()) {
     p->set_failed();
     if (!reason.empty()) {
-      p->log(std::string{reason});
+      p->log(DL_Error, std::string{reason});
     }
   }
 }
@@ -105,11 +123,11 @@ ConfigContext::fail(swoc::Errata const &errata, 
std::string_view summary)
     p->set_failed();
     // Log the summary first
     if (!summary.empty()) {
-      p->log(std::string{summary});
+      p->log(DL_Error, std::string{summary});
     }
     // Log each error from the errata
     for (auto const &err : errata) {
-      p->log(std::string{err.text()});
+      p->log(static_cast<DiagsLevel>(static_cast<int>(err.severity())), 
std::string{err.text()});
     }
   }
 }
diff --git a/src/mgmt/config/ConfigReloadTrace.cc 
b/src/mgmt/config/ConfigReloadTrace.cc
index aa05843388..bf26a6489f 100644
--- a/src/mgmt/config/ConfigReloadTrace.cc
+++ b/src/mgmt/config/ConfigReloadTrace.cc
@@ -24,6 +24,7 @@
 #include "mgmt/config/ConfigReloadTrace.h"
 #include "mgmt/config/ConfigContext.h"
 #include "records/RecCore.h"
+#include "tsutil/ts_diag_levels.h"
 
 #include <algorithm>
 #include "tsutil/Metrics.h"
@@ -91,7 +92,15 @@ ConfigReloadTask &
 ConfigReloadTask::log(std::string const &text)
 {
   std::unique_lock<std::shared_mutex> lock(_mutex);
-  _info.logs.push_back(text);
+  _info.logs.push_back({DL_Undefined, text});
+  return *this;
+}
+
+ConfigReloadTask &
+ConfigReloadTask::log(DiagsLevel level, std::string const &text)
+{
+  std::unique_lock<std::shared_mutex> lock(_mutex);
+  _info.logs.push_back({level, text});
   return *this;
 }
 
@@ -163,7 +172,7 @@ ConfigReloadTask::mark_as_bad_state(std::string_view reason)
   _atomic_last_updated_ms.store(now_ms(), std::memory_order_release);
   if (!reason.empty()) {
     // Push directly to avoid deadlock (log() would try to acquire same mutex)
-    _info.logs.emplace_back(reason);
+    _info.logs.push_back({DL_Undefined, std::string{reason}});
   }
 }
 
@@ -287,6 +296,74 @@ ConfigReloadTask::aggregate_status()
   }
 }
 
+void
+ConfigReloadTask::log_reload_summary(State final_state)
+{
+  // Snapshot token and sub_tasks under the lock to avoid races with RPC 
readers
+  // that also consult _info via get_info()/get_state() on other threads.
+  std::string                      token;
+  std::vector<ConfigReloadTaskPtr> sub_tasks_snapshot;
+  {
+    std::unique_lock<std::shared_mutex> lock(_mutex);
+    if (!_info.main_task || !is_terminal(final_state) || _summary_logged) {
+      return;
+    }
+    _summary_logged    = true;
+    token              = _info.token;
+    sub_tasks_snapshot = _info.sub_tasks;
+  }
+
+  int success_count{0}, fail_count{0}, total{0};
+  for (const auto &sub : sub_tasks_snapshot) {
+    State st = sub->get_state();
+    if (st == State::SUCCESS) {
+      success_count++;
+    } else if (st == State::FAIL || st == State::TIMEOUT) {
+      fail_count++;
+    }
+    total++;
+  }
+
+  if (final_state == State::SUCCESS) {
+    Note("Config reload [%s] completed: %d/%d tasks succeeded", token.c_str(), 
success_count, total);
+  } else {
+    Warning("Config reload [%s] finished with failures: %d succeeded, %d 
failed (%d total) "
+            "— run: traffic_ctl config status -t %s",
+            token.c_str(), success_count, fail_count, total, token.c_str());
+  }
+
+  dump_subtask_tree(sub_tasks_snapshot, 2);
+}
+
+void
+ConfigReloadTask::dump_subtask_tree(const std::vector<ConfigReloadTaskPtr> 
&tasks, int indent)
+{
+  static constexpr int MAX_DEPTH = 10;
+  if (indent / 2 > MAX_DEPTH) {
+    return;
+  }
+  for (const auto &sub : tasks) {
+    auto sub_state = sub->get_state();
+    auto sub_info  = sub->get_info();
+    Dbg(dbg_ctl_config, "%*s[%.*s] %s", indent, "", 
static_cast<int>(state_to_string(sub_state).size()),
+        state_to_string(sub_state).data(), sub_info.description.c_str());
+    for (const auto &entry : sub_info.logs) {
+      if (entry.level != DL_Undefined) {
+        static constexpr const char *tags[] = {
+          "[Diag] ", "[Dbg] ", "[Stat] ", "[Note] ", "[Warn] ", "[Err] ", 
"[Fatal] ", "[Alert] ", "[Emrg] ",
+        };
+        int idx = std::min(std::max(static_cast<int>(entry.level), 0), 
static_cast<int>(DL_Emergency));
+        Dbg(dbg_ctl_config, "%*s  %s%s", indent, "", tags[idx], 
entry.text.c_str());
+      } else {
+        Dbg(dbg_ctl_config, "%*s  %s", indent, "", entry.text.c_str());
+      }
+    }
+    if (!sub_info.sub_tasks.empty()) {
+      dump_subtask_tree(sub_info.sub_tasks, indent + 2);
+    }
+  }
+}
+
 int64_t
 ConfigReloadTask::get_last_updated_time_ms() const
 {
@@ -336,6 +413,7 @@ ConfigReloadProgress::check_progress(int /* etype */, void 
* /* data */)
       // Confirmed terminal — safe to stop.
       Dbg(dbg_ctl_config, "Reload task %s confirmed %.*s after grace period, 
stopping progress check.",
           _reload->get_token().c_str(), static_cast<int>(state_str.size()), 
state_str.data());
+      _reload->log_reload_summary(current_state);
       return EVENT_DONE;
     }
     // First observation of terminal state — reschedule once more to confirm
diff --git a/src/proxy/CacheControl.cc b/src/proxy/CacheControl.cc
index ae0bc70e76..12fff11253 100644
--- a/src/proxy/CacheControl.cc
+++ b/src/proxy/CacheControl.cc
@@ -33,6 +33,7 @@
 #include "tscore/Filenames.h"
 #include "proxy/CacheControl.h"
 #include "proxy/ControlMatcher.h"
+#include "mgmt/config/ConfigContextDiags.h"
 #include "mgmt/config/ConfigRegistry.h"
 #include "proxy/http/HttpConfig.h"
 namespace
@@ -125,16 +126,16 @@ initCacheControl()
 void
 reloadCacheControl(ConfigContext ctx)
 {
-  Note("%s loading ...", ts::filename::CACHE);
-  CC_table *newTable;
-
+  CfgLoadLog(ctx, DL_Note, "%s loading ...", ts::filename::CACHE);
   Dbg(dbg_ctl_cache_control, "%s updated, reloading", ts::filename::CACHE);
+
   eventProcessor.schedule_in(new CC_FreerContinuation(CacheControlTable), 
CACHE_CONTROL_TIMEOUT, ET_CALL);
-  newTable = new CC_table("proxy.config.cache.control.filename", modulePrefix, 
&http_dest_tags);
+  CC_table *newTable =
+    new CC_table("proxy.config.cache.control.filename", modulePrefix, 
&http_dest_tags,
+                 ALLOW_HOST_TABLE | ALLOW_IP_TABLE | ALLOW_REGEX_TABLE | 
ALLOW_HOST_REGEX_TABLE | ALLOW_URL_TABLE, ctx);
   ink_atomic_swap(&CacheControlTable, newTable);
 
-  Note("%s finished loading", ts::filename::CACHE);
-  ctx.complete("Finished loading");
+  CfgLoadComplete(ctx, "%s finished loading", ts::filename::CACHE);
 }
 
 void
diff --git a/src/proxy/ControlMatcher.cc b/src/proxy/ControlMatcher.cc
index 8e6b6b7ee5..4be7cf1880 100644
--- a/src/proxy/ControlMatcher.cc
+++ b/src/proxy/ControlMatcher.cc
@@ -31,6 +31,7 @@
 #include "swoc/bwf_ip.h"
 #include "swoc/swoc_file.h"
 
+#include "mgmt/config/ConfigContextDiags.h"
 #include "tscore/MatcherUtils.h"
 #include "tscore/Tokenizer.h"
 #include "proxy/ControlMatcher.h"
@@ -639,7 +640,8 @@ IpMatcher<Data, MatchResult>::Print() const
 }
 
 template <class Data, class MatchResult>
-ControlMatcher<Data, MatchResult>::ControlMatcher(const char *file_var, const 
char *name, const matcher_tags *tags, int flags_in)
+ControlMatcher<Data, MatchResult>::ControlMatcher(const char *file_var, const 
char *name, const matcher_tags *tags, int flags_in,
+                                                  ConfigContext ctx)
 {
   flags = flags_in;
   ink_assert(flags & (ALLOW_HOST_TABLE | ALLOW_REGEX_TABLE | ALLOW_URL_TABLE | 
ALLOW_IP_TABLE));
@@ -664,7 +666,7 @@ ControlMatcher<Data, MatchResult>::ControlMatcher(const 
char *file_var, const ch
   hrMatch   = nullptr;
 
   if (!(flags & DONT_BUILD_TABLE)) {
-    m_numEntries = this->BuildTable();
+    m_numEntries = this->BuildTable(ctx);
   } else {
     m_numEntries = 0;
   }
@@ -731,7 +733,7 @@ ControlMatcher<Data, MatchResult>::Match(RequestData 
*rdata, MatchResult *result
 //
 template <class Data, class MatchResult>
 int
-ControlMatcher<Data, MatchResult>::BuildTableFromString(char *file_buf)
+ControlMatcher<Data, MatchResult>::BuildTableFromString(char *file_buf, 
ConfigContext ctx)
 {
   // Table build locals
   Tokenizer      bufTok("\n");
@@ -776,7 +778,7 @@ ControlMatcher<Data, 
MatchResult>::BuildTableFromString(char *file_buf)
         if (config_tags != &socks_server_tags) {
           Result error =
             Result::failure("%s discarding %s entry at line %d : %s", 
matcher_name, config_file_path, line_num, errptr);
-          Error("%s", error.message());
+          CfgLoadLog(ctx, DL_Error, "%s", error.message());
         }
         ats_free(current);
       } else {
@@ -874,7 +876,7 @@ ControlMatcher<Data, 
MatchResult>::BuildTableFromString(char *file_buf)
 
     // Check to see if there was an error in creating the NewEntry
     if (error.failed()) {
-      Error("%s", error.message());
+      CfgLoadLog(ctx, DL_Error, "%s", error.message());
     }
 
     // Deallocate the parsing structure
@@ -893,22 +895,23 @@ ControlMatcher<Data, 
MatchResult>::BuildTableFromString(char *file_buf)
 
 template <class Data, class MatchResult>
 int
-ControlMatcher<Data, MatchResult>::BuildTable()
+ControlMatcher<Data, MatchResult>::BuildTable(ConfigContext ctx)
 {
   std::error_code ec;
   std::string     content{swoc::file::load(swoc::file::path{config_file_path}, 
ec)};
+
   if (ec) {
     switch (ec.value()) {
     case ENOENT:
-      Warning("ControlMatcher - Cannot open config file: %s - %s", 
config_file_path, strerror(ec.value()));
+      CfgLoadLog(ctx, DL_Warning, "ControlMatcher - Cannot open config file: 
%s - %s", config_file_path, strerror(ec.value()));
       break;
     default:
-      Error("ControlMatcher - %s failed to load: %s", config_file_path, 
strerror(ec.value()));
+      CfgLoadFail(ctx, "ControlMatcher - %s failed to load: %s", 
config_file_path, strerror(ec.value()));
       return 1;
     }
   }
 
-  return BuildTableFromString(content.data());
+  return BuildTableFromString(content.data(), ctx);
 }
 
 /****************************************************************
diff --git a/src/proxy/IPAllow.cc b/src/proxy/IPAllow.cc
index 0cbca7100b..6659fb2b69 100644
--- a/src/proxy/IPAllow.cc
+++ b/src/proxy/IPAllow.cc
@@ -27,6 +27,7 @@
 #include <string_view>
 
 #include "proxy/IPAllow.h"
+#include "mgmt/config/ConfigContextDiags.h"
 #include "mgmt/config/ConfigRegistry.h"
 #include "records/RecCore.h"
 #include "swoc/Errata.h"
@@ -123,37 +124,28 @@ IpAllow::startup()
 void
 IpAllow::reconfigure(ConfigContext ctx)
 {
-  self_type  *new_table;
-  std::string text;
-  std::string errata_text;
-  Note("%s loading ...", ts::filename::IP_ALLOW);
-  ctx.in_progress();
+  CfgLoadLog(ctx, DL_Note, "%s loading ...", ts::filename::IP_ALLOW);
+
+  auto *new_table = new self_type("proxy.config.cache.ip_allow.filename", 
"proxy.config.cache.ip_categories.filename");
 
-  new_table = new self_type("proxy.config.cache.ip_allow.filename", 
"proxy.config.cache.ip_categories.filename");
   // IP rules need categories, so load them first (if they exist).
   if (auto errata = new_table->BuildCategories(); !errata.is_ok()) {
-    swoc::bwprint(text, "{} failed to load", 
new_table->ip_categories_config_file);
-    swoc::bwprint(errata_text, "{}", errata);
-    Error("%s\n%s", text.c_str(), errata_text.c_str());
-    ctx.fail(errata, "{} failed to load", 
new_table->ip_categories_config_file);
+    CfgLoadFailWithErrata(ctx, errata, "%s failed to load", 
new_table->ip_categories_config_file.c_str());
     delete new_table;
     return;
   }
   if (auto errata = new_table->BuildTable(); !errata.is_ok()) {
-    swoc::bwprint(text, "{} failed to load", ts::filename::IP_ALLOW);
-    swoc::bwprint(errata_text, "{}", errata);
-    if (errata.severity() <= ERRATA_ERROR) {
-      Error("%s\n%s", text.c_str(), errata_text.c_str());
-    } else {
-      Fatal("%s\n%s", text.c_str(), errata_text.c_str());
+    if (errata.severity() > ERRATA_ERROR) {
+      std::string errata_text;
+      swoc::bwprint(errata_text, "{}", errata);
+      Fatal("%s failed to load\n%s", ts::filename::IP_ALLOW, 
errata_text.c_str());
     }
-    ctx.fail(errata, "{} failed to load", ts::filename::IP_ALLOW);
+    CfgLoadFailWithErrata(ctx, errata, "%s failed to load", 
ts::filename::IP_ALLOW);
     delete new_table;
     return;
   }
   configid = configProcessor.set(configid, new_table);
-  Note("%s finished loading", ts::filename::IP_ALLOW);
-  ctx.complete("Finished loading");
+  CfgLoadComplete(ctx, "%s finished loading", ts::filename::IP_ALLOW);
 }
 
 IpAllow *
diff --git a/src/proxy/ParentSelection.cc b/src/proxy/ParentSelection.cc
index c18f44a212..25a246bc64 100644
--- a/src/proxy/ParentSelection.cc
+++ b/src/proxy/ParentSelection.cc
@@ -24,6 +24,7 @@
 #include "proxy/ParentConsistentHash.h"
 #include "proxy/ParentRoundRobin.h"
 #include "proxy/ControlMatcher.h"
+#include "mgmt/config/ConfigContextDiags.h"
 #include "mgmt/config/ConfigRegistry.h"
 #include "proxy/HostStatus.h"
 #include "proxy/hdrs/HTTP.h"
@@ -303,14 +304,13 @@ ParentConfig::startup()
 void
 ParentConfig::reconfigure(ConfigContext ctx)
 {
-  Note("%s loading ...", ts::filename::PARENT);
-
-  ParentConfigParams *params = nullptr;
+  CfgLoadLog(ctx, DL_Note, "%s loading ...", ts::filename::PARENT);
 
   // Allocate parent table
-  P_table *pTable = new P_table(file_var, modulePrefix, &http_dest_tags);
-
-  params = new ParentConfigParams(pTable);
+  P_table *pTable =
+    new P_table(file_var, modulePrefix, &http_dest_tags,
+                ALLOW_HOST_TABLE | ALLOW_IP_TABLE | ALLOW_REGEX_TABLE | 
ALLOW_HOST_REGEX_TABLE | ALLOW_URL_TABLE, ctx);
+  ParentConfigParams *params = new ParentConfigParams(pTable);
   ink_assert(params != nullptr);
 
   m_id = configProcessor.set(m_id, params);
@@ -319,8 +319,7 @@ ParentConfig::reconfigure(ConfigContext ctx)
     ParentConfig::print();
   }
 
-  Note("%s finished loading", ts::filename::PARENT);
-  ctx.complete("Finished loading");
+  CfgLoadComplete(ctx, "%s finished loading", ts::filename::PARENT);
 }
 
 void
diff --git a/src/proxy/ReverseProxy.cc b/src/proxy/ReverseProxy.cc
index a7add1f796..893f421d7c 100644
--- a/src/proxy/ReverseProxy.cc
+++ b/src/proxy/ReverseProxy.cc
@@ -32,6 +32,7 @@
 #include <dlfcn.h>
 #include "iocore/cache/Cache.h"
 #include "proxy/ReverseProxy.h"
+#include "mgmt/config/ConfigContextDiags.h"
 #include "mgmt/config/ConfigRegistry.h"
 #include "tscore/MatcherUtils.h"
 #include "tscore/Tokenizer.h"
@@ -159,16 +160,17 @@ bool
 reloadUrlRewrite(ConfigContext ctx)
 {
   std::string msg_buffer;
+
   msg_buffer.reserve(1024);
   UrlRewrite *newTable, *oldTable;
 
-  Note("%s loading (checking first) ...", ts::filename::REMAP_YAML);
-  Note("%s loading ...", ts::filename::REMAP);
+  CfgLoadLog(ctx, DL_Note, "%s loading (checking first) ...", 
ts::filename::REMAP_YAML);
+  CfgLoadLog(ctx, DL_Note, "%s loading ...", ts::filename::REMAP);
   Dbg(dbg_ctl_url_rewrite, "%s updated, reloading...", 
ts::filename::REMAP_YAML);
   Dbg(dbg_ctl_url_rewrite, "%s updated, reloading...", ts::filename::REMAP);
   newTable = new UrlRewrite();
 
-  bool status  = newTable->load();
+  bool status  = newTable->load(ctx);
   bool is_yaml = (newTable->is_remap_yaml());
 
   if (status) {
@@ -186,16 +188,14 @@ reloadUrlRewrite(ConfigContext ctx)
     oldTable->release();
 
     Dbg(dbg_ctl_url_rewrite, "%s", msg_buffer.c_str());
-    Note("%s", msg_buffer.c_str());
-    ctx.complete(msg_buffer);
+    CfgLoadComplete(ctx, "%s finished loading", is_yaml ? 
ts::filename::REMAP_YAML : ts::filename::REMAP);
     return true;
   } else {
     swoc::bwprint(msg_buffer, "{} failed to load", is_yaml ? 
ts::filename::REMAP_YAML : ts::filename::REMAP);
 
     delete newTable;
     Dbg(dbg_ctl_url_rewrite, "%s", msg_buffer.c_str());
-    Error("%s", msg_buffer.c_str());
-    ctx.fail(msg_buffer);
+    CfgLoadFail(ctx, "%s failed to load", is_yaml ? ts::filename::REMAP_YAML : 
ts::filename::REMAP);
     return false;
   }
 }
diff --git a/src/proxy/http/PreWarmConfig.cc b/src/proxy/http/PreWarmConfig.cc
index 4adab6e212..dbf79ce102 100644
--- a/src/proxy/http/PreWarmConfig.cc
+++ b/src/proxy/http/PreWarmConfig.cc
@@ -58,7 +58,7 @@ PreWarmConfig::reconfigure(ConfigContext ctx)
   _config_id                  = configProcessor.set(_config_id, params);
 
   prewarmManager.reconfigure();
-  ctx.complete("PreWarm config published.");
+  ctx.complete("PreWarm config published");
 }
 
 PreWarmConfigParams *
diff --git a/src/proxy/http/remap/RemapConfig.cc 
b/src/proxy/http/remap/RemapConfig.cc
index bbefa27c68..bb7bc915f5 100644
--- a/src/proxy/http/remap/RemapConfig.cc
+++ b/src/proxy/http/remap/RemapConfig.cc
@@ -24,6 +24,7 @@
 #include "proxy/http/remap/AclFiltering.h"
 #include "swoc/swoc_file.h"
 
+#include "mgmt/config/ConfigContextDiags.h"
 #include "proxy/http/remap/RemapConfig.h"
 #include "proxy/http/remap/UrlRewrite.h"
 #include "proxy/ReverseProxy.h"
@@ -1070,7 +1071,7 @@ lFail:
 }
 
 bool
-remap_parse_config_bti(const char *path, BUILD_TABLE_INFO *bti)
+remap_parse_config_bti(const char *path, BUILD_TABLE_INFO *bti, ConfigContext 
ctx)
 {
   char        errBuf[1024];
   char        errStrBuf[1024];
@@ -1109,7 +1110,7 @@ remap_parse_config_bti(const char *path, BUILD_TABLE_INFO 
*bti)
     return true;
   }
   if (ec.value()) {
-    Warning("Failed to open remapping configuration file %s - %s", path, 
strerror(ec.value()));
+    CfgLoadLog(ctx, DL_Warning, "Failed to open remapping configuration file 
%s - %s", path, strerror(ec.value()));
     return false;
   }
 
@@ -1117,7 +1118,7 @@ remap_parse_config_bti(const char *path, BUILD_TABLE_INFO 
*bti)
 
   ACLBehaviorPolicy behavior_policy = ACLBehaviorPolicy::ACL_BEHAVIOR_LEGACY;
   if (!UrlRewrite::get_acl_behavior_policy(behavior_policy)) {
-    Warning("Failed to get ACL matching policy.");
+    CfgLoadLog(ctx, DL_Warning, "Failed to get ACL matching policy.");
     return false;
   }
   bti->behavior_policy = behavior_policy;
@@ -1538,7 +1539,7 @@ remap_parse_config_bti(const char *path, BUILD_TABLE_INFO 
*bti)
   MAP_ERROR:
 
     snprintf(errBuf, sizeof(errBuf), "%s failed to add remap rule at %s line 
%d: %s", modulePrefix, path, cln + 1, errStr);
-    Error("%s", errBuf);
+    CfgLoadLog(ctx, DL_Error, "%s", errBuf);
 
     delete reg_map;
     delete new_mapping;
@@ -1550,7 +1551,7 @@ remap_parse_config_bti(const char *path, BUILD_TABLE_INFO 
*bti)
 }
 
 bool
-remap_parse_config(const char *path, UrlRewrite *rewrite)
+remap_parse_config(const char *path, UrlRewrite *rewrite, ConfigContext ctx)
 {
   BUILD_TABLE_INFO bti;
 
@@ -1559,7 +1560,7 @@ remap_parse_config(const char *path, UrlRewrite *rewrite)
   rewrite->pluginFactory.indicatePreReload();
 
   bti.rewrite = rewrite;
-  bool status = remap_parse_config_bti(path, &bti);
+  bool status = remap_parse_config_bti(path, &bti, ctx);
 
   /* Now after we parsed the configuration and (re)loaded plugins and plugin 
instances
    * accordingly notify all plugins that we are done */
diff --git a/src/proxy/http/remap/RemapYamlConfig.cc 
b/src/proxy/http/remap/RemapYamlConfig.cc
index 04070030e7..7a44143446 100644
--- a/src/proxy/http/remap/RemapYamlConfig.cc
+++ b/src/proxy/http/remap/RemapYamlConfig.cc
@@ -23,6 +23,8 @@
 
 #include "proxy/http/remap/RemapYamlConfig.h"
 
+#include "mgmt/config/ConfigContextDiags.h"
+
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <dirent.h>
@@ -962,7 +964,7 @@ MAP_ERROR:
 }
 
 bool
-remap_parse_yaml_bti(const char *path, BUILD_TABLE_INFO *bti)
+remap_parse_yaml_bti(const char *path, BUILD_TABLE_INFO *bti, ConfigContext 
ctx)
 {
   try {
     Dbg(dbg_ctl_remap_yaml, "Parsing YAML config file: %s", path);
@@ -978,7 +980,7 @@ remap_parse_yaml_bti(const char *path, BUILD_TABLE_INFO 
*bti)
 
     ACLBehaviorPolicy behavior_policy = ACLBehaviorPolicy::ACL_BEHAVIOR_LEGACY;
     if (!UrlRewrite::get_acl_behavior_policy(behavior_policy)) {
-      Warning("Failed to get ACL matching policy.");
+      CfgLoadLog(ctx, DL_Warning, "Failed to get ACL matching policy.");
       return false;
     }
     bti->behavior_policy = behavior_policy;
@@ -988,14 +990,14 @@ remap_parse_yaml_bti(const char *path, BUILD_TABLE_INFO 
*bti)
       for (const auto &filter_def : config["acl_filters"]) {
         auto errata = parse_yaml_define_directive(filter_def, bti);
         if (!errata.is_ok()) {
-          Error("Failed to parse acl_filters section");
+          CfgLoadLog(ctx, DL_Error, "Failed to parse acl_filters section");
           return false;
         }
       }
     }
 
     if (config["remap"].IsNull() || !config["remap"].IsSequence()) {
-      Error("Expected toplevel 'remap' key to be a sequence");
+      CfgLoadLog(ctx, DL_Error, "Expected toplevel 'remap' key to be a 
sequence");
       return false;
     }
 
@@ -1006,7 +1008,7 @@ remap_parse_yaml_bti(const char *path, BUILD_TABLE_INFO 
*bti)
 
       auto errata = parse_yaml_remap_rule(rule, bti);
       if (!errata.is_ok()) {
-        Error("Failed to parse remap rule");
+        CfgLoadLog(ctx, DL_Error, "Failed to parse remap rule");
         return false;
       }
     }
@@ -1017,15 +1019,15 @@ remap_parse_yaml_bti(const char *path, BUILD_TABLE_INFO 
*bti)
     return true;
 
   } catch (YAML::Exception &ex) {
-    Error("YAML parsing error in %s: %s", path, ex.what());
+    CfgLoadLog(ctx, DL_Error, "YAML parsing error in %s: %s", path, ex.what());
   } catch (std::exception &ex) {
-    Error("Exception parsing YAML config %s: %s", path, ex.what());
+    CfgLoadLog(ctx, DL_Error, "Exception parsing YAML config %s: %s", path, 
ex.what());
   }
   return false;
 }
 
 bool
-remap_parse_yaml(const char *path, UrlRewrite *rewrite)
+remap_parse_yaml(const char *path, UrlRewrite *rewrite, ConfigContext ctx)
 {
   BUILD_TABLE_INFO bti;
 
@@ -1034,7 +1036,7 @@ remap_parse_yaml(const char *path, UrlRewrite *rewrite)
   rewrite->pluginFactory.indicatePreReload();
 
   bti.rewrite = rewrite;
-  bool status = remap_parse_yaml_bti(path, &bti);
+  bool status = remap_parse_yaml_bti(path, &bti, ctx);
 
   /* Now after we parsed the configuration and (re)loaded plugins and plugin 
instances
    * accordingly notify all plugins that we are done */
diff --git a/src/proxy/http/remap/UrlRewrite.cc 
b/src/proxy/http/remap/UrlRewrite.cc
index aa49bfde34..fbda217462 100644
--- a/src/proxy/http/remap/UrlRewrite.cc
+++ b/src/proxy/http/remap/UrlRewrite.cc
@@ -25,6 +25,7 @@
 #include "proxy/http/remap/UrlRewrite.h"
 #include "proxy/http/remap/RemapYamlConfig.h"
 #include "iocore/eventsystem/ConfigProcessor.h"
+#include "mgmt/config/ConfigContextDiags.h"
 #include "proxy/ReverseProxy.h"
 #include "tscore/Layout.h"
 #include "tscore/Filenames.h"
@@ -76,7 +77,7 @@ UrlRewrite::get_acl_behavior_policy(ACLBehaviorPolicy &policy)
 }
 
 bool
-UrlRewrite::load()
+UrlRewrite::load(ConfigContext ctx)
 {
   ats_scoped_str config_file_path;
 
@@ -85,13 +86,12 @@ UrlRewrite::load()
   this->_remap_yaml = true;
 
   if (!config_file_path || 
!swoc::file::exists(swoc::file::path(config_file_path))) {
-    Dbg(dbg_ctl_url_rewrite, "%s failed to load, fall back to %s", 
ts::filename::REMAP_YAML, ts::filename::REMAP);
-    Note("%s failed to load, fall back to %s", ts::filename::REMAP_YAML, 
ts::filename::REMAP);
+    CfgLoadLog(ctx, DL_Note, "%s failed to load, fall back to %s", 
ts::filename::REMAP_YAML, ts::filename::REMAP);
     // Fall back to remap.config if remap.yaml not found
     this->_remap_yaml = false;
     config_file_path  = 
RecConfigReadConfigPath("proxy.config.url_remap.filename", ts::filename::REMAP);
     if (!config_file_path) {
-      Warning("%s Unable to locate %s. No remappings in effect", modulePrefix, 
ts::filename::REMAP);
+      CfgLoadLog(ctx, DL_Warning, "%s Unable to locate %s. No remappings in 
effect", modulePrefix, ts::filename::REMAP);
       return false;
     }
   }
@@ -101,7 +101,7 @@ UrlRewrite::load()
     this->ts_name = ats_stringdup(rec_str);
   }
   if (this->ts_name == nullptr) {
-    Warning("%s Unable to determine proxy name.  Incorrect redirects could be 
generated", modulePrefix);
+    CfgLoadLog(ctx, DL_Warning, "%s Unable to determine proxy name.  Incorrect 
redirects could be generated", modulePrefix);
     this->ts_name = ats_strdup("");
   }
 
@@ -110,7 +110,7 @@ UrlRewrite::load()
     this->http_default_redirect_url = ats_stringdup(rec_str);
   }
   if (this->http_default_redirect_url == nullptr) {
-    Warning("%s Unable to determine default redirect url for \"referer\" 
filter.", modulePrefix);
+    CfgLoadLog(ctx, DL_Warning, "%s Unable to determine default redirect url 
for \"referer\" filter.", modulePrefix);
     this->http_default_redirect_url = ats_strdup("http://www.apache.org";);
   }
 
@@ -130,7 +130,7 @@ UrlRewrite::load()
     fs::file_status status       = fs::status(compilerPath, ec);
 
     if (ec || !swoc::file::is_regular_file(status)) {
-      Error("Configured plugin compiler path '%s' is not a regular file", buf);
+      CfgLoadFail(ctx, "Configured plugin compiler path '%s' is not a regular 
file", buf);
       return false;
     } else {
       // This also adds the configuration directory (etc/trafficserver) to 
find Cripts etc.
@@ -143,7 +143,7 @@ UrlRewrite::load()
   Dbg(dbg_ctl_url_rewrite_regex, "strategyFactory file: %s", sf.c_str());
   strategyFactory = new NextHopStrategyFactory(sf.c_str());
 
-  if (TS_SUCCESS == this->BuildTable(config_file_path)) {
+  if (TS_SUCCESS == this->BuildTable(config_file_path, ctx)) {
     int n_rules = this->rule_count(); // Minimum # of rules to be considered a 
valid configuration.
     int required_rules;
     required_rules = 
RecGetRecordInt("proxy.config.url_remap.min_rules_required").value_or(0);
@@ -154,10 +154,11 @@ UrlRewrite::load()
         Print();
       }
     } else {
-      Warning("%s %d rules defined but %d rules required, configuration is 
invalid.", modulePrefix, n_rules, required_rules);
+      CfgLoadLog(ctx, DL_Warning, "%s %d rules defined but %d rules required, 
configuration is invalid.", modulePrefix, n_rules,
+                 required_rules);
     }
   } else {
-    Warning("something failed during BuildTable() -- check your remap 
plugins!");
+    CfgLoadLog(ctx, DL_Warning, "something failed during BuildTable() -- check 
your remap plugins!");
   }
 
   // ACL Matching Policy
@@ -816,7 +817,7 @@ UrlRewrite::InsertForwardMapping(mapping_type maptype, 
url_mapping *mapping, con
 
 */
 int
-UrlRewrite::BuildTable(const char *path)
+UrlRewrite::BuildTable(const char *path, ConfigContext ctx)
 {
   ink_assert(forward_mappings.empty());
   ink_assert(reverse_mappings.empty());
@@ -837,9 +838,9 @@ UrlRewrite::BuildTable(const char *path)
 
   bool parse_success;
   if (is_remap_yaml()) {
-    parse_success = remap_parse_yaml(path, this);
+    parse_success = remap_parse_yaml(path, this, ctx);
   } else {
-    parse_success = remap_parse_config(path, this);
+    parse_success = remap_parse_config(path, this, ctx);
   }
 
   if (!parse_success) {
diff --git a/src/proxy/logging/LogConfig.cc b/src/proxy/logging/LogConfig.cc
index 9af71a7e40..0aa16221db 100644
--- a/src/proxy/logging/LogConfig.cc
+++ b/src/proxy/logging/LogConfig.cc
@@ -48,6 +48,7 @@ using namespace std::literals;
 #include "tscore/SimpleTokenizer.h"
 
 #include "proxy/logging/YamlLogConfig.h"
+#include "mgmt/config/ConfigContextDiags.h"
 #include "mgmt/config/ConfigRegistry.h"
 
 #define DISK_IS_CONFIG_FULL_MESSAGE                    \
@@ -422,7 +423,7 @@ LogConfig::setup_log_objects()
 void
 LogConfig::reconfigure(ConfigContext ctx)
 {
-  Dbg(dbg_ctl_log_config, "Reconfiguration request accepted");
+  CfgLoadDbg(ctx, dbg_ctl_log_config, "Reconfiguration request accepted");
 
   Log::config->reconfiguration_needed = true;
   Log::config->reload_ctx             = ctx;
@@ -774,8 +775,8 @@ LogConfig::evaluate_config()
   ats_scoped_str 
path(RecConfigReadConfigPath("proxy.config.log.config.filename", 
ts::filename::LOGGING));
   struct stat    sbuf;
   if (stat(path.get(), &sbuf) == -1 && errno == ENOENT) {
-    Warning("logging configuration '%s' doesn't exist", path.get());
-    reload_ctx.fail("logging configuration '{}' doesn't exist", path.get());
+    // File doesn't exist — not a failure; ATS uses default logging.
+    CfgLoadComplete(reload_ctx, "logging configuration '%s' doesn't exist, 
using defaults", path.get());
     return false;
   }
 
@@ -784,11 +785,9 @@ LogConfig::evaluate_config()
 
   bool zret = y.parse(path.get());
   if (zret) {
-    Note("%s finished loading", path.get());
-    reload_ctx.complete("{} finished loading", path.get());
+    CfgLoadComplete(reload_ctx, "%s finished loading", path.get());
   } else {
-    Note("%s failed to load", path.get());
-    reload_ctx.fail("{} failed to load", path.get());
+    CfgLoadFail(reload_ctx, "%s failed to load", path.get());
   }
 
   return zret;
diff --git a/src/proxy/logging/YamlLogConfig.cc 
b/src/proxy/logging/YamlLogConfig.cc
index e554e8f966..ee88b2f724 100644
--- a/src/proxy/logging/YamlLogConfig.cc
+++ b/src/proxy/logging/YamlLogConfig.cc
@@ -22,6 +22,7 @@
 #include "proxy/logging/YamlLogConfig.h"
 #include "proxy/logging/YamlLogConfigDecoders.h"
 
+#include "mgmt/config/ConfigContextDiags.h"
 #include "proxy/logging/LogConfig.h"
 #include "proxy/logging/LogObject.h"
 
@@ -45,7 +46,7 @@ YamlLogConfig::parse(const char *cfgFilename)
   try {
     result = loadLogConfig(cfgFilename);
   } catch (std::exception &ex) {
-    Error("%s", ex.what());
+    CfgLoadLog(cfg->reload_ctx, DL_Error, "%s", ex.what());
     result = false;
   }
   return result;
@@ -61,14 +62,14 @@ YamlLogConfig::loadLogConfig(const char *cfgFilename)
   }
 
   if (!config.IsMap()) {
-    Error("malformed %s file; expected a map", cfgFilename);
+    CfgLoadLog(cfg->reload_ctx, DL_Error, "malformed %s file; expected a map", 
cfgFilename);
     return false;
   }
 
   if (config["logging"]) {
     config = config["logging"];
   } else {
-    Error("malformed %s file; expected a toplevel 'logging' node", 
cfgFilename);
+    CfgLoadLog(cfg->reload_ctx, DL_Error, "malformed %s file; expected a 
toplevel 'logging' node", cfgFilename);
     return false;
   }
 
diff --git a/src/records/unit_tests/test_ConfigReloadTask.cc 
b/src/records/unit_tests/test_ConfigReloadTask.cc
index 9fa1704b31..9cc1acdb10 100644
--- a/src/records/unit_tests/test_ConfigReloadTask.cc
+++ b/src/records/unit_tests/test_ConfigReloadTask.cc
@@ -92,7 +92,7 @@ TEST_CASE("ConfigReloadTask state transitions", 
"[config][reload][state]")
     // Verify logs contain the reason
     auto logs = task->get_logs();
     REQUIRE(!logs.empty());
-    REQUIRE(logs.back().find("Test timeout") != std::string::npos);
+    REQUIRE(logs.back().text.find("Test timeout") != std::string::npos);
   }
 
   SECTION("Terminal states cannot be changed via mark_as_bad_state")
@@ -109,8 +109,8 @@ TEST_CASE("ConfigReloadTask state transitions", 
"[config][reload][state]")
 
     // Verify the rejected reason was NOT added to logs
     auto logs = task->get_logs();
-    for (const auto &log : logs) {
-      REQUIRE(log.find("Should not apply") == std::string::npos);
+    for (const auto &entry : logs) {
+      REQUIRE(entry.text.find("Should not apply") == std::string::npos);
     }
   }
 
diff --git a/src/traffic_ctl/CtrlCommands.cc b/src/traffic_ctl/CtrlCommands.cc
index 21700b3154..03dee5075a 100644
--- a/src/traffic_ctl/CtrlCommands.cc
+++ b/src/traffic_ctl/CtrlCommands.cc
@@ -20,6 +20,8 @@
  */
 #include "CtrlCommands.h"
 
+#include <algorithm>
+#include <cctype>
 #include <fstream>
 #include <unordered_map>
 #include <chrono>
@@ -257,8 +259,9 @@ ConfigCommand::config_diff()
 void
 ConfigCommand::config_status()
 {
-  std::string token = get_parsed_arguments()->get("token").value();
-  std::string count = get_parsed_arguments()->get("count").value();
+  std::string token     = get_parsed_arguments()->get("token").value();
+  std::string count     = get_parsed_arguments()->get("count").value();
+  std::string min_level = get_parsed_arguments()->get("min-level").value();
 
   if (!count.empty() && !token.empty()) {
     // can't use both.
@@ -268,6 +271,27 @@ ConfigCommand::config_status()
     count = ""; // server will ignore this if token is set anyways.
   }
 
+  if (!min_level.empty()) {
+    static const std::unordered_map<std::string_view, DiagsLevel> level_map = {
+      {"debug",   DL_Debug  },
+      {"note",    DL_Note   },
+      {"warning", DL_Warning},
+      {"error",   DL_Error  },
+    };
+
+    std::string lowered{min_level};
+    std::transform(lowered.begin(), lowered.end(), lowered.begin(),
+                   [](unsigned char c) { return 
static_cast<char>(std::tolower(c)); });
+
+    if (auto it = level_map.find(lowered); it != level_map.end()) {
+      _printer->as<ConfigReloadPrinter>()->set_min_level(it->second);
+    } else {
+      _printer->write_output("Invalid --min-level value. Use: debug, note, 
warning, error");
+      App_Exit_Status_Code = CTRL_EX_ERROR;
+      return;
+    }
+  }
+
   auto resp = fetch_config_reload(token, count);
 
   if (resp.error.size()) {
diff --git a/src/traffic_ctl/CtrlPrinters.cc b/src/traffic_ctl/CtrlPrinters.cc
index f055c4ba7e..22b934179d 100644
--- a/src/traffic_ctl/CtrlPrinters.cc
+++ b/src/traffic_ctl/CtrlPrinters.cc
@@ -308,7 +308,7 @@ dot_fill(int width)
 // @param content_width visual columns available for icon+name+dots+duration 
(shrinks per nesting)
 void
 print_task_tree(const ConfigReloadResponse::ReloadInfo &f, bool full_report, 
const std::string &prefix,
-                const std::string &child_prefix, int content_width = 55)
+                const std::string &child_prefix, DiagsLevel min_level = 
DL_Undefined, int content_width = 55)
 {
   std::string fname;
   if (f.filename.empty() || f.filename == "<none>") {
@@ -348,8 +348,21 @@ print_task_tree(const ConfigReloadResponse::ReloadInfo &f, 
bool full_report, con
   // Log lines: indented under the task, with tree continuation line if 
children follow.
   if (full_report && !f.logs.empty()) {
     std::string log_pfx = has_children ? (child_prefix + "\xe2\x94\x82  ") : 
(child_prefix + "   ");
-    for (const auto &log : f.logs) {
-      std::cout << log_pfx << log << '\n';
+    for (const auto &entry : f.logs) {
+      if (min_level != DL_Undefined && entry.level != DL_Undefined && 
entry.level < min_level) {
+        continue;
+      }
+      std::cout << log_pfx;
+      if (entry.level != DL_Undefined) {
+        // Indexed by DiagsLevel enum. In practice only [Dbg], [Note], [Warn], 
[Err] appear
+        // in task logs — Fatal/Alert/Emergency terminate the process before 
any task completes.
+        static constexpr const char *severity_tags[] = {
+          "[Diag]  ", "[Dbg]   ", "[Stat]  ", "[Note]  ", "[Warn]  ", "[Err]   
", "[Fatal] ", "[Alert] ", "[Emrg]  ",
+        };
+        int idx = std::min(std::max(static_cast<int>(entry.level), 0), 
static_cast<int>(DL_Emergency));
+        std::cout << severity_tags[idx];
+      }
+      std::cout << entry.text << '\n';
     }
   }
 
@@ -358,7 +371,7 @@ print_task_tree(const ConfigReloadResponse::ReloadInfo &f, 
bool full_report, con
     bool        is_last          = (i == f.sub_tasks.size() - 1);
     std::string sub_prefix       = child_prefix + (is_last ? 
"\xe2\x94\x94\xe2\x94\x80 " : "\xe2\x94\x9c\xe2\x94\x80 ");
     std::string sub_child_prefix = child_prefix + (is_last ? "   " : 
"\xe2\x94\x82  ");
-    print_task_tree(f.sub_tasks[i], full_report, sub_prefix, sub_child_prefix, 
content_width - 3);
+    print_task_tree(f.sub_tasks[i], full_report, sub_prefix, sub_child_prefix, 
min_level, content_width - 3);
   }
 }
 
@@ -454,7 +467,7 @@ ConfigReloadPrinter::print_reload_report(const 
ConfigReloadResponse::ReloadInfo
   }
   const std::string base_prefix("   ");
   for (const auto &sub : info.sub_tasks) {
-    print_task_tree(sub, full_report, base_prefix, base_prefix);
+    print_task_tree(sub, full_report, base_prefix, base_prefix, _min_level);
   }
 }
 
diff --git a/src/traffic_ctl/CtrlPrinters.h b/src/traffic_ctl/CtrlPrinters.h
index 050cb705e3..dcae30837f 100644
--- a/src/traffic_ctl/CtrlPrinters.h
+++ b/src/traffic_ctl/CtrlPrinters.h
@@ -225,9 +225,22 @@ class ConfigReloadPrinter : public BasePrinter
 {
   void write_output(YAML::Node const &result) override;
 
+  DiagsLevel _min_level{DL_Undefined};
+
 public:
   ConfigReloadPrinter(BasePrinter::Options opt) : BasePrinter(opt) {}
 
+  void
+  set_min_level(DiagsLevel level)
+  {
+    _min_level = level;
+  }
+  DiagsLevel
+  min_level() const
+  {
+    return _min_level;
+  }
+
   void print_reload_report(const ConfigReloadResponse::ReloadInfo &info, bool 
full_report = false);
   void write_progress_line(const ConfigReloadResponse::ReloadInfo &info);
 };
diff --git a/src/traffic_ctl/jsonrpc/CtrlRPCRequests.h 
b/src/traffic_ctl/jsonrpc/CtrlRPCRequests.h
index 528b11b3f0..f3e51b77ce 100644
--- a/src/traffic_ctl/jsonrpc/CtrlRPCRequests.h
+++ b/src/traffic_ctl/jsonrpc/CtrlRPCRequests.h
@@ -23,6 +23,7 @@
 
 // We base on the common client types.
 #include "shared/rpc/RPCRequests.h"
+#include "tsutil/ts_diag_levels.h"
 
 #include <yaml-cpp/yaml.h>
 
@@ -61,13 +62,18 @@ struct ConfigReloadRequest : shared::rpc::ClientRequest {
 // Full list of reload tasks, could be nested.
 struct ConfigReloadResponse {
   // Existing reload task info, could be nested.
+  struct LogEntry {
+    DiagsLevel  level{DL_Undefined}; ///< DL_Undefined for state-change 
messages
+    std::string text;
+  };
+
   struct ReloadInfo {
-    std::string              config_token;
-    std::string              status;
-    std::string              description;
-    std::string              filename;
-    std::vector<std::string> logs;
-    std::vector<ReloadInfo>  sub_tasks;
+    std::string             config_token;
+    std::string             status;
+    std::string             description;
+    std::string             filename;
+    std::vector<LogEntry>   logs;
+    std::vector<ReloadInfo> sub_tasks;
     struct Meta { // internal info.
       int64_t created_time_ms{0};
       int64_t last_updated_time_ms{0};
diff --git a/src/traffic_ctl/jsonrpc/ctrl_yaml_codecs.h 
b/src/traffic_ctl/jsonrpc/ctrl_yaml_codecs.h
index 96a98886d8..3f9ff1016f 100644
--- a/src/traffic_ctl/jsonrpc/ctrl_yaml_codecs.h
+++ b/src/traffic_ctl/jsonrpc/ctrl_yaml_codecs.h
@@ -103,7 +103,14 @@ template <> struct convert<ConfigReloadResponse> {
       info.description  = helper::try_extract<std::string>(from, 
"description", false, std::string{"<none>"});
       info.filename     = helper::try_extract<std::string>(from, "filename", 
false, std::string{"<none>"});
       for (auto &&log : from["logs"]) {
-        info.logs.push_back(log.as<std::string>());
+        if (log.IsMap()) {
+          ConfigReloadResponse::LogEntry entry;
+          entry.level = 
static_cast<DiagsLevel>(log["level"].as<int>(DL_Undefined));
+          entry.text  = log["text"].as<std::string>();
+          info.logs.push_back(entry);
+        } else {
+          info.logs.push_back({DL_Undefined, log.as<std::string>()});
+        }
       }
 
       for (auto &&sub : from["sub_tasks"]) {
diff --git a/src/traffic_ctl/traffic_ctl.cc b/src/traffic_ctl/traffic_ctl.cc
index 6fbfc7f01d..0ac3021f1c 100644
--- a/src/traffic_ctl/traffic_ctl.cc
+++ b/src/traffic_ctl/traffic_ctl.cc
@@ -177,6 +177,7 @@ main([[maybe_unused]] int argc, const char **argv)
   config_command.add_command("status", "Check the configuration status", [&]() 
{ command->execute(); })
     .add_option("--token", "-t", "Configuration token to check status.", "", 
1, "")
     .add_option("--count", "-c", "Number of status records to return. Use 
numeric or 'all' to get the full history", "", 1, "")
+    .add_option("--min-level", "", "Minimum severity level for log entries: 
debug, note, warning, error", "", 1, "")
     .add_example_usage("traffic_ctl config status");
   config_command.add_command("set", "Set a configuration value", "", 2, 
Command_Execute)
     .add_option("--cold", "-c",
diff --git a/src/traffic_server/traffic_server.cc 
b/src/traffic_server/traffic_server.cc
index ff55185f02..a9ea60fcab 100644
--- a/src/traffic_server/traffic_server.cc
+++ b/src/traffic_server/traffic_server.cc
@@ -92,8 +92,8 @@ extern "C" int plock(int);
 #include "iocore/eventsystem/RecProcess.h"
 #include "proxy/Transform.h"
 #include "iocore/eventsystem/ConfigProcessor.h"
+#include "mgmt/config/ConfigContextDiags.h"
 #include "mgmt/config/ConfigRegistry.h"
-#include "mgmt/config/ConfigContext.h"
 #include "proxy/http/HttpProxyServerMain.h"
 #include "proxy/http/HttpBodyFactory.h"
 #include "proxy/ProxySession.h"
@@ -775,11 +775,11 @@ register_config_files()
       } else {
         ctx.log("{}", zret);
         if (zret.severity() >= ERRATA_ERROR) {
-          ctx.fail("Failed to reload records.yaml");
+          CfgLoadFail(ctx, "Failed to reload %s", ts::filename::RECORDS);
           return;
         }
       }
-      ctx.complete();
+      CfgLoadComplete(ctx, "%s finished loading", ts::filename::RECORDS);
     },
     ConfigSource::FileOnly);
 

Reply via email to