Skip to content

feat(activations): log retention Phase 2 — timestamp filters, DEBUG toggle, purge API, safety valve - #1650

Open
B-Whitt wants to merge 4 commits into
ansible:mainfrom
B-Whitt:feat/AAP-77938-log-retention-phase2
Open

feat(activations): log retention Phase 2 — timestamp filters, DEBUG toggle, purge API, safety valve#1650
B-Whitt wants to merge 4 commits into
ansible:mainfrom
B-Whitt:feat/AAP-77938-log-retention-phase2

Conversation

@B-Whitt

@B-Whitt B-Whitt commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Phase 2 of the unbounded log growth fix (AAP-77938). Phase 1 (PR feat(logs): add log retention, purge command, and API page_size cap [Phase 1] #1633) added the purge infrastructure; Phase 2 adds the controls needed to safely backport the max_page_size cap and -id ordering.
  • Four backend stories, each in its own commit:
    1. Timestamp filterslog_timestamp__gt/log_timestamp__lt query params on the logs endpoint
    2. DEBUG storage togglestore_debug_logs boolean on Activation (default false); DEBUG lines go to container stdout but not DB
    3. Purge APIPOST /activations/{id}/clear-logs/ (per-activation) and POST /logs/purge/ (global, superuser only) with batched deletion
    4. Safety valveEDA_MAX_LOG_LINES_PER_INSTANCE (default 500K) trims oldest rows every 1000 lines

Changes

  • src/aap_eda/api/filters/activation.py — added timestamp filters to ActivationInstanceLogFilter
  • src/aap_eda/core/models/activation.py — added store_debug_logs field
  • src/aap_eda/core/migrations/0074_activation_store_debug_logs.py — migration
  • src/aap_eda/services/activation/tee_system_logger.py — filter DEBUG lines from DB buffer
  • src/aap_eda/services/activation/db_log_handler.py — accept store_debug_logs param, enforce line cap
  • src/aap_eda/services/activation/activation_manager.py — pass store_debug_logs via functools.partial
  • src/aap_eda/api/serializers/activation.py — expose store_debug_logs in Create/Update/Read/List/Copy, add purge serializers
  • src/aap_eda/api/views/activation.pyclear_logs action + LogPurgeViewSet
  • src/aap_eda/api/urls.py — register /logs/ route
  • src/aap_eda/core/utils/delete_log_util.py — per-activation purge + batched deletion
  • src/aap_eda/settings/defaults.pyEDA_MAX_LOG_LINES_PER_INSTANCE setting

Test Plan

  • poetry run python -m pytest tests/integration/api/test_activation_instance.py -k "timestamp" — 4 timestamp filter tests
  • poetry run python -m pytest tests/integration/services/activation/test_tee_system_logger.py — 5 tests (3 new for DEBUG toggle)
  • poetry run python -m pytest tests/integration/api/test_activation.py::test_create_activationstore_debug_logs in base assertion
  • poetry run python -m pytest tests/integration/api/test_log_purge.py — 6 purge API tests
  • poetry run python -m pytest tests/integration/services/activation/test_db_log_handler.py — 3 safety valve tests
  • Full suite: 119 passed, 0 failures

Jira

Resolves: AAP-84682, AAP-84681, AAP-84683, AAP-84680
Parent: AAP-77938

Summary by CodeRabbit

  • New Features

    • Added optional persistence controls for DEBUG logs per activation.
    • Added activation-level and administrator-only global log purge actions, with optional date filters and deletion counts.
    • Added timestamp range filtering for activation instance logs.
    • Added automatic log retention limits, with configurable unlimited retention.
  • Bug Fixes

    • Non-DEBUG logs continue to be persisted regardless of DEBUG-log settings.

Adds log_timestamp__gt and log_timestamp__lt query parameters to the
activation instance logs endpoint, enabling timestamp-based filtering
for polling new logs and fetching historical data.

Resolves: AAP-84682
Assisted by: Claude Opus 4.6
…DEBUG lines

When store_debug_logs is false (the default), DEBUG-level log lines are
still sent to container stdout but excluded from the database. This
drastically reduces DB log volume (~225x fewer rows) for activations
running at DEBUG level while preserving observability through container
logs.

Resolves: AAP-84681
Assisted by: Claude Opus 4.6
Adds POST /activations/{id}/clear-logs/ for per-activation log purge
and POST /logs/purge/ for global purge (superuser only). Both support
an optional before_date parameter for partial purges. Deletion is
batched (10K rows per batch) to avoid long-running queries and lock
contention on large tables.

Resolves: AAP-84683
Assisted by: Claude Opus 4.6
Adds EDA_MAX_LOG_LINES_PER_INSTANCE (default 500K, 0 = unlimited) that
trims the oldest log rows when a per-instance cap is exceeded. The
COUNT check runs every 1000 lines to amortize the query cost. This
prevents any single activation from consuming unbounded DB storage
even when the DEBUG toggle is enabled.

Resolves: AAP-84680
Assisted by: Claude Opus 4.6
@B-Whitt
B-Whitt requested a review from a team as a code owner August 11, 2026 03:00
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Activation logging now supports configurable DEBUG persistence, periodic retention trimming, activation-scoped and global purge endpoints, and timestamp range filtering for activation-instance logs.

Changes

Activation logging and log management

Layer / File(s) Summary
Configurable debug-log storage
src/aap_eda/core/models/activation.py, src/aap_eda/core/migrations/0074_activation_store_debug_logs.py, src/aap_eda/api/serializers/activation.py, src/aap_eda/services/activation/*, tests/integration/services/activation/*
Activations expose store_debug_logs. Logger instances use this setting to persist or exclude DEBUG records while retaining other log levels.
Bounded log retention
src/aap_eda/settings/defaults.py, src/aap_eda/services/activation/db_log_handler.py, tests/integration/services/activation/test_db_log_handler.py
DBLogger periodically trims the oldest records above the configured per-instance limit. A zero limit disables trimming.
Activation and global log purge
src/aap_eda/core/utils/delete_log_util.py, src/aap_eda/api/serializers/activation.py, src/aap_eda/api/views/activation.py, src/aap_eda/api/views/__init__.py, src/aap_eda/api/urls.py, tests/integration/api/test_log_purge.py
Batched deletion utilities and purge endpoints delete logs by activation or globally, optionally before a date, and return the deleted count. Global purge requires a superuser.
Timestamp-based log filtering
src/aap_eda/api/filters/activation.py, tests/integration/api/test_activation_instance.py, tests/integration/conftest.py
Activation-instance log queries support log_timestamp__gt and log_timestamp__lt filters, including combined ranges and empty results.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant logs_route
  participant LogPurgeViewSet
  participant LogPurgeRequestSerializer
  participant delete_all_logs
  participant RulebookProcessLog
  Client->>logs_route: Submit purge request
  logs_route->>LogPurgeViewSet: Dispatch purge action
  LogPurgeViewSet->>LogPurgeRequestSerializer: Validate before_date
  LogPurgeViewSet->>delete_all_logs: Delete matching logs
  delete_all_logs->>RulebookProcessLog: Batch-delete records
  RulebookProcessLog-->>LogPurgeViewSet: Return deleted count
  LogPurgeViewSet-->>Client: Return deleted count
Loading

Suggested reviewers: hsong-rh, mkanoor

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 32.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main log retention changes, including filters, DEBUG control, purge API, and safety limits.
Description check ✅ Passed The description explains the changes, purpose, issue references, affected areas, testing, and reported results in a clear structure.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/aap_eda/core/utils/delete_log_util.py`:
- Around line 82-90: Capture the highest matching record ID from the queryset
before entering the batch loop, then constrain each batch query and deletion in
the purge flow to IDs at or below that captured boundary. Update the loop around
queryset.values_list and RulebookProcessLog.objects.filter so newly written
matching logs are excluded while preserving existing batch deletion and logging
behavior.

In `@src/aap_eda/services/activation/db_log_handler.py`:
- Around line 100-105: Update DBLogger._enforce_max_log_lines so retention-check
progress persists across logger instances created by
ActivationManager.update_logs(), rather than relying on the instance-local
line_count. Store durable per-activation-instance state or derive the check
interval from persisted log data, while preserving the existing max_lines guard
and periodic trimming behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5fa7ce1d-9e87-47c6-8ef8-1dd3e8bae01c

📥 Commits

Reviewing files that changed from the base of the PR and between 36fc351 and 9780c89.

📒 Files selected for processing (19)
  • src/aap_eda/api/filters/activation.py
  • src/aap_eda/api/serializers/__init__.py
  • src/aap_eda/api/serializers/activation.py
  • src/aap_eda/api/urls.py
  • src/aap_eda/api/views/__init__.py
  • src/aap_eda/api/views/activation.py
  • src/aap_eda/core/migrations/0074_activation_store_debug_logs.py
  • src/aap_eda/core/models/activation.py
  • src/aap_eda/core/utils/delete_log_util.py
  • src/aap_eda/services/activation/activation_manager.py
  • src/aap_eda/services/activation/db_log_handler.py
  • src/aap_eda/services/activation/tee_system_logger.py
  • src/aap_eda/settings/defaults.py
  • tests/integration/api/test_activation.py
  • tests/integration/api/test_activation_instance.py
  • tests/integration/api/test_log_purge.py
  • tests/integration/conftest.py
  • tests/integration/services/activation/test_db_log_handler.py
  • tests/integration/services/activation/test_tee_system_logger.py

Comment on lines +82 to +90
while True:
batch_ids = list(queryset.values_list("id", flat=True)[:BATCH_SIZE])
if not batch_ids:
break
deleted, _ = models.RulebookProcessLog.objects.filter(
id__in=batch_ids,
).delete()
total_deleted += deleted
logger.info("Purged %d log records (batch)", deleted)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Bound the purge to a stable record set.

Line 83 reevaluates an unbounded queryset for every batch. Active log writers can add matching rows faster than this loop deletes them. A global purge without before_date can then run until the request times out.

Capture the highest matching ID before the loop. Delete only records at or below that ID.

Proposed fix
 def _batched_delete(queryset) -> int:
     """Delete queryset in batches to avoid long-running queries."""
     total_deleted = 0
+    upper_id = queryset.order_by("-id").values_list("id", flat=True).first()
+    if upper_id is None:
+        return total_deleted
+    queryset = queryset.filter(id__lte=upper_id)
+
     while True:
         batch_ids = list(queryset.values_list("id", flat=True)[:BATCH_SIZE])
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
while True:
batch_ids = list(queryset.values_list("id", flat=True)[:BATCH_SIZE])
if not batch_ids:
break
deleted, _ = models.RulebookProcessLog.objects.filter(
id__in=batch_ids,
).delete()
total_deleted += deleted
logger.info("Purged %d log records (batch)", deleted)
upper_id = queryset.order_by("-id").values_list("id", flat=True).first()
if upper_id is None:
return total_deleted
queryset = queryset.filter(id__lte=upper_id)
while True:
batch_ids = list(queryset.values_list("id", flat=True)[:BATCH_SIZE])
if not batch_ids:
break
deleted, _ = models.RulebookProcessLog.objects.filter(
id__in=batch_ids,
).delete()
total_deleted += deleted
logger.info("Purged %d log records (batch)", deleted)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/aap_eda/core/utils/delete_log_util.py` around lines 82 - 90, Capture the
highest matching record ID from the queryset before entering the batch loop,
then constrain each batch query and deletion in the purge flow to IDs at or
below that captured boundary. Update the loop around queryset.values_list and
RulebookProcessLog.objects.filter so newly written matching logs are excluded
while preserving existing batch deletion and logging behavior.

Comment on lines +100 to +105
def _enforce_max_log_lines(self) -> None:
max_lines = int(settings.EDA_MAX_LOG_LINES_PER_INSTANCE)
if max_lines <= 0:
return
if self.line_count % 1000 != 0:
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

Keep retention check state across logger instances.

line_count starts at zero for every DBLogger. ActivationManager.update_logs() creates a new logger for each poll. If each poll writes fewer than 1,000 lines, this method never counts or trims records. The activation instance can exceed EDA_MAX_LOG_LINES_PER_INSTANCE without bound.

Use durable per-instance check state, or calculate the trim interval from persisted state.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/aap_eda/services/activation/db_log_handler.py` around lines 100 - 105,
Update DBLogger._enforce_max_log_lines so retention-check progress persists
across logger instances created by ActivationManager.update_logs(), rather than
relying on the instance-local line_count. Store durable per-activation-instance
state or derive the check interval from persisted log data, while preserving the
existing max_lines guard and periodic trimming behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant