Skip to content

Commit b3f4df0

Browse files
committed
feat(supervisor): emit start_time on wide events
1 parent d600124 commit b3f4df0

4 files changed

Lines changed: 42 additions & 0 deletions

File tree

apps/supervisor/src/wideEvents/emit.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,26 @@ describe("emit", () => {
3535
expect(out.duration_ms).toBe(5);
3636
});
3737

38+
it("emits start_time as an ISO timestamp set by newState", () => {
39+
const s = newState({ service: "supervisor", env: {} });
40+
s.statusCode = 200;
41+
s.ok = true;
42+
const out = captureEmit(s);
43+
expect(typeof out.start_time).toBe("string");
44+
// Microsecond-precision RFC3339 (6 fractional digits), parseable as a date.
45+
expect(out.start_time).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}Z$/);
46+
expect(Number.isNaN(new Date(out.start_time as string).getTime())).toBe(false);
47+
});
48+
49+
it("omits start_time when unset", () => {
50+
const s = newState({ service: "supervisor", env: {} });
51+
delete s.startTime;
52+
s.statusCode = 200;
53+
s.ok = true;
54+
const out = captureEmit(s);
55+
expect(out).not.toHaveProperty("start_time");
56+
});
57+
3858
it("omits empty optional fields", () => {
3959
const s = newState({ service: "supervisor", env: {} });
4060
s.statusCode = 200;

apps/supervisor/src/wideEvents/emit.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export function emit(state: State): void {
2121
};
2222

2323
if (state.traceId) out.trace_id = state.traceId;
24+
appendIfSet(out, "start_time", state.startTime);
2425
appendIfSet(out, "service", state.service);
2526
appendIfSet(out, "version", state.version);
2627
appendIfSet(out, "commit_sha", state.commitSha);

apps/supervisor/src/wideEvents/new.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export function newState(opts: NewStateOptions): State {
5858
const requestId = isValidRequestId(inbound) ? inbound : newRequestId();
5959

6060
return {
61+
startTime: nowRfc3339(),
6162
requestId,
6263
traceId: parseTraceId(traceparent),
6364
traceparent,
@@ -80,3 +81,16 @@ export function newState(opts: NewStateOptions): State {
8081
function newRequestId(): string {
8182
return "req-" + randomBytes(16).toString("hex");
8283
}
84+
85+
/**
86+
* Current wall-clock time as an RFC3339 string with microsecond precision.
87+
* `Date.toISOString()` only has millisecond resolution, which is too coarse to
88+
* order multiple wide events emitted within the same millisecond.
89+
* `performance.timeOrigin + performance.now()` gives a sub-millisecond wall-clock
90+
* reading; we append the microsecond digits to the millisecond ISO string.
91+
*/
92+
function nowRfc3339(): string {
93+
const ms = performance.timeOrigin + performance.now();
94+
const micros = Math.floor((ms % 1) * 1000); // microseconds within the millisecond (0..999)
95+
return new Date(ms).toISOString().slice(0, -1) + String(micros).padStart(3, "0") + "Z";
96+
}

apps/supervisor/src/wideEvents/state.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@
55
* events stay compact.
66
*/
77
export type State = {
8+
/**
9+
* Wall-clock time the event began, as an ISO-8601 string. Emitted as
10+
* `start_time` so log collection orders events by when work started rather
11+
* than by the collector's ingestion time.
12+
*/
13+
startTime?: string;
14+
815
// Cross-stack correlation.
916
requestId: string;
1017
traceId: string;

0 commit comments

Comments
 (0)