Skip to content

LW-3297 Capture parent correlation ID from incoming requests#22

Merged
mSprunskas merged 15 commits into
paysera:masterfrom
siarhei-kruk:LW-3297-capture-parent-correlation-id
Apr 15, 2026
Merged

LW-3297 Capture parent correlation ID from incoming requests#22
mSprunskas merged 15 commits into
paysera:masterfrom
siarhei-kruk:LW-3297-capture-parent-correlation-id

Conversation

@siarhei-kruk
Copy link
Copy Markdown
Contributor

Implement parent correlation ID capture in paysera/lib-logging-extra-bundle. When a service receives an HTTP request containing the X-Paysera-Correlation-Id header (set by an upstream calling), the system captures this value and includes it as parent_correlation_id in all Monolog log records.

Changes

  • ParentCorrelationIdProvider — stores the parent correlation ID for the current request
  • ParentCorrelationIdListenerkernel.request listener (priority 255, main request only) that reads the X-Paysera-Correlation-Id header
  • ParentCorrelationIdProcessor — Monolog processor that adds parent_correlation_id to log record extra when present (Monolog v1/v2/v3)
  • DI registration in services.xml, listeners.xml, processors.xml
  • Unit tests for all three components

siarhei-kruk and others added 11 commits April 10, 2026 14:54
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…-provider

LW-3365 Add ParentCorrelationIdProvider service class
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…-listener

LW-3306 Add ParentCorrelationIdListener
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…-processor

LW-3307 Add ParentCorrelationIdProcessor
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…lation-id-services

LW-3308 Register parent correlation ID services in DI configuration
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…-tests

LW-3309 Add unit tests for parent correlation ID components
@siarhei-kruk siarhei-kruk force-pushed the LW-3297-capture-parent-correlation-id branch from 0c8d704 to be8f94f Compare April 13, 2026 13:13
return $record;
}

return new \Monolog\LogRecord(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why separate log entry is needed? Why not just append parent correlation to record in \Paysera\LoggingExtraBundle\Service\Processor\CorrelationIdProcessor::__invoke ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You're absolutely right! But I deliberately made it this way, to be as much compliant with CorrelationIdProcessor, as possible. (basically, claude just copied CorrelationIdProcessor with minor changes)

Comment thread src/Service/ParentCorrelationIdProvider.php
$record->level,
$record->message,
$record->context,
array_merge($record->extra, ['parent_correlation_id' => $parentCorrelationId]),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

With this approach query in graylog / elasticsearch would be correlation_id = ... OR parent_correlation_id = ...

I'd rather go with introducing totally new value, e.g. request_trace_id or smth, so we'd have two different things:

  • filtering by correlation id, which shows logs from single request, filtering logic does not change
  • filtering by new property, which would show trace of all requests (backend-for-frontend service A -> B backend service -> C backend dependency), new filtering case for "global" tracing.

In general, having parent_correlation_id kinda achieves this, but naming might be confusing - not exactly clear when to use correlation id and when to use parent correlation id.

Alternatively, if filtering backwards-compatibility does not matter much, lets just set value to correlation id from parent correlation id header, the only downside I see is that current filtering will start revealing logs with correlation id belonging to different app, because correlation id currently has app prefix.

PM for exact examples, don't want to disclose too much publicly

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The idea behind all this to have both correlation_id (current one, without changes) and parent_correlation_id - which basically is current correlation_id in calling application - in the same log record. It's not the global correlation id for the whole chain. It's just allows to build call chain like a linked list of a sort.

@siarhei-kruk siarhei-kruk requested a review from mSprunskas April 14, 2026 08:39
@siarhei-kruk siarhei-kruk force-pushed the LW-3297-capture-parent-correlation-id branch from aba29ac to fc90f4d Compare April 14, 2026 13:59
@siarhei-kruk siarhei-kruk force-pushed the LW-3297-capture-parent-correlation-id branch from 980b1df to 54d237e Compare April 14, 2026 14:23
Comment thread src/Service/ParentCorrelationIdProvider.php Outdated
return $this->parentCorrelationId;
}

public function setParentCorrelationId(?string $parentCorrelationId): void
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

would be better to only accept string in case there is no use-case where currently set value should be reset setParentCorrelationId(string $parentCorrelationId): void

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Listener can set it to null when no header provided. it's normal use-case by design.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yes, header value can be null, but it doesn't meant that ParentCorrelationIdProvider::$parentCorrelationId value is not null, hence "reset" scenario. If there is no way that value should be reset, then just accept string and move non-null validation to listener, this is how it's done with DTOs, and this one, from code perspective is a typical DTO, it just has service definition, which makes it a "service", in a sense that it's object managed by singleton pattern,

Comment thread src/Resources/config/services/listeners.xml Outdated
Comment thread src/Resources/config/services/processors.xml Outdated
Comment thread src/Resources/config/services.xml Outdated
Comment thread src/Listener/ParentCorrelationIdListener.php Outdated
 - FQCN as services IDs
 - got rid of duplicate constant
 - code style fix in ParentCorrelationIdProvider
@siarhei-kruk siarhei-kruk requested a review from mSprunskas April 14, 2026 15:27
- Add IterationEndListenerTest covering parent correlation ID reset and Sentry flush
- Update ParentCorrelationIdProviderTest to use resetParentCorrelationId() instead of nullable setter
- Update ParentCorrelationIdListenerTest to verify header absence preserves existing value
@mSprunskas mSprunskas merged commit 08e76ba into paysera:master Apr 15, 2026
38 checks passed
@siarhei-kruk siarhei-kruk deleted the LW-3297-capture-parent-correlation-id branch April 15, 2026 08:50
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