feat(telemetry): collect PostHog $exception error reports in CLI and daemon (#262)

* feat(telemetry): add node exception reporter

* feat(telemetry): report node cli exceptions

* feat(telemetry): add daemon exception reporter

* feat(telemetry): report daemon exceptions

* docs(telemetry): document error reports

* fix(telemetry): pass redaction snapshots from node call sites

* test(telemetry): verify prepared node exception payload

* fix(telemetry): close daemon exception lifecycle gaps

* test(telemetry): verify prepared daemon exception payload

* test(telemetry): close error collection acceptance gaps

* test(telemetry): close posthog exception acceptance gaps
This commit is contained in:
Andrey Avtomonov 2026-06-05 19:36:21 +02:00 committed by GitHub
parent c3d8cedb0b
commit fb7b94b60e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 2870 additions and 140 deletions

View file

@ -10,6 +10,12 @@ import type { KtxConnectionDriver, KtxScanConnector } from '../src/context/scan/
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { runKtxConnection } from '../src/connection.js';
const reportExceptionMock = vi.hoisted(() => vi.fn(async () => {}));
vi.mock('../src/telemetry/exception.js', () => ({
reportException: reportExceptionMock,
}));
function stripAnsi(s: string): string {
return s.replace(/\[[0-9;]*m/g, '');
}
@ -72,6 +78,7 @@ describe('runKtxConnection', () => {
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), 'ktx-cli-connection-'));
reportExceptionMock.mockClear();
});
afterEach(async () => {
@ -165,12 +172,13 @@ describe('runKtxConnection', () => {
it('records the raw errorDetail in connection_test telemetry when a native test fails', async () => {
vi.stubEnv('KTX_TELEMETRY_DEBUG', '1');
vi.stubEnv('CI', '');
vi.stubEnv('DATABASE_URL', 'postgres://svc:db-url-password@db.example.test/analytics'); // pragma: allowlist secret
const projectDir = join(tempDir, 'project');
await initKtxProject({ projectDir });
await writeConnections(projectDir, {
warehouse: { driver: 'sqlite' },
warehouse: { driver: 'postgres', url: 'env:DATABASE_URL' },
});
const { connector } = nativeConnector('sqlite', { success: false, error: 'database file is unreadable' });
const { connector } = nativeConnector('postgres', { success: false, error: 'database file is unreadable' });
const io = makeIo();
const code = await runKtxConnection({ command: 'test', projectDir, connectionId: 'warehouse' }, io.io, {
@ -181,6 +189,16 @@ describe('runKtxConnection', () => {
expect(io.stderr()).toContain('"event":"connection_test"');
expect(io.stderr()).toContain('"outcome":"error"');
expect(io.stderr()).toContain('"errorDetail":"database file is unreadable"');
expect(reportExceptionMock).toHaveBeenCalledWith(
expect.objectContaining({
context: expect.objectContaining({ source: 'connection test', handled: true, fatal: false }),
projectDir,
redactionSecrets: expect.arrayContaining([
'postgres://svc:db-url-password@db.example.test/analytics', // pragma: allowlist secret
'db-url-password',
]),
}),
);
});
it('preserves the driver error class and code in connection_test telemetry', async () => {