Skip to content
Kien

From a trace-id back to the exact request we sent

Two design calls — snapshot the request at the first handler line, before any code mutates it, and derive the field table from each endpoint's own validation schema — made the tool portable enough to ship across all five products in a single day.

Period
08/2026
Role
Sole author, rolled out to four further services

Stack

  • Node.js
  • Koa
  • Express
  • MySQL + Knex
  • Redis
  • Joi
  • JWT
  • GitLab CI

The problem

Support kept getting "this search returned nothing" or "the price is wrong" with nothing but a trace-id. The request that actually goes upstream is assembled from cached mappings, per-account config and supplier credentials — nobody can rebuild it by hand.

The hotel product had tooling for this. The other four logged to Redis and MySQL with no way to read any of it back short of SSH-ing into Redis.

The design

Two decisions carry the whole tool.

Snapshot the request at the first line of each handler — before controllers backfill defaults and transformers rewrite date formats — and store it as a compact normalised plain-text string rather than a JSON blob.

The field table is derived from each endpoint's own validation schema, so no field is silently dropped and types survive: null, empty string and absent stay three distinct states.

A read-back endpoint mints a fresh JWT and returns a paste-and-run curl.

The whole logging path is fire-and-forget with per-call-site try/catch, proven safe by renaming the log table on staging and confirming search still returned 200.

The result

Because the snapshot point and the schema-derived field table are the same decision in every gateway, the rollout was mechanical: one MR per gateway, five products on the same day, verified against real staging domains. Payloads are 44–75% smaller than JSON.

After deploy I found my own bug: five identical searches produced five rows with exactly one distinct payload. Fixed by moving the write inside each product's existing cache-MISS branch — reusing a signal already present in the code instead of adding a new Redis key.

The production search-log table now holds roughly 626,000 rows.