Problem
Wide event tracing lives in `@songtrivia/logger` (~600 LOC) and `songtrivia/convex/lib/traced.ts` (~300 LOC). It was explicitly deferred from v1 extraction. Now with `@vllnt/convex-helpers` being built, this is the right time — traced Convex functions need wide events from `@vllnt/logger`, not `@songtrivia/logger`.
Current State
```
@songtrivia/logger (922 LOC, monolith)
├── Core logging → EXTRACTED to @vllnt/logger v0.1.2 ✓
├── Wide events → THIS ISSUE (v2)
│ ├── WideEventBuilder (accumulate context, emit once)
│ ├── withWideEvent (try/catch/finally wrapper)
│ ├── createWideEventLogger (factory)
│ ├── Tail sampling (configurable rates)
│ ├── PII/secrets redaction (regex-based)
│ └── Context size limits (100KB hard cap)
└── Client logger → Separate concern (Axiom-specific, stays in songtrivia)
songtrivia/convex/lib/traced.ts (283 LOC)
├── tracedHandler() → wraps handler with ctx.evt
├── createTracedFunctions() → factory for traced query/mutation/action/httpAction
└── Traced context types → TracedQueryCtx, TracedMutationCtx, TracedActionCtx
```
Proposed Architecture
@vllnt/logger v2 additions
New subpath: `@vllnt/logger/wide`
```
src/wide.ts
├── Types
│ ├── WideEvent — one comprehensive event per request
│ ├── WideEventBuilder — accumulate context, emit once
│ ├── WideEventLoggerFactory — factory creates builders
│ └── SamplingConfig — tail sampling configuration
│
├── Core
│ ├── createWideEventLogger(scope) → WideEventLoggerFactory
│ ├── withWideEvent(event, fn) → execute + guaranteed emit
│ └── createWideEventBuilder() → internal implementation
│
├── Sampling
│ ├── getSamplingConfig() → from env vars
│ └── shouldSampleEvent(event) → tail sampling decision
│
├── Safety
│ ├── PII/secrets redaction → regex-based field sanitization
│ ├── Context size limits → 100KB hard cap with truncation
│ ├── Circular reference handling → safe serialization
│ └── Double-emit protection → no-op + warning
```
Relationship to @vllnt/convex-helpers
```
@vllnt/logger/wide ← generic wide event primitives (this issue)
│
│ required peer dep (for tracing/ only)
▼
@vllnt/convex-helpers/tracing ← Convex-specific wrappers (vllnt/convex-helpers#1)
├── tracedHandler()
├── createTracedFunctions()
└── TracedQueryCtx types
@vllnt/convex-helpers/{auth,security,http} ← uses @vllnt/logger as optional peer dep
falls back to console.* if not installed
```
Dependency rules:
- Logger NEVER imports convex-helpers (one-way, no cycle)
- `@vllnt/logger` is optional peer dep for most convex-helpers modules (console fallback)
- `@vllnt/logger/wide` is required peer dep for `convex-helpers/tracing` only
- Wide events are pure primitives — no Convex knowledge in this package
Boundary: logger owns the wide event primitive. convex-helpers owns the Convex function wrappers that use it.
Scope
In scope (this issue)
Out of scope
- Convex traced function wrappers (`tracedHandler`, `createTracedFunctions`) → `@vllnt/convex-helpers/tracing` (vllnt/convex-helpers#1)
- Client-side logging (Axiom, platform detection) → stays in `@songtrivia/logger`
- OpenTelemetry integration → future
- Distributed tracing (cross-service request correlation) → future
Source Code
All code exists and is battle-tested in `@songtrivia/logger`:
| Feature |
Source |
LOC |
| WideEvent types |
`songtrivia/packages/logger/src/index.ts:79-117` |
~40 |
| WideEventBuilder impl |
`songtrivia/packages/logger/src/index.ts:732-857` |
~125 |
| withWideEvent |
`songtrivia/packages/logger/src/index.ts:896-921` |
~25 |
| createWideEventLogger |
`songtrivia/packages/logger/src/index.ts:869-882` |
~15 |
| Tail sampling |
`songtrivia/packages/logger/src/index.ts:648-695` |
~50 |
| PII redaction |
`songtrivia/packages/logger/src/index.ts:581-623` |
~45 |
| Size limits + safe serialize |
`songtrivia/packages/logger/src/index.ts:697-726` |
~30 |
| Tests |
`songtrivia/packages/backend/convex/lib/tests/lib.wideEvent.test.ts` |
~100 |
| Total |
|
~430 |
Technical Decisions
| Decision |
Choice |
Why |
| Subpath |
`@vllnt/logger/wide` |
Tree-shakeable, doesn't bloat core import |
| Output |
Reuses existing `LogOutput` type from core |
Wide events emit through same output pipeline |
| Env vars |
`LOG_SAMPLE_*` prefix |
Matches songtrivia battle-tested config |
| Redaction |
Regex patterns |
Simple, extensible, no deps |
| Size limit |
100KB hard cap |
Prevents OOM in high-context handlers |
| Side effects |
Zero module-level side effects |
Safe in Convex, edge, browser |
Migration Path
After shipping:
- `@songtrivia/logger` imports `@vllnt/logger/wide` instead of inline wide events
- `songtrivia/convex/lib/traced.ts` imports from `@vllnt/logger/wide` instead of `@songtrivia/logger`
- `traced.ts` moves to `@vllnt/convex-helpers/tracing` (vllnt/convex-helpers#1)
- `@songtrivia/logger` becomes thin wrapper: re-exports `@vllnt/logger` + `@vllnt/logger/wide` + client-specific code
Success Criteria
Problem
Wide event tracing lives in `@songtrivia/logger` (~600 LOC) and `songtrivia/convex/lib/traced.ts` (~300 LOC). It was explicitly deferred from v1 extraction. Now with `@vllnt/convex-helpers` being built, this is the right time — traced Convex functions need wide events from `@vllnt/logger`, not `@songtrivia/logger`.
Current State
```
@songtrivia/logger (922 LOC, monolith)
├── Core logging → EXTRACTED to @vllnt/logger v0.1.2 ✓
├── Wide events → THIS ISSUE (v2)
│ ├── WideEventBuilder (accumulate context, emit once)
│ ├── withWideEvent (try/catch/finally wrapper)
│ ├── createWideEventLogger (factory)
│ ├── Tail sampling (configurable rates)
│ ├── PII/secrets redaction (regex-based)
│ └── Context size limits (100KB hard cap)
└── Client logger → Separate concern (Axiom-specific, stays in songtrivia)
songtrivia/convex/lib/traced.ts (283 LOC)
├── tracedHandler() → wraps handler with ctx.evt
├── createTracedFunctions() → factory for traced query/mutation/action/httpAction
└── Traced context types → TracedQueryCtx, TracedMutationCtx, TracedActionCtx
```
Proposed Architecture
@vllnt/logger v2 additions
New subpath: `@vllnt/logger/wide`
```
src/wide.ts
├── Types
│ ├── WideEvent — one comprehensive event per request
│ ├── WideEventBuilder — accumulate context, emit once
│ ├── WideEventLoggerFactory — factory creates builders
│ └── SamplingConfig — tail sampling configuration
│
├── Core
│ ├── createWideEventLogger(scope) → WideEventLoggerFactory
│ ├── withWideEvent(event, fn) → execute + guaranteed emit
│ └── createWideEventBuilder() → internal implementation
│
├── Sampling
│ ├── getSamplingConfig() → from env vars
│ └── shouldSampleEvent(event) → tail sampling decision
│
├── Safety
│ ├── PII/secrets redaction → regex-based field sanitization
│ ├── Context size limits → 100KB hard cap with truncation
│ ├── Circular reference handling → safe serialization
│ └── Double-emit protection → no-op + warning
```
Relationship to @vllnt/convex-helpers
```
@vllnt/logger/wide ← generic wide event primitives (this issue)
│
│ required peer dep (for tracing/ only)
▼
@vllnt/convex-helpers/tracing ← Convex-specific wrappers (vllnt/convex-helpers#1)
├── tracedHandler()
├── createTracedFunctions()
└── TracedQueryCtx types
@vllnt/convex-helpers/{auth,security,http} ← uses @vllnt/logger as optional peer dep
falls back to console.* if not installed
```
Dependency rules:
Boundary: logger owns the wide event primitive. convex-helpers owns the Convex function wrappers that use it.
Scope
In scope (this issue)
Out of scope
Source Code
All code exists and is battle-tested in `@songtrivia/logger`:
Technical Decisions
Migration Path
After shipping:
Success Criteria