Skip to content

feat: migrate to new backend logging convention - #4

Merged
AquiGorka merged 4 commits into
mainfrom
backend-logging-convention
May 31, 2026
Merged

feat: migrate to new backend logging convention#4
AquiGorka merged 4 commits into
mainfrom
backend-logging-convention

Conversation

@AquiGorka

Copy link
Copy Markdown
Contributor

Summary

Adopts the convention defined in local-dev/logging.md. First of four backend migrations (provider/council/pay are pending).

  • New Logger at src/utils/logger/index.ts — TS port of AquiGorka/go-logger. Five methods (info, event, debug, error, scope), four levels (Debug, Info, Event, Disabled), nested scopes, dual stdout-human / file-JSON output.
  • No module-level singleton. src/config/logger.ts now exports createLogger(); main.ts is the only construction site and threads { log, bus } through to every service and free function.
  • NetworkEventBus becomes a DI'd class — takes { log } in its constructor, no longer exported as a singleton. The stray console.warn in its publish() catch is now log.error.
  • HTTP routes use the handle<X>(deps) factory pattern. handleNetworkWs(deps) returns the request handler; buildNetworkWsRouter(deps) and buildApiRouter(deps) wire it up.
  • Sync free functions (refreshWasmRegistry, fetchCouncilTopology, refreshTopology, coldStartScan, evaluateUnknownContract, drainPendingAdoptions, startSorobanWatcher, startScheduler) all take { log, bus } deps and scope internally via log.scope("funcName").
  • Removed the local describeErr() helper — the new Logger handles error stringification for both human and JSON sinks via instanceof Error / safeJsonValue.
  • Migrated all 42 LOG calls + the one stray console.warn per the convention's mapping rules.

No OTEL changes (this repo has no OTEL today).

Logging coverage

Subsystem Levels emitted
HTTP routes (handleNetworkWs) info, event, debug, error
Background services (soroban-watcher, scheduler) info, event, debug, error
External API calls (council-platform, GitHub releases, Stellar RPC) info, event, debug, error
Error paths error
Bootstrap (main.ts) info, event, debug, error

Verified locally (pre-push)

  • deno fmt --check: clean
  • deno lint: clean (22 files)
  • deno task test: 17 passed, 0 failed
  • deno task check: clean
  • grep -rn 'LOG\.(...)' src/: 0 hits
  • grep -rn 'console\.(...)' src/: 0 hits
  • grep -rn 'describeErr' src/: 0 hits

Test plan

  • CI green on this branch
  • PM merges PR #100 (logging convention doc) first, then this
  • Smoke test locally with LOG_LEVEL=info via local-dev/up.sh to confirm output format matches the spec

Last commit (chore: bump version to 0.1.4) is the patch-bump-alone commit per the open-pr-checklist.

AquiGorka added 4 commits May 27, 2026 12:30
Replaces the previous custom Logger (six-level variadic LOG singleton)
with the convention defined in local-dev/logging.md:

- Five-method Logger interface: info, event, debug, error, scope.
  Source ported from github.com/AquiGorka/go-logger, lives at
  src/utils/logger/index.ts. Dual stdout-human / file-JSON output.
- src/config/logger.ts now exports createLogger() (no module-level
  singleton). main.ts is the only place the root logger is created.
- NetworkEventBus becomes a DI'd class (takes { log } in constructor);
  main.ts constructs it and passes deps into the soroban watcher,
  scheduler, and HTTP routes.
- All sync free functions (refreshWasmRegistry, fetchCouncilTopology,
  refreshTopology, coldStartScan, evaluateUnknownContract, etc.) take
  { log, bus } deps. Internal scoping via log.scope("funcName").
- WebSocket route exposes handleNetworkWs(deps) factory and
  buildNetworkWsRouter(deps). v1.routes.ts now exports
  buildApiRouter(deps).
- Removed local describeErr() helper — the new Logger stringifies
  errors internally for both human and JSON sinks.
- Migrated all 42 LOG call sites + the one stray console.warn in
  events/bus.ts.

No OTEL changes (this repo had no OTEL to begin with).
- NetworkEventBus: subscribe, publish entry breadcrumbs + debug for
  listener count and event kind.
- soroban-watcher: publishMappedEvent + processRawEventBatch take log;
  callers updated.

