|
|
@@ -99,6 +99,7 @@ async function boot(url: string) {
|
|
|
const ctx = new Context()
|
|
|
await ctx.plugin(SessionStore)
|
|
|
const fiber = await ctx.plugin(TelemetryOtel, {
|
|
|
+ mode: TelemetryMode.FULL,
|
|
|
exporter: { url, headers: { authorization: 'Bearer test-token' } },
|
|
|
})
|
|
|
return { ctx, fiber }
|
|
|
@@ -180,6 +181,7 @@ describe('TelemetryOtel wire', () => {
|
|
|
const ctx = new Context()
|
|
|
await ctx.plugin(SessionStore)
|
|
|
const fiber = await ctx.plugin(TelemetryOtel, {
|
|
|
+ mode: TelemetryMode.FULL,
|
|
|
exporter: { url },
|
|
|
processor: { scheduledDelayMillis: 10 },
|
|
|
})
|
|
|
@@ -210,6 +212,7 @@ describe('TelemetryOtel wire', () => {
|
|
|
const ctx = new Context()
|
|
|
await ctx.plugin(SessionStore)
|
|
|
const fiber = await ctx.plugin(TelemetryOtel, {
|
|
|
+ mode: TelemetryMode.FULL,
|
|
|
exporter: { url, timeoutMillis: 60_000 },
|
|
|
processor: { scheduledDelayMillis: 10, exportTimeoutMillis: 60_000 },
|
|
|
shutdownTimeoutMillis: 50,
|
|
|
@@ -238,6 +241,7 @@ describe('TelemetryOtel wire', () => {
|
|
|
// verbatim passthrough must hand it (and every other field) to the
|
|
|
// exporter rather than silently rebuilding url/headers only.
|
|
|
const fiber = await ctx.plugin(TelemetryOtel, {
|
|
|
+ mode: TelemetryMode.FULL,
|
|
|
exporter: { url, compression: 'gzip' },
|
|
|
} as Config)
|
|
|
const session = ctx.sessions.create(SessionId('gzip'), { meta: {} })
|
|
|
@@ -369,7 +373,7 @@ describe('TelemetryOtel wire', () => {
|
|
|
|
|
|
const fullCtx = new Context()
|
|
|
await fullCtx.plugin(SessionStore)
|
|
|
- const full = await fullCtx.plugin(TelemetryOtel, { exporter: { url } })
|
|
|
+ const full = await fullCtx.plugin(TelemetryOtel, { mode: TelemetryMode.FULL, exporter: { url } })
|
|
|
expect(fullCtx.telemetry.sharing).toBe('full')
|
|
|
await full.dispose()
|
|
|
|
|
|
@@ -385,20 +389,35 @@ describe('TelemetryOtel wire', () => {
|
|
|
expect(disabledCtx.telemetry.sharing).toBe('disabled')
|
|
|
await disabled.dispose()
|
|
|
|
|
|
+ // An omitted mode is DISABLED, so the default also shares nothing.
|
|
|
+ const defaultCtx = new Context()
|
|
|
+ await defaultCtx.plugin(SessionStore)
|
|
|
+ const defaulted = await defaultCtx.plugin(TelemetryOtel, {})
|
|
|
+ expect(defaultCtx.telemetry.sharing).toBe('disabled')
|
|
|
+ await defaulted.dispose()
|
|
|
+
|
|
|
// No record was emitted by any mode, so nothing reached the collector.
|
|
|
expect(captures).toEqual([])
|
|
|
})
|
|
|
|
|
|
- it('defaults direct construction to full delivery', async () => {
|
|
|
+ it('defaults direct construction to disabled delivery', async () => {
|
|
|
const { url, captures } = await mockCollector()
|
|
|
const ctx = new Context()
|
|
|
await ctx.plugin(SessionStore)
|
|
|
- new TelemetryOtel(ctx, { exporter: { url } })
|
|
|
+ const warn = vi.spyOn(ctx.logger, 'warn').mockImplementation(() => {})
|
|
|
+ new TelemetryOtel(ctx, {
|
|
|
+ exporter: { url },
|
|
|
+ processor: { maxExportBatchSize: 0 },
|
|
|
+ })
|
|
|
const session = ctx.sessions.create(SessionId('direct-default'), { meta: {} })
|
|
|
session.append('turn/start', { turn: 1 })
|
|
|
+ recordFeedback(session, 'local report')
|
|
|
await ctx.fiber.dispose()
|
|
|
|
|
|
- expect(eventTypes(captures)).toContain('turn/start')
|
|
|
+ expect(warn).toHaveBeenCalledWith(
|
|
|
+ 'session telemetry is DISABLED; nothing will be shared and this feedback remains local',
|
|
|
+ )
|
|
|
+ expect(captures).toEqual([])
|
|
|
})
|
|
|
})
|
|
|
|
|
|
@@ -407,23 +426,23 @@ describe('TelemetryOtel config fails loud', () => {
|
|
|
expectTypeOf<Config['mode']>().toEqualTypeOf<TelemetryMode | undefined>()
|
|
|
expectTypeOf<'FULL'>().not.toExtend<TelemetryMode>()
|
|
|
expectTypeOf<TelemetryMode.FULL>().toExtend<TelemetryMode>()
|
|
|
- expect(DEFAULT_TELEMETRY_MODE).toBe(TelemetryMode.FULL)
|
|
|
+ expect(DEFAULT_TELEMETRY_MODE).toBe(TelemetryMode.DISABLED)
|
|
|
expect(Config({}).mode).toBe(DEFAULT_TELEMETRY_MODE)
|
|
|
})
|
|
|
|
|
|
it.each([
|
|
|
- [{}, /exporter\.url is required/],
|
|
|
- [{ exporter: { url: '' } }, /exporter\.url is required/],
|
|
|
- [{ exporter: { url: 'not a url' } }, /not a valid URL/],
|
|
|
- [{ exporter: { url: 'ftp://collector' } }, /must be http\(s\)/],
|
|
|
+ [{ mode: TelemetryMode.FULL }, /exporter\.url is required/],
|
|
|
+ [{ mode: TelemetryMode.FULL, exporter: { url: '' } }, /exporter\.url is required/],
|
|
|
+ [{ mode: TelemetryMode.FULL, exporter: { url: 'not a url' } }, /not a valid URL/],
|
|
|
+ [{ mode: TelemetryMode.FULL, exporter: { url: 'ftp://collector' } }, /must be http\(s\)/],
|
|
|
[{ mode: TelemetryMode.FEEDBACK_ONLY }, /exporter\.url is required/],
|
|
|
[{ mode: 'INVALID' }, /INVALID/],
|
|
|
// The SDK accepts a non-positive batch size but its shutdown drain then
|
|
|
// splices empty batches forever — dispose would hang, so reject at load.
|
|
|
- [{ exporter: { url: 'http://c/v1/logs' }, processor: { maxExportBatchSize: 0 } }, /maxExportBatchSize/],
|
|
|
- [{ exporter: { url: 'http://c/v1/logs' }, processor: { maxExportBatchSize: 0.5 } }, /maxExportBatchSize/],
|
|
|
- [{ exporter: { url: 'http://c/v1/logs' }, shutdownTimeoutMillis: 0 }, /shutdownTimeoutMillis/],
|
|
|
- [{ exporter: { url: 'http://c/v1/logs' }, shutdownTimeoutMillis: Number.POSITIVE_INFINITY }, /shutdownTimeoutMillis/],
|
|
|
+ [{ mode: TelemetryMode.FULL, exporter: { url: 'http://c/v1/logs' }, processor: { maxExportBatchSize: 0 } }, /maxExportBatchSize/],
|
|
|
+ [{ mode: TelemetryMode.FULL, exporter: { url: 'http://c/v1/logs' }, processor: { maxExportBatchSize: 0.5 } }, /maxExportBatchSize/],
|
|
|
+ [{ mode: TelemetryMode.FULL, exporter: { url: 'http://c/v1/logs' }, shutdownTimeoutMillis: 0 }, /shutdownTimeoutMillis/],
|
|
|
+ [{ mode: TelemetryMode.FULL, exporter: { url: 'http://c/v1/logs' }, shutdownTimeoutMillis: Number.POSITIVE_INFINITY }, /shutdownTimeoutMillis/],
|
|
|
])('rejects %j at plugin load', async (config, message) => {
|
|
|
const ctx = new Context()
|
|
|
await ctx.plugin(SessionStore)
|
|
|
@@ -488,7 +507,7 @@ describe('dsh-session-telemetry-otel real-load-path guard', () => {
|
|
|
const unwrapped = loader.unwrapExports(module) as Parameters<Context['plugin']>[0]
|
|
|
const ctx = new Context()
|
|
|
await ctx.plugin(SessionStore)
|
|
|
- const fiber = await ctx.plugin(unwrapped, { exporter: { url } })
|
|
|
+ const fiber = await ctx.plugin(unwrapped, { mode: TelemetryMode.FULL, exporter: { url } })
|
|
|
expect(ctx.telemetry).toBeInstanceOf(TelemetryOtel)
|
|
|
await fiber.dispose()
|
|
|
})
|