Simplify inert analytics compatibility layer
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
* Shared analytics configuration
|
* Shared analytics configuration
|
||||||
*
|
*
|
||||||
* Common logic for determining when analytics should be disabled
|
* Common logic for determining when analytics should be disabled
|
||||||
* across all analytics systems (Datadog, 1P)
|
* across the remaining local analytics compatibility surfaces.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { isEnvTruthy } from '../../utils/envUtils.js'
|
import { isEnvTruthy } from '../../utils/envUtils.js'
|
||||||
@@ -31,7 +31,7 @@ export function isAnalyticsDisabled(): boolean {
|
|||||||
*
|
*
|
||||||
* Unlike isAnalyticsDisabled(), this does NOT block on 3P providers
|
* Unlike isAnalyticsDisabled(), this does NOT block on 3P providers
|
||||||
* (Bedrock/Vertex/Foundry). The survey is a local UI prompt with no
|
* (Bedrock/Vertex/Foundry). The survey is a local UI prompt with no
|
||||||
* transcript data — enterprise customers capture responses via OTEL.
|
* transcript upload in this fork.
|
||||||
*/
|
*/
|
||||||
export function isFeedbackSurveyDisabled(): boolean {
|
export function isFeedbackSurveyDisabled(): boolean {
|
||||||
return process.env.NODE_ENV === 'test' || isTelemetryDisabled()
|
return process.env.NODE_ENV === 'test' || isTelemetryDisabled()
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
/**
|
/**
|
||||||
* Analytics service - public API for event logging
|
* Analytics service - public API for event logging
|
||||||
*
|
*
|
||||||
* This module serves as the main entry point for analytics events in Claude CLI.
|
* The open build intentionally ships without product telemetry. We keep this
|
||||||
*
|
* module as a compatibility boundary so existing call sites can remain
|
||||||
* DESIGN: This module has NO dependencies to avoid import cycles.
|
* unchanged while all analytics become inert.
|
||||||
* Events are queued until attachAnalyticsSink() is called during app initialization.
|
|
||||||
* The sink handles routing to Datadog and 1P event logging.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -27,15 +25,14 @@ export type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS = never
|
|||||||
*/
|
*/
|
||||||
export type AnalyticsMetadata_I_VERIFIED_THIS_IS_PII_TAGGED = never
|
export type AnalyticsMetadata_I_VERIFIED_THIS_IS_PII_TAGGED = never
|
||||||
|
|
||||||
// Internal type for logEvent metadata in the local no-op sink.
|
export function stripProtoFields<V>(
|
||||||
type LogEventMetadata = { [key: string]: boolean | number | undefined }
|
metadata: Record<string, V>,
|
||||||
|
): Record<string, V> {
|
||||||
type QueuedEvent = {
|
return metadata
|
||||||
eventName: string
|
|
||||||
metadata: LogEventMetadata
|
|
||||||
async: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LogEventMetadata = { [key: string]: boolean | number | undefined }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sink interface for the analytics backend
|
* Sink interface for the analytics backend
|
||||||
*/
|
*/
|
||||||
@@ -47,97 +44,26 @@ export type AnalyticsSink = {
|
|||||||
) => Promise<void>
|
) => Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
// Event queue for events logged before sink is attached
|
export function attachAnalyticsSink(_newSink: AnalyticsSink): void {}
|
||||||
const eventQueue: QueuedEvent[] = []
|
|
||||||
|
|
||||||
// Sink - initialized during app startup
|
|
||||||
let sink: AnalyticsSink | null = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attach the analytics sink that will receive all events.
|
|
||||||
* Queued events are drained asynchronously via queueMicrotask to avoid
|
|
||||||
* adding latency to the startup path.
|
|
||||||
*
|
|
||||||
* Idempotent: if a sink is already attached, this is a no-op. This allows
|
|
||||||
* calling from both the preAction hook (for subcommands) and setup() (for
|
|
||||||
* the default command) without coordination.
|
|
||||||
*/
|
|
||||||
export function attachAnalyticsSink(newSink: AnalyticsSink): void {
|
|
||||||
if (sink !== null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
sink = newSink
|
|
||||||
|
|
||||||
// Drain the queue asynchronously to avoid blocking startup
|
|
||||||
if (eventQueue.length > 0) {
|
|
||||||
const queuedEvents = [...eventQueue]
|
|
||||||
eventQueue.length = 0
|
|
||||||
|
|
||||||
// Log queue size for ants to help debug analytics initialization timing
|
|
||||||
if (process.env.USER_TYPE === 'ant') {
|
|
||||||
sink.logEvent('analytics_sink_attached', {
|
|
||||||
queued_event_count: queuedEvents.length,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
queueMicrotask(() => {
|
|
||||||
for (const event of queuedEvents) {
|
|
||||||
if (event.async) {
|
|
||||||
void sink!.logEventAsync(event.eventName, event.metadata)
|
|
||||||
} else {
|
|
||||||
sink!.logEvent(event.eventName, event.metadata)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log an event to analytics backends (synchronous)
|
* Log an event to analytics backends (synchronous)
|
||||||
*
|
|
||||||
* Events may be sampled based on the 'tengu_event_sampling_config' dynamic config.
|
|
||||||
* When sampled, the sample_rate is added to the event metadata.
|
|
||||||
*
|
|
||||||
* If no sink is attached, events are queued and drained when the sink attaches.
|
|
||||||
*/
|
*/
|
||||||
export function logEvent(
|
export function logEvent(
|
||||||
eventName: string,
|
_eventName: string,
|
||||||
// intentionally no strings unless AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
_metadata: LogEventMetadata,
|
||||||
// to avoid accidentally logging code/filepaths
|
): void {}
|
||||||
metadata: LogEventMetadata,
|
|
||||||
): void {
|
|
||||||
if (sink === null) {
|
|
||||||
eventQueue.push({ eventName, metadata, async: false })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
sink.logEvent(eventName, metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log an event to analytics backends (asynchronous)
|
* Log an event to analytics backends (asynchronous)
|
||||||
*
|
|
||||||
* Events may be sampled based on the 'tengu_event_sampling_config' dynamic config.
|
|
||||||
* When sampled, the sample_rate is added to the event metadata.
|
|
||||||
*
|
|
||||||
* If no sink is attached, events are queued and drained when the sink attaches.
|
|
||||||
*/
|
*/
|
||||||
export async function logEventAsync(
|
export async function logEventAsync(
|
||||||
eventName: string,
|
_eventName: string,
|
||||||
// intentionally no strings, to avoid accidentally logging code/filepaths
|
_metadata: LogEventMetadata,
|
||||||
metadata: LogEventMetadata,
|
): Promise<void> {}
|
||||||
): Promise<void> {
|
|
||||||
if (sink === null) {
|
|
||||||
eventQueue.push({ eventName, metadata, async: true })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
await sink.logEventAsync(eventName, metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset analytics state for testing purposes only.
|
* Reset analytics state for testing purposes only.
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export function _resetForTesting(): void {
|
export function _resetForTesting(): void {}
|
||||||
sink = null
|
|
||||||
eventQueue.length = 0
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,31 +1,10 @@
|
|||||||
/**
|
/**
|
||||||
* Analytics sink implementation
|
* Analytics sink implementation
|
||||||
*
|
*
|
||||||
* This open build keeps the analytics sink boundary for compatibility, but
|
* Telemetry sinks are disabled in this build. The exported functions remain so
|
||||||
* drops all queued analytics events locally instead of routing them onward.
|
* startup code does not need to special-case the open build.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { attachAnalyticsSink } from './index.js'
|
export function initializeAnalyticsSink(): void {
|
||||||
|
|
||||||
type LogEventMetadata = { [key: string]: boolean | number | undefined }
|
|
||||||
|
|
||||||
function logEventImpl(
|
|
||||||
_eventName: string,
|
|
||||||
_metadata: LogEventMetadata,
|
|
||||||
): void {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
function logEventAsyncImpl(
|
|
||||||
_eventName: string,
|
|
||||||
_metadata: LogEventMetadata,
|
|
||||||
): Promise<void> {
|
|
||||||
return Promise.resolve()
|
|
||||||
}
|
|
||||||
|
|
||||||
export function initializeAnalyticsSink(): void {
|
|
||||||
attachAnalyticsSink({
|
|
||||||
logEvent: logEventImpl,
|
|
||||||
logEventAsync: logEventAsyncImpl,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -368,7 +368,7 @@ export async function setup(
|
|||||||
) // Start team memory sync watcher
|
) // Start team memory sync watcher
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
initSinks() // Attach error log + analytics sinks and drain queued events
|
initSinks() // Attach error log sink and analytics compatibility stubs
|
||||||
|
|
||||||
// Session-success-rate denominator. Emit immediately after the analytics
|
// Session-success-rate denominator. Emit immediately after the analytics
|
||||||
// sink is attached — before any parsing, fetching, or I/O that could throw.
|
// sink is attached — before any parsing, fetching, or I/O that could throw.
|
||||||
|
|||||||
@@ -2,10 +2,9 @@ import { initializeAnalyticsSink } from '../services/analytics/sink.js'
|
|||||||
import { initializeErrorLogSink } from './errorLogSink.js'
|
import { initializeErrorLogSink } from './errorLogSink.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attach error log and analytics sinks, draining any events queued before
|
* Attach error log and analytics compatibility sinks. Both inits are
|
||||||
* attachment. Both inits are idempotent. Called from setup() for the default
|
* idempotent. Called from setup() for the default command; other entrypoints
|
||||||
* command; other entrypoints (subcommands, daemon, bridge) call this directly
|
* (subcommands, daemon, bridge) call this directly since they bypass setup().
|
||||||
* since they bypass setup().
|
|
||||||
*
|
*
|
||||||
* Leaf module — kept out of setup.ts to avoid the setup → commands → bridge
|
* Leaf module — kept out of setup.ts to avoid the setup → commands → bridge
|
||||||
* → setup import cycle.
|
* → setup import cycle.
|
||||||
|
|||||||
Reference in New Issue
Block a user