/** * Copyright (c) 2025 Databricks Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { EventEmitter } from 'events'; import IClientContext from '../contracts/IClientContext'; import { TelemetryEvent, TelemetryEventType, DriverConfiguration } from './types'; /** * Typed map of event-type → listener payload shape. Keeps `on`/`off` calls * structurally typed: `emitter.on(TelemetryEventType.ERROR, (e) => …)` infers * `e: TelemetryEvent` instead of `any`. Avoids the EventEmitter-default * `(...args: any[]) => void` trap where a typo in the event name silently * registers a listener that never fires. */ export interface TelemetryEventMap { [TelemetryEventType.CONNECTION_OPEN]: (event: TelemetryEvent) => void; [TelemetryEventType.CONNECTION_CLOSE]: (event: TelemetryEvent) => void; [TelemetryEventType.STATEMENT_START]: (event: TelemetryEvent) => void; [TelemetryEventType.STATEMENT_COMPLETE]: (event: TelemetryEvent) => void; [TelemetryEventType.CLOUDFETCH_CHUNK]: (event: TelemetryEvent) => void; [TelemetryEventType.ERROR]: (event: TelemetryEvent) => void; } /** * EventEmitter for driver telemetry. * Emits events at key driver operations. * * CRITICAL REQUIREMENT: ALL exceptions must be caught and logged at LogLevel.debug ONLY * (never warn/error) to avoid customer anxiety. NO console logging allowed - only IDBSQLLogger. * * All emit methods funnel through `emitWrapped`, which holds the * try/catch/debug-log scaffold. The per-method bodies do nothing but build * the event shape — adding a new event type is a one-method change. */ export default class TelemetryEventEmitter extends EventEmitter { private context; private enabled; constructor(context: IClientContext); on(eventName: K, listener: TelemetryEventMap[K]): this; off(eventName: K, listener: TelemetryEventMap[K]): this; once(eventName: K, listener: TelemetryEventMap[K]): this; /** * Build-and-emit helper. The per-event `build` callback constructs the * payload; everything else (enabled check, try/catch, swallow-and-log) * lives here so the wrapping cannot drift between event types. */ private emitWrapped; emitConnectionOpen(data: { sessionId: string; workspaceId?: string; /** * The full driver-configuration block (~1KB). Static for the process — * emit sites SHOULD pass it once per client and pass `undefined` on * subsequent CONNECTION_OPEN events (one client may open many sessions). * The aggregator and exporter both treat `undefined` as "no change since * the last metric on the same session lineage". */ driverConfig?: DriverConfiguration; latencyMs: number; }): void; emitConnectionClose(data: { sessionId: string; latencyMs: number; }): void; emitStatementStart(data: { statementId: string; sessionId?: string; operationType?: string; }): void; emitStatementComplete(data: { statementId: string; sessionId?: string; latencyMs?: number; resultFormat?: string; chunkCount?: number; bytesDownloaded?: number; pollCount?: number; }): void; emitCloudFetchChunk(data: { statementId: string; chunkIndex: number; latencyMs?: number; bytes: number; compressed?: boolean; }): void; /** * Emit an error event. * * Redaction happens HERE — not at the exporter — so any in-process listener * (this class extends `EventEmitter` and `getTelemetryEmitter()` is reachable * from any consumer of `@databricks/sql`) sees the same redacted strings * the export pipeline does. `redactSensitive` strips Bearer/Basic, Databricks * token prefixes, JWTs, JSON-encoded secrets, URL userinfo, and common * username-bearing filesystem paths, then caps length. * * `errorMessage` is also redacted, not only `errorStack` — operation error * messages can carry query fragments, table names, parameter values that the * SECRET_PATTERNS regex must scrub before they're emitted anywhere. */ emitError(data: { statementId?: string; sessionId?: string; errorName: string; errorMessage: string; errorStack?: string; isTerminal: boolean; }): void; }