Skip to content

refactor: give the remaining loggers the Ascender name - #1022

Merged
cigamit merged 4 commits into
ctrliq:mainfrom
blaipr:refactor/give-the-remaining-loggers-the-ascender-name
Sep 17, 2026
Merged

cigamit merged 4 commits into
ctrliq:mainfrom
blaipr:refactor/give-the-remaining-loggers-the-ascender-name

Conversation

@blaipr

@blaipr blaipr commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

#990 moved the analytics loggers once Ledger read both names. The platform's own diagnostics stayed on awx: 18 logger names in the LOGGING block and 145 getLogger calls, which is every log line an operator reads.

Why this is not a search and replace

LOG_AGGREGATOR_LOGGERS is a database setting, holding the first segment of the names a deployment forwards to its aggregator. Its default was awx, and the filter decides like this:

base_name, trailing_path = record.name.split('.', 1)
return bool(base_name in loggers)

Rename the loggers alone and every existing install keeps logging happily and forwards nothing, with no error anywhere. The records become ascender.main.dispatch, the stored setting still says awx, and the two never meet again.

So the old selector is an alias

A record named ascender.main.dispatch is forwarded when the stored setting says either ascender or awx. Both prefixes are named in constants.py beside ANALYTICS_LOGGER_PREFIX rather than spelled out at the point of use.

The default and its help text move to ascender, and the help says plainly that awx still selects for a deployment that predates the rename.

What moved with them

  • The blocklist in constants.py, which lists loggers by name.
  • The two tests that capture by logger name (caplog.at_level(logger='awx.conf.settings')).

What was deliberately left

test_dispatch.py's resolve_callable('awx.main.tests...') assertions. Those resolve import paths, not logger names, and the awx alias there belongs to #988's module aliasing, not to this change. Renaming them would have deleted a test of a different feature.

Verification

Run against a real PostgreSQL, with the suite's data paths pointed somewhere writable, so the run is not limited by this machine.

The unit suite, this branch against the same suite on main:

origin/main :  13 failed, 1410 passed, 1 skipped
this branch :  13 failed, 1424 passed, 1 skipped

Widening to ascender/conf/tests/ as well, the failing and erroring test IDs were diffed rather than counted: 25 on each side, an identical set. The 14 extra passes are this PR's new test file, and the only textual difference anywhere in that output is three captured log lines reading ascender.main.tasks.jobs where they read awx.main.tasks.jobs before, which is the change doing its job.

ascender/main/tests/unit/test_tasks.py on its own: 150 passed.

ruff check and ruff format --check pass on all 144 files.

One thing running it caught

The first version of the test asserted that ascender.conf.settings is forwarded under either selector. It is not: ascender.conf is on LOGGER_BLOCKLIST, so record_is_blocked returns before the selector is considered. Reading a setting to decide whether to forward a log line about reading a setting is the recursion that blocklist exists to stop.

The test was wrong, not the filter, and it now asserts the blocking instead. Worth saying because the check I ran before the libraries were available, extracting the decision branch from the source, could not have caught it: that extract did not include record_is_blocked.

For the release notes

Log lines change prefix. Anything grepping awx.main in a customer's own tooling needs updating, though their LOG_AGGREGATOR_LOGGERS setting does not.

ctrliq#990 moved the analytics loggers once Ledger read both names. The
platform's own diagnostics stayed on awx: eighteen logger names in the
LOGGING block and a hundred and forty five getLogger calls, which is
every log line an operator reads.

They move, and the reason this is not a search and replace is
LOG_AGGREGATOR_LOGGERS. It is a database setting holding the first
segment of the names a deployment forwards to its aggregator, its default
was awx, and the filter decides with record.name.split('.')[0] in
loggers. Renaming the loggers alone would leave every existing install
logging happily and forwarding nothing, with no error anywhere.

So the old selector is an alias. A record named ascender.main.dispatch is
forwarded when the stored setting says either ascender or awx, and the
two prefixes are named in constants.py beside the analytics one rather
than spelled out at the point of use. The default and its help text move
to ascender, and the help says awx still selects for a deployment that
predates the rename.

The blocklist moves with the names, since it lists loggers by name, and
the two tests that capture by logger name follow. The module alias tests
in test_dispatch.py are left alone: those resolve import paths rather
than logger names, and the awx alias there is ctrliq#988's, not this one.

test_service_logger_names.py states the names and the alias, including
that accepting awx does not turn into accepting anything at all.

Verified as far as this environment allows. ruff check and ruff format
pass on all 144 files. The new test could not be run here because the
suite imports python-ldap, which needs system libraries this box does not
have, so the filter's decision branch was extracted from the source and
exercised verbatim instead: a record under the new name is forwarded for
a stored selector of either spelling, and an unrelated logger is still
not forwarded under either. CI runs the real thing.
The new test claimed ascender.conf.settings is forwarded to the external
logger under either selector. Running it said otherwise: ascender.conf is
on LOGGER_BLOCKLIST, so record_is_blocked returns before the selector is
ever considered.

