Skip to content

SPOC-551: add spock.log_verbosity and spock.apply_change_logging GUCs#477

Open
ibrarahmad wants to merge 1 commit into
mainfrom
SPOC-551
Open

SPOC-551: add spock.log_verbosity and spock.apply_change_logging GUCs#477
ibrarahmad wants to merge 1 commit into
mainfrom
SPOC-551

Conversation

@ibrarahmad
Copy link
Copy Markdown
Contributor

log_verbosity (normal|verbose) promotes spock DEBUG1/DEBUG2 to LOG. apply_change_logging (none|key_only|verbose) emits per-change JSON from the apply worker: action/schema/table/pk/origin/commit_ts in key_only, plus old/new row data in verbose. DDL is logged in both non-none modes. Both GUCs are PGC_SUSET (SET / SIGHUP reload).

TAP coverage in tests/tap/t/044_apply_change_logging.pl - 24/24.

log_verbosity (normal|verbose) promotes spock DEBUG1/DEBUG2 to LOG.
apply_change_logging (none|key_only|verbose) emits per-change JSON
from the apply worker: action/schema/table/pk/origin/commit_ts in
key_only, plus old/new row data in verbose.  DDL is logged in both
non-none modes.  Both GUCs are PGC_SUSET (SET / SIGHUP reload).

TAP coverage in tests/tap/t/044_apply_change_logging.pl - 24/24.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 20, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces structured JSON logging for the Spock apply worker, enabling operators to observe replicated changes. It adds two GUCs controlling log verbosity and detail level, implements JSON serialization helpers and logging entry points, integrates change logging into apply operations, refines debug message routing, and provides comprehensive test coverage.

Changes

Spock Apply Worker Change Logging

Layer / File(s) Summary
Log Configuration and Debug Routing Foundation
include/spock.h, src/spock.c
Declares spock_log_verbosity and spock_apply_change_logging GUCs, defines SpockLogVerbosity and SpockApplyChangeLogging enums, introduces SPOCK_DEBUG1/SPOCK_DEBUG2 macros that conditionally promote debug messages based on verbosity, and registers both GUCs during module initialization with help text and default values.
Change Log API and Implementation
include/spock_change_log.h, src/spock_change_log.c
Exposes spock_log_apply_change() and spock_log_apply_ddl() entry points via forward-declared header to avoid include conflicts. Implements JSON serialization helpers (append_json_string, append_json_value, append_row_json, append_change_log_header) and two logging functions that emit LOG-level JSON records containing action, schema/table names, origin, primary-key values, and optionally full row payloads in verbose mode.
Apply Worker Integration
src/spock_apply.c
Includes spock_change_log.h and emits change logs after successful row-level DML operations in handle_insert, handle_update, and handle_delete. For queued SQL/DDL via handle_sql, logs the DDL command before execution so the original SQL is captured regardless of execution outcome.
Debug Log Routing Enhancement
src/spock_common.c, src/spock_dependency.c, src/spock_sync.c
Updates existing debug logging at serialization-failure and Windows process-handling sites to use SPOCK_DEBUG1 and SPOCK_DEBUG2 macros, making those messages conditionally visible based on the spock_log_verbosity setting.
End-to-End Test Coverage
tests/tap/schedule, tests/tap/t/044_apply_change_logging.pl
Adds scheduled TAP test that sets up a 2-node cluster, verifies both GUCs exist with correct defaults and are PGC_SUSET-settable, replicates INSERT/UPDATE/DDL in key_only and verbose modes, asserts presence of expected JSON log records with correct fields, and confirms logging is disabled in none mode. Includes log-reading and log-matching helper subroutines.

Poem

🐰 A change log appears in JSON dress,
Verbosity, apply, logging finesse—
From INSERT to DELETE, each row takes flight,
The apply worker now sees the light!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically identifies the main changes: addition of two new GUCs (spock.log_verbosity and spock.apply_change_logging) with their allowed values.
Description check ✅ Passed The description comprehensively relates to the changeset, explaining the purpose and behavior of both GUCs, their modes, logging behavior, and test coverage.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch SPOC-551
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch SPOC-551

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 and usage tips.