deno check src clean.
@AquiGorka
AquiGorka merged commit 8550196 into main May 31, 2026
5 checks passed
@AquiGorka
AquiGorka deleted the backend-logging-convention branch May 31, 2026 16:54
AquiGorka added a commit that referenced this pull request Jun 5, 2026
…roring provider-platform watcher pattern) (#5)

## Summary

Refactors the topology / event-discovery surface to mirror
`provider-platform`'s `event-watcher` pattern: **sync at boot, set
listeners, update state as events come in**. The previous hourly
periodic re-sync is gone; the new-council fallback that depended on a
GitHub-sourced WASM hash registry is gone; what's left is a tight
chain-event-driven loop that publishes within ~5 s of a new council
going on-chain.

Validated by a local-stack end-to-end smoke
(`/Users/theahaco/repos/tmp/add-events-capture-framework-1/`) where two
flow scripts deploy a fresh council mid-test, and the network-dashboard
WS surfaces the resulting `council_formed → provider_added →
channel_deposit → channel_bundle → channel_settlement` sequence in real
time.

## The four architectural changes

1. **Remove the hourly topology resync** (`src/core/sync/scheduler.ts`).
The scheduler now does only the 60-second rolling-window sweep (memory
cap on the 24h counter window). Topology arrives at boot via
`fetchCouncilTopology` and updates via the contract-init listener.

2. **Drop the WASM hash registry** (`src/core/sync/wasm-registry.ts`
deleted; `src/main.ts`, `src/core/sync/topology-refresh.ts` updated).
The registry was sourced from `Moonlight-Protocol/soroban-core`'s GitHub
releases and returned `HTTP 403` for any unauthenticated fetch — which
meant the new-council fallback was effectively a permanent no-op in
local-dev. `isKnownChannelAuthHash` / `isWasmRegistryReady` /
`knownHashCount` had no callers after change #3.

3. **Ungate the contract-init listener**
(`src/core/sync/contract-init-listener.ts`,
`src/core/sync/soroban-watcher.ts`). The watcher always polls
`getEvents` for `contract_initialized` from unknown contracts (no more
`isContractInitListenerEnabled()` gate). Each unknown is held in
`pendingAdoption` with an `observedAtLedger` for back-fill (see #4) and
a TTL of `PENDING_TTL_MS = 120 s` — long enough that a council
registered AFTER its on-chain deploy still gets adopted on a subsequent
tick. Past the TTL the contract is cached as `notMoonlight` so a chatty
unrelated contract can't drag us into infinite topology refreshes.

4. **Back-fill on adoption**
(`src/core/sync/soroban-watcher.ts:backfillFromLedger`, threaded via
`drainPendingAdoptions`'s new `backfillFromLedger` dep). Forward polling
resumes from `lastLedgerSeen + 1`, so any chain event between the deploy
ledger and the adoption tick (most notably `provider_added` and the
first SAC `transfer`) would otherwise be lost. The back-fill scan walks
every currently-watched contract from the earliest adopted
`observedAtLedger` to head, maps each event, and publishes via the bus.
`networkState.recordEvent`'s dedup makes this safe to run concurrent
with `pollTick`.

The hot-path behaviour now matches
`provider-platform/src/core/service/event-watcher/index.ts`:
- **Boot**: `initFromDb` → `ensureWatcher` per ACTIVE membership.
- **Listeners**: per-contract Soroban poll, narrowly-scoped.
- **State updates as events come in**: handler emits via `emitForPp` /
`emitForBundles`.
- **Hot-path additions**: `addCouncilWatcher` / `addProviderAddress`
exported for API handlers to call when new entities are created.

For network-dashboard the analog is now: `fetchCouncilTopology` at boot,
then chain-event-driven adoption via `contract_initialized` →
`refreshTopology` → `backfillFromLedger`.

## Cross-PR dependency

This PR must merge **before** Moonlight-Protocol/local-dev#112. That PR
adds a strict-order, strict-value assertion harness whose
`EXPECTED_EVENTS.network` begins with `council_formed` — which only
fires deterministically after this refactor's `backfillFromLedger` is in
place. Without this PR, the local-dev assertion would still fail on the
first event index.

## Test plan

- [x] `deno fmt --check` — clean (32 files)
- [x] `deno lint` — clean (21 files)
- [x] `deno task test` — **17 / 0**
- [x] Local-stack end-to-end against a fresh-deploy flow script
(testnet/main.ts + lifecycle/testnet-verify.ts): 5/5 network events
captured in correct order per script, both with `council_formed` landing
first via the back-fill path.
- [ ] Reviewer pass on the `notMoonlight` TTL choice (`PENDING_TTL_MS =
120 s`) — that's the upper bound between a council's on-chain deploy and
council-platform's `PUT /council/metadata` in the testnet scripts.
- [ ] Reviewer pass on the back-fill scope — `backfillFromLedger` scans
every watched contract from `minAdoptedLedger`, not only the
newly-adopted ones, because cross-council SAC events (XLM `transfer` /
`fee`) are mapped via the linkage table that `refreshTopology` just
installed.
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