The test was wrong, not the filter. Reading a setting to decide whether
to forward a log line about reading a setting is the recursion the
blocklist exists to stop.

So that name comes out of the forwarded cases and gets a test of its own,
which states the blocking instead. The branch extracted from the source
and run by hand could not have caught this: it did not include
record_is_blocked.
@cigamit cigamit self-assigned this Sep 17, 2026
@cigamit

cigamit commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator

I had Fable take a glance and it also came back with this.

What the PR missed.

The logging connectivity test view still special-cases the old selector. Read views.py:168-173 on the PR's own commit: when the first stored selector is awx, the test message is sent through a logger named awx, and after the rename that logger has no handlers and the root logger has none either. I probed this live in the container.

On a pre-rename install the connectivity test message reaches no handler at all, so the "Test" button on the logging settings page silently proves nothing for exactly the deployments the alias was written to protect. With the new default the message went out under a bogus name, ascender.analytics.ascender.

The fix for pr-1022:

  • ascender/conf/views.py gains a small connection_test_logger_name helper. Either service selector, current or legacy, maps to the ascender logger. Any other selector is filed under the analytics prefix as before. The empty-list fallback also moves to ascender.
  • Three unit tests added to ascender/main/tests/unit/utils/test_service_logger_names.py for the helper.
  • ascender/main/tests/unit/utils/test_filters.py record names updated to ascender.*. One case there was passing for the wrong reason: the blocklist entry it exercised no longer matched its awx.main.utils.handlers record.
diff --git a/ascender/conf/views.py b/ascender/conf/views.py
index 2992213b..8cb50d8c 100644
--- a/ascender/conf/views.py
+++ b/ascender/conf/views.py
@@ -31,7 +31,7 @@ from ascender.conf.models import Setting
 from ascender.conf.serializers import SettingCategorySerializer, SettingSingletonSerializer
 from ascender.conf import settings_registry
 from ascender.main.utils.external_logging import reconfigure_rsyslog
-from ascender.main.constants import ANALYTICS_LOGGER_PREFIX
+from ascender.main.constants import ANALYTICS_LOGGER_PREFIX, LEGACY_SERVICE_LOGGER_PREFIX, SERVICE_LOGGER_PREFIX
 
 SettingCategory = collections.namedtuple('SettingCategory', ('url', 'slug', 'name'))
 
@@ -150,6 +150,25 @@ class SettingSingletonDetail(RetrieveUpdateDestroyAPIView):
                 settings.ASCENDER_URL_BASE = url
 
 
+def connection_test_logger_name(enabled_loggers):
+    """The logger the connectivity test message goes through.
+
+    The first selector stored in LOG_AGGREGATOR_LOGGERS decides. The service
+    selector maps to the service logger, whether the install stores it under its
+    current name or under awx from before the rename: that logger is the one
+    carrying the external_logger handler for service records, and a record named
+    plain awx would reach no handler at all. Any other selector is an analytics
+    headline name, so the record is filed under the analytics prefix.
+    """
+    try:
+        selector = enabled_loggers[0]
+    except IndexError:
+        return SERVICE_LOGGER_PREFIX
+    if selector in (SERVICE_LOGGER_PREFIX, LEGACY_SERVICE_LOGGER_PREFIX):
+        return SERVICE_LOGGER_PREFIX
+    return f'{ANALYTICS_LOGGER_PREFIX}.{selector}'
+
+
 class SettingLoggingTest(GenericAPIView):
     name = _('Logging Connectivity Test')
     model = Setting
@@ -164,13 +183,7 @@ class SettingLoggingTest(GenericAPIView):
             return Response({'error': 'Logging not enabled'}, status=status.HTTP_409_CONFLICT)
 
         # Send test message to configured logger based on db settings
-        try:
-            default_logger = settings.LOG_AGGREGATOR_LOGGERS[0]
-            if default_logger != 'awx':
-                default_logger = f'{ANALYTICS_LOGGER_PREFIX}.{default_logger}'
-        except IndexError:
-            default_logger = 'awx'
-        logging.getLogger(default_logger).error('AWX Connection Test Message')
+        logging.getLogger(connection_test_logger_name(settings.LOG_AGGREGATOR_LOGGERS)).error('Ascender Connection Test Message')
 
         hostname = getattr(settings, 'LOG_AGGREGATOR_HOST', None)
         protocol = getattr(settings, 'LOG_AGGREGATOR_PROTOCOL', None)
diff --git a/ascender/main/tests/unit/utils/test_filters.py b/ascender/main/tests/unit/utils/test_filters.py
index 59b9686d..26529a87 100644
--- a/ascender/main/tests/unit/utils/test_filters.py
+++ b/ascender/main/tests/unit/utils/test_filters.py
@@ -13,12 +13,14 @@ from django.db.models import Q
     'params, logger_name, expected',
     [
         # skip all records if enabled_flag = False
-        ({'enabled_flag': False}, 'awx.main', False),
+        ({'enabled_flag': False}, 'ascender.main', False),
         # skip all records if the host is undefined
-        ({'enabled_flag': True}, 'awx.main', False),
+        ({'enabled_flag': True}, 'ascender.main', False),
         # skip all records if underlying logger is used by handlers themselves
-        ({'enabled_flag': True}, 'awx.main.utils.handlers', False),
-        ({'enabled_flag': True, 'enabled_loggers': ['awx']}, 'awx.main', True),
+        ({'enabled_flag': True, 'enabled_loggers': ['ascender']}, 'ascender.main.utils.handlers', False),
+        ({'enabled_flag': True, 'enabled_loggers': ['ascender']}, 'ascender.main', True),
+        # the selector an install stored before the rename still picks the service loggers up
+        ({'enabled_flag': True, 'enabled_loggers': ['awx']}, 'ascender.main', True),
         ({'enabled_flag': True, 'enabled_loggers': ['abc']}, 'ascender.analytics.xyz', False),
         ({'enabled_flag': True, 'enabled_loggers': ['xyz']}, 'ascender.analytics.xyz', True),
     ],
diff --git a/ascender/main/tests/unit/utils/test_service_logger_names.py b/ascender/main/tests/unit/utils/test_service_logger_names.py
index da1a2ffd..562c7347 100644
--- a/ascender/main/tests/unit/utils/test_service_logger_names.py
+++ b/ascender/main/tests/unit/utils/test_service_logger_names.py
@@ -14,7 +14,8 @@ quietly stop forwarding, which is the failure this file exists to prevent.
 
 import pytest
 
-from ascender.main.constants import LEGACY_SERVICE_LOGGER_PREFIX, SERVICE_LOGGER_PREFIX
+from ascender.conf.views import connection_test_logger_name
+from ascender.main.constants import ANALYTICS_LOGGER_PREFIX, LEGACY_SERVICE_LOGGER_PREFIX, SERVICE_LOGGER_PREFIX
 from ascender.main.utils.filters import ExternalLoggerEnabled
 
 
@@ -62,6 +63,22 @@ def test_the_legacy_selector_does_not_forward_everything():
     assert enabled.filter(record=_record('django.request')) is False
 
 
+@pytest.mark.parametrize('stored_selector', ['ascender', 'awx'])
+def test_the_connection_test_message_goes_through_the_service_logger_under_either_selector(stored_selector):
+    """The settings page's connectivity test sends a record through the first
+    selected logger. A record named plain awx would reach no handler now that the
+    service loggers are configured under ascender, so both selectors map there."""
+    assert connection_test_logger_name([stored_selector, 'activity_stream']) == SERVICE_LOGGER_PREFIX
+
+
+def test_the_connection_test_message_defaults_to_the_service_logger():
+    assert connection_test_logger_name([]) == SERVICE_LOGGER_PREFIX
+
+
+def test_the_connection_test_message_follows_an_analytics_selector():
+    assert connection_test_logger_name(['job_events', 'ascender']) == f'{ANALYTICS_LOGGER_PREFIX}.job_events'
+
+
 def _record(name):
     class Record:
         pass

The logging settings page's Test button sent its record through the first
selector stored in LOG_AGGREGATOR_LOGGERS, special-casing the literal awx.
After the rename that logger is configured under ascender, so on an install
storing the old selector the record reached no handler at all and the root
logger has none either: the button proved nothing for exactly the deployments
the alias was written to protect. With the current selector it went out as
ascender.analytics.ascender, which is not a logger anything declares.

Both selectors now map to the service logger, and so does the empty fallback.
Any other selector is an analytics headline name and is filed under the
analytics prefix as before.

Reported by cigamit on ctrliq#1022.
@blaipr

blaipr commented Sep 17, 2026

Copy link
Copy Markdown
Contributor Author

Confirmed and fixed in 67d6827, thanks. I reproduced all three parts before applying it rather than taking the report on trust.

Probing the handler chain on the branch, logging.getLogger('awx') reaches no handler and neither does root, so the Test button really was proving nothing on a pre-rename install. Old behaviour against new, by stored selector:

LOG_AGGREGATOR_LOGGERS before reaches a handler after reaches a handler
['awx'] awx no ascender yes
[] awx no ascender yes
['ascender'] ascender.analytics.ascender yes ascender yes
['job_events'] ascender.analytics.job_events yes unchanged yes

The ascender.analytics.ascender case did reach the rsyslog handler through the analytics prefix, so that one was a wrong label rather than a lost record, but it is not a name anything declares.

The test_filters.py point was right too: LOGGER_BLOCKLIST holds ascender.main.utils.handlers on this branch while the case passed a record named awx.main.utils.handlers, so it returned False on the undefined-host branch and never exercised the blocklist at all.

Verification: the two new helper tests fail if the helper keeps the old awx mapping, and the failing test ID set across tests/unit and conf is identical before and after the change, so nothing else moved.

@cigamit
cigamit merged commit 1d4703c into ctrliq:main Sep 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Development

Successfully merging this pull request may close these issues.

2 participants