@codacy-production
Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 2 medium

Results:
2 new issues

Category Results
Complexity 2 medium

View in Codacy

🟢 Metrics 42 complexity · 0 duplication

Metric Results
Complexity 42
Duplication 0

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/tap/t/044_apply_change_logging.pl (1)

20-30: ⚡ Quick win

Add a test case for skipped DML in TRANSDISCARD/SUB_DISABLE modes.

Current coverage is strong for normal apply flow, but it doesn’t assert that skipped DML in exception replay modes does not emit apply-change records. Adding this would close a high-risk gap in logging correctness.

🤖 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 `@tests/tap/t/044_apply_change_logging.pl` around lines 20 - 30, Add a new test
in the same TAP file that verifies skipped DML under the TRANSDISCARD and
SUB_DISABLE replay modes does not emit apply-change JSON log records: set
apply_change_logging to key_only (and repeat for verbose), configure
publisher/subscriber, perform DML on the publisher that will be skipped on the
subscriber by triggering TRANSDISCARD and SUB_DISABLE replay behavior, capture
the subscriber server logs (same log-capture helpers used elsewhere in this test
file), and assert that no apply-change JSON lines (those containing
action/schema/table/pk and, for verbose, new row data) are present for the
skipped DML; name the test case clearly (e.g., "skipped DML in
TRANSDISCARD/SUB_DISABLE produces no apply_change_logging records") so it’s easy
to find.
🤖 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/spock_apply.c`:
- Around line 1414-1416: The apply-change log is emitted even when DML was
intentionally skipped under exception-replay modes TRANSDISCARD/SUB_DISABLE;
update the three spots that call spock_log_apply_change (the INSERT block with
newtup, the UPDATE block with oldtup/newtup, and the DELETE block with oldtup)
to only call spock_log_apply_change when the operation both did not fail and was
actually applied — i.e., change the guard from if (!failed) to if (!failed &&
!<skip-dml-flag>), where <skip-dml-flag> checks the current exception replay
mode (TRANSDISCARD or SUB_DISABLE) or otherwise detects that DML was skipped;
use the existing symbols for those modes so the check reflects the code’s
exception replay state and ensures no “applied change” log is produced for
skipped operations.

---

Nitpick comments:
In `@tests/tap/t/044_apply_change_logging.pl`:
- Around line 20-30: Add a new test in the same TAP file that verifies skipped
DML under the TRANSDISCARD and SUB_DISABLE replay modes does not emit
apply-change JSON log records: set apply_change_logging to key_only (and repeat
for verbose), configure publisher/subscriber, perform DML on the publisher that
will be skipped on the subscriber by triggering TRANSDISCARD and SUB_DISABLE
replay behavior, capture the subscriber server logs (same log-capture helpers
used elsewhere in this test file), and assert that no apply-change JSON lines
(those containing action/schema/table/pk and, for verbose, new row data) are
present for the skipped DML; name the test case clearly (e.g., "skipped DML in
TRANSDISCARD/SUB_DISABLE produces no apply_change_logging records") so it’s easy
to find.
🪄 Autofix (Beta)

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

Run ID: ebbbe842-5466-4b4d-86ca-e2caf912cda8

📥 Commits

Reviewing files that changed from the base of the PR and between 3886058 and 771775f.

📒 Files selected for processing (10)
  • include/spock.h
  • include/spock_change_log.h
  • src/spock.c
  • src/spock_apply.c
  • src/spock_change_log.c
  • src/spock_common.c
  • src/spock_dependency.c
  • src/spock_sync.c
  • tests/tap/schedule
  • tests/tap/t/044_apply_change_logging.pl

Comment thread src/spock_apply.c
Comment on lines +1414 to +1416
if (!failed)
spock_log_apply_change("INSERT", rel, NULL, &newtup,
remote_origin_name);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Prevent false “applied change” logs for skipped DML in exception replay modes.

In TRANSDISCARD/SUB_DISABLE, DML is intentionally skipped but failed remains false, so these blocks still emit spock_log_apply_change(...). That produces incorrect apply-change logs for operations never applied.

Proposed fix
-	if (!failed)
+	if (!failed &&
+		!(MyApplyWorker->use_try_block &&
+		  (exception_behaviour == TRANSDISCARD ||
+		   exception_behaviour == SUB_DISABLE)))
 		spock_log_apply_change("INSERT", rel, NULL, &newtup,
 							   remote_origin_name);
-	if (!failed)
+	if (!failed &&
+		!(MyApplyWorker->use_try_block &&
+		  (exception_behaviour == TRANSDISCARD ||
+		   exception_behaviour == SUB_DISABLE)))
 		spock_log_apply_change("UPDATE", rel,
 							   hasoldtup ? &oldtup : NULL, &newtup,
 							   remote_origin_name);
-	if (!failed)
+	if (!failed &&
+		!(MyApplyWorker->use_try_block &&
+		  (exception_behaviour == TRANSDISCARD ||
+		   exception_behaviour == SUB_DISABLE)))
 		spock_log_apply_change("DELETE", rel, &oldtup, NULL,
 							   remote_origin_name);

Also applies to: 1584-1587, 1716-1718

🤖 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/spock_apply.c` around lines 1414 - 1416, The apply-change log is emitted
even when DML was intentionally skipped under exception-replay modes
TRANSDISCARD/SUB_DISABLE; update the three spots that call
spock_log_apply_change (the INSERT block with newtup, the UPDATE block with
oldtup/newtup, and the DELETE block with oldtup) to only call
spock_log_apply_change when the operation both did not fail and was actually
applied — i.e., change the guard from if (!failed) to if (!failed &&
!<skip-dml-flag>), where <skip-dml-flag> checks the current exception replay
mode (TRANSDISCARD or SUB_DISABLE) or otherwise detects that DML was skipped;
use the existing symbols for those modes so the check reflects the code’s
exception replay state and ensures no “applied change” log is produced for
skipped operations.

Comment thread src/spock.c
Comment on lines +1103 to +1115
DefineCustomEnumVariable("spock.log_verbosity",
gettext_noop("Sets the verbosity of spock-specific log output."),
gettext_noop("When 'verbose', spock's own DEBUG1 and DEBUG2 messages are "
"promoted to LOG level so they appear in standard server logs "
"without changing log_min_messages globally."),
&spock_log_verbosity,
SPOCK_LOG_VERBOSITY_NORMAL,
log_verbosity_options,
PGC_SUSET, 0,
NULL, NULL, NULL);

DefineCustomEnumVariable("spock.apply_change_logging",
gettext_noop("Controls JSON logging of changes applied by the apply worker."),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't think we need both spock.log_verbosity and spock.apply_change_logging. A single spock.debug_log_level using the stock server_message_level_options enum should be sufficient: increasing the DEBUG level increases verbosity exactly like Postgres itself.

That also removes the need for the new SPOCK_DEBUG1/SPOCK_DEBUG2 macros — every existing ereport(DEBUG1/2, …) is already gated by log_min_messages, so the GUC simply controls the level at which spock's own debug output is emitted.

Comment thread src/spock_apply.c
Comment on lines +1584 to +1586
if (!failed)
spock_log_apply_change("UPDATE", rel,
hasoldtup ? &oldtup : NULL, &newtup,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should the change-log record really be gated by !failed? When user turns this on they almost certainly want to see the conflict and exception cases — those are the interesting ones. As written, it silently drops exactly the records you'd enable it to capture. I'd either always emit (with "incoming"|"applied"|"conflict"|"skipped" field, can be a generic string) or hook into spock_report_conflict()/log_insert_exception.

Same comment applies to the DDL path in handle_sql — it logs before execution, so a failed DDL still produces an "action":"DDL" line that looks applied.

Comment thread src/spock_change_log.c
Comment on lines +36 to +39
static void
append_json_string(StringInfo buf, const char *str)
{
appendStringInfoChar(buf, '"');
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

src/spock_jsonb_utils.c already has most of the tuple->JSON functionality needed here (and already makes use of escape_json()); it should be enhanced where it falls short rather than duplicated.

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.

2 participants