This guide explains how to trace failed requests and debug issues using the structured logging and correlation propagation system implemented in StreamPay.
The StreamPay system implements end-to-end correlation propagation across:
- API Edge: HTTP request handling
- Queue System: Job enqueue with correlation context
- Worker Processing: Job execution with context restoration
- Chain Submission: Stellar transaction submission
- Webhook Emission: Event delivery with correlation
The system uses:
- request_id: Unique identifier for each HTTP request
- correlation_id: Propagated across all async operations for a single business transaction
- traceparent: Optional W3C trace context for distributed tracing
- stream_id: Stream identifier when applicable
- job_id: Job identifier for async operations
- stellar_tx_hash: Stellar transaction hash for chain submissions
- webhook_id: Webhook delivery identifier
- retry_count: Retry attempt number
- queue_name: Queue name for job processing
All logs are structured JSON with consistent fields for easy querying in log aggregation systems like Datadog, ELK, or CloudWatch.
Every log entry includes:
{
"level": "info|warn|error|debug",
"message": "Human-readable message",
"timestamp": "2026-04-28T10:30:00.000Z",
"service": "streampay-frontend",
"environment": "development|production",
"request_id": "uuid-v4",
"correlation_id": "uuid-v4",
"stream_id": "stream-abc123 (when applicable)",
"job_id": "job-xyz (when applicable)",
"stellar_tx_hash": "tx-hash (when applicable)",
"webhook_id": "webhook-id (when applicable)",
"retry_count": 3 (when applicable)",
"queue_name": "settlement-queue (when applicable)",
"traceparent": "00-... (when applicable)",
"...": "additional context fields"
}The correlation context propagates through the following stages:
- Request arrives with optional headers (
x-request-id,x-correlation-id,traceparent) - Middleware extracts or generates correlation IDs
- Context stored in AsyncLocalStorage
- Log:
Incoming requestwith correlation metadata
- Job created with correlation context copied from current context
- Job metadata includes:
request_id,correlation_id,stream_id,traceparent - Log:
Job enqueuedwithjob_id,queue_name,correlation_id
- Worker retrieves job and restores correlation context
- Context wrapped with AsyncLocalStorage for job execution
- Job-specific context added:
job_id,queue_name,retry_count - Log:
Worker processing jobwith full correlation metadata
- Stellar service adds
stellar_tx_hashto correlation context - Transaction build logged with stream_id and correlation_id
- RPC submission logged with stellar_tx_hash
- Log:
Stellar transaction submitted to RPCwith all correlation fields
- Webhook service adds
webhook_idto correlation context - Internal headers stripped before external delivery
- Log:
Webhook delivered successfullywith webhook_id and correlation_id
- Safe correlation headers returned to client:
x-request-id,x-correlation_id - Internal headers stripped:
x-internal-auth,x-service-token - Log:
Request completedwith status and correlation_id
When a user reports a failed settlement, gather:
- Stream ID (if known)
- Approximate time of failure
- Error message (if available)
If you have a correlation ID from the error response:
# Datadog
correlation_id:"abc-123-def-456"
# ELK / Kibana
correlation_id: "abc-123-def-456"
# CloudWatch Logs Insights
fields @message
| filter correlation_id = 'abc-123-def-456'
| sort @timestamp descIf you only have the stream ID:
# Datadog
stream_id:"stream-ada123"
# ELK / Kibana
stream_id: "stream-ada123"
# CloudWatch Logs Insights
fields @message
| filter stream_id = 'stream-ada123'
| sort @timestamp descIf a transaction was submitted but failed:
# Datadog
stellar_tx_hash:"fake-tx-abc123"
# ELK / Kibana
stellar_tx_hash: "fake-tx-abc123"
# CloudWatch Logs Insights
fields @message
| filter stellar_tx_hash = 'fake-tx-abc123'
| sort @timestamp descOnce you find the initial log entry, use the correlation_id to trace the entire request flow:
# Get all logs for a single correlation
correlation_id:"abc-123-def-456" | sort @timestamp ascThis will show you:
- Initial API request
- Stream state validation
- Transaction submission
- Any retries
- Final outcome
[2026-04-28 10:30:00] Stream settled
[2026-04-28 10:30:01] Transaction submitted
[2026-04-28 10:30:02] Error: transaction failed
Problems:
- No correlation between logs
- No request context
- Hard to trace across services
- No structured fields for filtering
{"level":"info","message":"Incoming request","timestamp":"2026-04-28T10:30:00.000Z","service":"streampay-frontend","environment":"production","request_id":"req-abc123","correlation_id":"corr-def456","method":"POST","url":"/api/streams/stream-ada/settle"}
{"level":"info","message":"Settlement request received","timestamp":"2026-04-28T10:30:00.100Z","service":"streampay-frontend","environment":"production","request_id":"req-abc123","correlation_id":"corr-def456","stream_id":"stream-ada"}
{"level":"info","message":"Settlement job enqueued","timestamp":"2026-04-28T10:30:00.200Z","service":"streampay-frontend","environment":"production","request_id":"req-abc123","correlation_id":"corr-def456","stream_id":"stream-ada","job_id":"job-xyz789","queue_name":"settlement-queue"}
{"level":"info","message":"Worker processing job","timestamp":"2026-04-28T10:30:00.300Z","service":"streampay-frontend","environment":"production","request_id":"req-abc123","correlation_id":"corr-def456","stream_id":"stream-ada","job_id":"job-xyz789","queue_name":"settlement-queue","attempt":1}
{"level":"info","message":"Stellar transaction build started","timestamp":"2026-04-28T10:30:00.400Z","service":"streampay-frontend","environment":"production","request_id":"req-abc123","correlation_id":"corr-def456","stream_id":"stream-ada","job_id":"job-xyz789"}
{"level":"info","message":"Stellar transaction built","timestamp":"2026-04-28T10:30:00.500Z","service":"streampay-frontend","environment":"production","request_id":"req-abc123","correlation_id":"corr-def456","stream_id":"stream-ada","stellar_tx_hash":"stellar-tx-abc123"}
{"level":"info","message":"Stellar transaction submitted to RPC","timestamp":"2026-04-28T10:30:00.700Z","service":"streampay-frontend","environment":"production","request_id":"req-abc123","correlation_id":"corr-def456","stream_id":"stream-ada","stellar_tx_hash":"stellar-tx-abc123"}
{"level":"info","message":"Stellar transaction confirmed","timestamp":"2026-04-28T10:30:01.000Z","service":"streampay-frontend","environment":"production","request_id":"req-abc123","correlation_id":"corr-def456","stream_id":"stream-ada","stellar_tx_hash":"stellar-tx-abc123"}
{"level":"info","message":"Webhook emission started","timestamp":"2026-04-28T10:30:01.100Z","service":"streampay-frontend","environment":"production","request_id":"req-abc123","correlation_id":"corr-def456","stream_id":"stream-ada","stellar_tx_hash":"stellar-tx-abc123","webhook_id":"webhook-def456"}
{"level":"info","message":"Webhook delivered successfully","timestamp":"2026-04-28T10:30:01.250Z","service":"streampay-frontend","environment":"production","request_id":"req-abc123","correlation_id":"corr-def456","stream_id":"stream-ada","webhook_id":"webhook-def456","status_code":200}
{"level":"info","message":"Job processed successfully","timestamp":"2026-04-28T10:30:01.300Z","service":"streampay-frontend","environment":"production","request_id":"req-abc123","correlation_id":"corr-def456","stream_id":"stream-ada","job_id":"job-xyz789"}
{"level":"info","message":"Settlement completed successfully","timestamp":"2026-04-28T10:30:01.400Z","service":"streampay-frontend","environment":"production","request_id":"req-abc123","correlation_id":"corr-def456","stream_id":"stream-ada","stellar_tx_hash":"stellar-tx-abc123","job_id":"job-xyz789"}
{"level":"info","message":"Request completed","timestamp":"2026-04-28T10:30:01.500Z","service":"streampay-frontend","environment":"production","request_id":"req-abc123","correlation_id":"corr-def456","status":200}Benefits:
- All logs linked by single
correlation_idacross API → Queue → Worker → Chain → Webhook - Easy to filter by any field (job_id, stellar_tx_hash, webhook_id, queue_name)
- Clear timeline of events with timestamps
- Structured for automated analysis
- Retry tracking with attempt numbers
- Queue-level visibility
-
User reports: "My settlement failed"
-
Get stream ID: User provides
stream-ada123 -
Search logs:
stream_id:"stream-ada123" level:error -
Find correlation_id: From error log, get
corr-def456 -
Trace full flow:
correlation_id:"corr-def456" | sort @timestamp asc
-
Identify failure point:
- Was the stream found?
- Was the state valid?
- Did transaction submit?
- Did it fail on chain?
- Were there retries?
-
Check retry count:
correlation_id:"corr-def456" retry_count:*
-
Check Stellar transaction:
stellar_tx_hash:"fake-tx-xyz789"
stream_id:"stream-abc123" level:errorretry_count:*duration_ms:>1000level:error stellar_tx_hash:*webhook_id:* level:errorqueue_name:"settlement-queue" level:errorExternal clients cannot override internal correlation IDs:
- Untrusted requests get fresh correlation IDs
- Only trusted internal services (localhost, authenticated) can set correlation headers
traceparentfrom external clients is ignored
The following headers are never exposed in responses:
x-internal-authx-service-tokenx-correlation-id-internal
Only safe tracing headers are exposed:
x-request-idx-correlation-idtraceparent(when present)
- No automatic PII logging
- PII must be explicitly added by developers if needed
- Review logs before adding sensitive fields
- Consider redaction for email addresses, phone numbers, etc.
Never log:
- Wallet seed phrases
- Private keys
- Auth tokens
- Full credit card numbers
- SSN or government IDs
- Passwords (even hashed)
Safe to log:
- User IDs (internal identifiers)
- Stream IDs
- Transaction hashes
- Error codes
- Status values
- Non-sensitive metadata
Use caution with:
- Email addresses (consider redaction)
- Phone numbers (consider redaction)
- Names (consider if truly necessary)
- IP addresses (consider privacy implications)
When backend services are added, they should:
-
Accept correlation headers from the frontend:
x-request-idx-correlation-idtraceparent(optional)
-
Propagate correlation context through:
- Queue jobs (add to job metadata)
- Worker processing (restore from job metadata)
- Chain submissions (include in logs)
- Webhook emissions (include in internal processing)
-
Return correlation headers in responses:
x-request-idx-correlation-id
-
Strip internal headers at public boundaries:
- Outbound webhooks to external services
- Public API responses
Run the test suite to verify correlation propagation:
npm testTests cover:
- Correlation ID generation
- Header extraction
- AsyncLocalStorage propagation
- Security (header spoofing prevention)
- Public boundary protection
- Structured logging format
Cause: Request not wrapped in correlation middleware
Solution: Ensure all API routes use withCorrelationMiddleware
Cause: New context created instead of propagating existing
Solution: Use withCorrelationContext to propagate, don't create new context
Cause: Response headers not set by middleware
Solution: Ensure middleware wraps the entire handler
Cause: Security check bypassed
Solution: Verify isTrustedInternalRequest is called before trusting headers
When backend services are added, consider:
- OpenTelemetry integration: Replace custom correlation with OpenTelemetry
- Jaeger/Zipkin: Add distributed tracing visualization
- Log aggregation: Centralize logs in ELK, Datadog, or CloudWatch
- Alerting: Set up alerts on error rates by correlation_id
- Metrics: Track settlement success/failure rates by stream_id
For issues with correlation propagation or logging:
- Check this guide first
- Review test cases in
app/lib/logger.test.tsandapp/lib/correlation-middleware.test.ts - Check implementation in
app/lib/logger.tsandapp/lib/correlation-middleware.ts - Review API route examples in
app/api/streams/