feat: per-request tracing via BSB createTrace

Each HTTP request gets a fresh BSB trace (not a child span of init).
onRequest creates trace, stores on event.context.obs. onError logs
with trace context. onResponse ends the trace. 4xx logged as warn,
5xx as error. H3EventContext typed with obs field.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mitchell R 2026-05-26 01:02:26 +02:00
parent 0113e4e54a
commit 57348b14ab
No known key found for this signature in database
3 changed files with 45 additions and 2 deletions

View file

@ -195,16 +195,37 @@ export class Plugin extends BSBService<InstanceType<typeof Config>, typeof Event
otaImportApiKey: this.config.otaImportApiKey || undefined,
};
const self = this;
const app = new H3({
onRequest: (event) => {
const method = event.req.method ?? "GET";
const path = event.req.url ?? "/";
event.context.obs = self.createTrace(`${method} ${path}`, {
"http.method": method,
"http.url": path,
});
},
onError: (error, event) => {
const reqObs = event.context.obs ?? obs;
const status = error.status ?? 500;
const path = event.req.url ?? "unknown";
if (status >= 500) {
obs.log.warn("HTTP {status} {path}: {err}", {
reqObs.log.error("HTTP {status} {path}: {err}", {
status,
path,
err: error.message ?? String(error),
});
} else if (status >= 400) {
reqObs.log.warn("HTTP {status} {path}: {err}", {
status,
path,
err: error.message ?? String(error),
});
}
},
onResponse: (_response, event) => {
if (event.context.obs) {
event.context.obs.end();
}
},
});

View file

@ -16,6 +16,7 @@ declare module "h3" {
user?: User;
session?: Session;
apiKeyPrefix?: string;
obs?: import("@bsb/base").Observable;
}
}

View file

@ -164,16 +164,37 @@ export class Plugin extends BSBService<InstanceType<typeof Config>, typeof Event
},
);
const self = this;
const app = new H3({
onRequest: (event) => {
const method = event.req.method ?? "GET";
const path = event.req.url ?? "/";
event.context.obs = self.createTrace(`${method} ${path}`, {
"http.method": method,
"http.url": path,
});
},
onError: (error, event) => {
const reqObs = event.context.obs ?? obs;
const status = error.status ?? 500;
const path = event.req.url ?? "unknown";
if (status >= 500) {
obs.log.warn("HTTP {status} {path}: {err}", {
reqObs.log.error("HTTP {status} {path}: {err}", {
status,
path,
err: error.message ?? String(error),
});
} else if (status >= 400) {
reqObs.log.warn("HTTP {status} {path}: {err}", {
status,
path,
err: error.message ?? String(error),
});
}
},
onResponse: (_response, event) => {
if (event.context.obs) {
event.context.obs.end();
}
},
});