mirror of
https://github.com/cline/cline.git
synced 2026-09-09 06:45:53 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1ddde625f | ||
|
|
bcf1c3b097 | ||
|
|
0e759ec6af | ||
|
|
8dc59eee82 | ||
|
|
d28120e1c5 | ||
|
|
898e0351cd | ||
|
|
66e26ee944 | ||
|
|
3eb4e3754e | ||
|
|
94d6241a99 | ||
|
|
455897e551 |
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Added support for reinitializing the telemetry service and all providers inside of it.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Fixed a bug and set up how environmental variables are preceded and overwritten by the remote configuration settings.
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getValidOpenTelemetryConfig } from "@/shared/services/config/otel-config"
|
||||
import { getValidOpenTelemetryConfig, OtelSettingsProvider } from "@/shared/services/config/otel-config"
|
||||
import { isPostHogConfigValid, posthogConfig } from "@/shared/services/config/posthog-config"
|
||||
import { Logger } from "../logging/Logger"
|
||||
import type { ITelemetryProvider } from "./providers/ITelemetryProvider"
|
||||
@@ -27,10 +27,11 @@ export class TelemetryProviderFactory {
|
||||
/**
|
||||
* Creates multiple telemetry providers based on configuration
|
||||
* Supports dual tracking during transition period
|
||||
* @param settingsProvider Optional settings provider for OpenTelemetry configuration
|
||||
* @returns Array of ITelemetryProvider instances
|
||||
*/
|
||||
public static async createProviders(): Promise<ITelemetryProvider[]> {
|
||||
const configs = TelemetryProviderFactory.getDefaultConfigs()
|
||||
public static async createProviders(settingsProvider?: OtelSettingsProvider): Promise<ITelemetryProvider[]> {
|
||||
const configs = TelemetryProviderFactory.getDefaultConfigs(settingsProvider)
|
||||
const providers: ITelemetryProvider[] = await Promise.all(configs.map((c) => TelemetryProviderFactory.createProvider(c)))
|
||||
|
||||
// Fallback to no-op if no providers available
|
||||
@@ -79,15 +80,15 @@ export class TelemetryProviderFactory {
|
||||
|
||||
/**
|
||||
* Gets the default telemetry provider configuration
|
||||
* @returns Default configuration using available providers
|
||||
* @param settingsProvider Optional settings provider for OpenTelemetry configuration
|
||||
* @returns Default configuration using available providers
|
||||
*/
|
||||
public static getDefaultConfigs(): TelemetryProviderConfig[] {
|
||||
public static getDefaultConfigs(settingsProvider?: OtelSettingsProvider): TelemetryProviderConfig[] {
|
||||
const configs: TelemetryProviderConfig[] = []
|
||||
if (isPostHogConfigValid(posthogConfig)) {
|
||||
configs.push({ type: "posthog", ...posthogConfig })
|
||||
}
|
||||
const otelConfig = getValidOpenTelemetryConfig()
|
||||
const otelConfig = getValidOpenTelemetryConfig(settingsProvider)
|
||||
if (otelConfig) {
|
||||
configs.push({ type: "opentelemetry", ...otelConfig })
|
||||
}
|
||||
@@ -131,6 +132,11 @@ export class NoOpTelemetryProvider implements ITelemetryProvider {
|
||||
}
|
||||
}
|
||||
|
||||
public async reinitializeIfNeeded(): Promise<boolean> {
|
||||
Logger.info("[NoOpTelemetryProvider] reinitializeIfNeeded called (no-op)")
|
||||
return false
|
||||
}
|
||||
|
||||
public async dispose(): Promise<void> {
|
||||
Logger.info("[NoOpTelemetryProvider] Disposing")
|
||||
}
|
||||
|
||||
@@ -3,12 +3,15 @@ import type { BrowserSettings } from "@shared/BrowserSettings"
|
||||
import { ShowMessageType } from "@shared/proto/host/window"
|
||||
import type { TaskFeedbackType } from "@shared/WebviewMessage"
|
||||
import * as os from "os"
|
||||
import { StateManager } from "@/core/storage/StateManager"
|
||||
import { ClineAccountUserInfo } from "@/services/auth/AuthService"
|
||||
import { Setting } from "@/shared/proto/index.host"
|
||||
import { OtelSettingsProvider } from "@/shared/services/config/otel-config"
|
||||
import { Mode } from "@/shared/storage/types"
|
||||
import { version as extensionVersion } from "../../../package.json"
|
||||
import { setDistinctId } from "../logging/distinctId"
|
||||
import type { ITelemetryProvider, TelemetryProperties } from "./providers/ITelemetryProvider"
|
||||
import { OpenTelemetryClientProvider } from "./providers/opentelemetry/OpenTelemetryClientProvider"
|
||||
import { TelemetryProviderFactory } from "./TelemetryProviderFactory"
|
||||
|
||||
/**
|
||||
@@ -218,7 +221,26 @@ export class TelemetryService {
|
||||
}
|
||||
|
||||
public static async create(): Promise<TelemetryService> {
|
||||
const providers = await TelemetryProviderFactory.createProviders()
|
||||
// Get StateManager instance to pass as settings provider
|
||||
let settingsProvider: OtelSettingsProvider | undefined
|
||||
try {
|
||||
const stateManager = StateManager.get()
|
||||
// Create a settings provider adapter that implements OtelSettingsProvider interface
|
||||
settingsProvider = {
|
||||
getGlobalSettingsKey: <K extends string>(key: K) => {
|
||||
return stateManager.getGlobalSettingsKey(key as any)
|
||||
},
|
||||
}
|
||||
} catch (error) {
|
||||
// StateManager not initialized yet - will use environment variables only
|
||||
console.log("[TelemetryService] StateManager not available, using environment config only")
|
||||
}
|
||||
|
||||
// Initialize OpenTelemetry client provider with settings
|
||||
OpenTelemetryClientProvider.initialize(settingsProvider)
|
||||
|
||||
// Create telemetry providers with settings
|
||||
const providers = await TelemetryProviderFactory.createProviders(settingsProvider)
|
||||
const hostVersion = await HostProvider.env.getHostVersion({})
|
||||
const metadata: TelemetryMetadata = {
|
||||
extension_version: extensionVersion,
|
||||
@@ -1582,6 +1604,30 @@ export class TelemetryService {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Reinitialize all telemetry providers if their configuration has changed.
|
||||
* Each provider decides internally whether reinitialization is needed.
|
||||
*
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
public async reinitializeAllProviders(): Promise<void> {
|
||||
console.log("[TelemetryService] Checking all providers for reinitialization...")
|
||||
|
||||
const results = await Promise.all(
|
||||
this.providers.map(async (provider) => {
|
||||
try {
|
||||
return await provider.reinitializeIfNeeded()
|
||||
} catch (error) {
|
||||
console.error("[TelemetryService] Error reinitializing provider:", error)
|
||||
return false
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
const reinitializedCount = results.filter((r) => r).length
|
||||
console.log(`[TelemetryService] Reinitialized ${reinitializedCount} of ${this.providers.length} provider(s)`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up resources when the service is disposed
|
||||
*/
|
||||
|
||||
@@ -104,6 +104,15 @@ export interface ITelemetryProvider {
|
||||
*/
|
||||
recordHistogram?(name: string, value: number, attributes?: TelemetryProperties): void
|
||||
|
||||
/**
|
||||
* Reinitialize the provider if configuration has changed.
|
||||
* Provider internally compares current config with new config
|
||||
* and only reinitializes if necessary.
|
||||
*
|
||||
* @returns Promise<boolean> - true if reinitialized, false if no change needed
|
||||
*/
|
||||
reinitializeIfNeeded(): Promise<boolean>
|
||||
|
||||
/**
|
||||
* Clean up resources when the provider is disposed
|
||||
*/
|
||||
|
||||
@@ -5,7 +5,11 @@ import { BatchLogRecordProcessor, LoggerProvider } from "@opentelemetry/sdk-logs
|
||||
import { MeterProvider } from "@opentelemetry/sdk-metrics"
|
||||
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions"
|
||||
import { ExtensionRegistryInfo } from "@/registry"
|
||||
import { getValidOpenTelemetryConfig, OpenTelemetryClientValidConfig } from "@/shared/services/config/otel-config"
|
||||
import {
|
||||
getValidOpenTelemetryConfig,
|
||||
OpenTelemetryClientValidConfig,
|
||||
OtelSettingsProvider,
|
||||
} from "@/shared/services/config/otel-config"
|
||||
import {
|
||||
createConsoleLogExporter,
|
||||
createConsoleMetricReader,
|
||||
@@ -19,10 +23,23 @@ import {
|
||||
*/
|
||||
export class OpenTelemetryClientProvider {
|
||||
private static _instance: OpenTelemetryClientProvider | null = null
|
||||
private static _settingsProvider: OtelSettingsProvider | undefined = undefined
|
||||
|
||||
/**
|
||||
* Initialize the OpenTelemetry client provider with an optional settings provider
|
||||
* @param settingsProvider Optional settings provider for configuration
|
||||
*/
|
||||
public static initialize(settingsProvider?: OtelSettingsProvider): void {
|
||||
OpenTelemetryClientProvider._settingsProvider = settingsProvider
|
||||
// Reset instance to force re-initialization with new settings provider
|
||||
if (OpenTelemetryClientProvider._instance) {
|
||||
OpenTelemetryClientProvider._instance = null
|
||||
}
|
||||
}
|
||||
|
||||
public static getInstance(): OpenTelemetryClientProvider {
|
||||
if (!OpenTelemetryClientProvider._instance) {
|
||||
OpenTelemetryClientProvider._instance = new OpenTelemetryClientProvider()
|
||||
OpenTelemetryClientProvider._instance = new OpenTelemetryClientProvider(OpenTelemetryClientProvider._settingsProvider)
|
||||
}
|
||||
return OpenTelemetryClientProvider._instance
|
||||
}
|
||||
@@ -47,8 +64,8 @@ export class OpenTelemetryClientProvider {
|
||||
return process.env.TEL_DEBUG_DIAGNOSTICS === "true" || process.env.IS_DEV === "true"
|
||||
}
|
||||
|
||||
private constructor() {
|
||||
this.config = getValidOpenTelemetryConfig()
|
||||
private constructor(settingsProvider?: OtelSettingsProvider) {
|
||||
this.config = getValidOpenTelemetryConfig(settingsProvider)
|
||||
|
||||
if (!this.config) {
|
||||
console.log("[OTEL DEBUG] OpenTelemetry is disabled or not configured")
|
||||
|
||||
@@ -14,6 +14,7 @@ export class PostHogTelemetryProvider implements ITelemetryProvider {
|
||||
private client: PostHog
|
||||
private telemetrySettings: TelemetrySettings
|
||||
private isSharedClient: boolean
|
||||
private currentConfig: typeof posthogConfig = null
|
||||
|
||||
constructor(sharedClient?: PostHog) {
|
||||
this.isSharedClient = !!sharedClient
|
||||
@@ -139,6 +140,23 @@ export class PostHogTelemetryProvider implements ITelemetryProvider {
|
||||
// no-op
|
||||
}
|
||||
|
||||
public async reinitializeIfNeeded(): Promise<boolean> {
|
||||
// Get new config from posthog-config
|
||||
const newConfig = posthogConfig
|
||||
|
||||
// Compare with current config
|
||||
if (JSON.stringify(this.currentConfig) === JSON.stringify(newConfig)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Update current config
|
||||
this.currentConfig = { ...newConfig }
|
||||
|
||||
// For now, just log - actual reinitialization can be added later
|
||||
console.log("[PostHogTelemetryProvider] Config changed, would reinitialize")
|
||||
return false
|
||||
}
|
||||
|
||||
public async dispose(): Promise<void> {
|
||||
// Only shut down the client if it's not shared (we own it)
|
||||
if (!this.isSharedClient) {
|
||||
|
||||
@@ -85,19 +85,16 @@ export interface OpenTelemetryClientValidConfig extends OpenTelemetryClientConfi
|
||||
const isTestEnv = process.env.E2E_TEST === "true" || process.env.IS_TEST === "true"
|
||||
|
||||
/**
|
||||
* Cached OpenTelemetry configuration.
|
||||
* Lazily initialized on first access to avoid race conditions with environment variable loading.
|
||||
* Interface for providing settings overrides to OpenTelemetry configuration.
|
||||
* This allows StateManager to provide settings without creating a circular dependency.
|
||||
*/
|
||||
let otelConfig: OpenTelemetryClientConfig | null = null
|
||||
export interface OtelSettingsProvider {
|
||||
getGlobalSettingsKey<K extends keyof OpenTelemetryClientConfig>(key: K): OpenTelemetryClientConfig[K] | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or creates the OpenTelemetry configuration from environment variables.
|
||||
* Configuration is cached after first access for performance.
|
||||
*
|
||||
* Configuration Sources:
|
||||
* - **Production Build**: Environment variables injected by esbuild at build time
|
||||
* via .github/workflows/publish.yml
|
||||
* - **Development**: Environment variables from .env file loaded by VSCode
|
||||
* Gets the base OpenTelemetry configuration from environment variables.
|
||||
* This is the lowest precedence configuration source.
|
||||
*
|
||||
* Supported Environment Variables:
|
||||
* - OTEL_TELEMETRY_ENABLED: "1" to enable OpenTelemetry (default: off)
|
||||
@@ -119,34 +116,82 @@ let otelConfig: OpenTelemetryClientConfig | null = null
|
||||
* @see .env.example for development setup
|
||||
* @see .github/workflows/publish.yml for production environment variable injection
|
||||
*/
|
||||
function getOtelConfig(): OpenTelemetryClientConfig {
|
||||
if (!otelConfig) {
|
||||
otelConfig = {
|
||||
enabled: process.env.OTEL_TELEMETRY_ENABLED === "1",
|
||||
metricsExporter: process.env.OTEL_METRICS_EXPORTER,
|
||||
logsExporter: process.env.OTEL_LOGS_EXPORTER,
|
||||
otlpProtocol: process.env.OTEL_EXPORTER_OTLP_PROTOCOL,
|
||||
otlpEndpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
|
||||
otlpMetricsProtocol: process.env.OTEL_EXPORTER_OTLP_METRICS_PROTOCOL,
|
||||
otlpMetricsEndpoint: process.env.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT,
|
||||
otlpLogsProtocol: process.env.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL,
|
||||
otlpLogsEndpoint: process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT,
|
||||
metricExportInterval: process.env.OTEL_METRIC_EXPORT_INTERVAL
|
||||
? parseInt(process.env.OTEL_METRIC_EXPORT_INTERVAL, 10)
|
||||
: undefined,
|
||||
otlpInsecure: process.env.OTEL_EXPORTER_OTLP_INSECURE === "true",
|
||||
logBatchSize: process.env.OTEL_LOG_BATCH_SIZE
|
||||
? Math.max(1, parseInt(process.env.OTEL_LOG_BATCH_SIZE, 10))
|
||||
: undefined,
|
||||
logBatchTimeout: process.env.OTEL_LOG_BATCH_TIMEOUT
|
||||
? Math.max(1, parseInt(process.env.OTEL_LOG_BATCH_TIMEOUT, 10))
|
||||
: undefined,
|
||||
logMaxQueueSize: process.env.OTEL_LOG_MAX_QUEUE_SIZE
|
||||
? Math.max(1, parseInt(process.env.OTEL_LOG_MAX_QUEUE_SIZE, 10))
|
||||
: undefined,
|
||||
function getBaseOtelConfig(): OpenTelemetryClientConfig {
|
||||
return {
|
||||
enabled: process.env.OTEL_TELEMETRY_ENABLED === "1",
|
||||
metricsExporter: process.env.OTEL_METRICS_EXPORTER,
|
||||
logsExporter: process.env.OTEL_LOGS_EXPORTER,
|
||||
otlpProtocol: process.env.OTEL_EXPORTER_OTLP_PROTOCOL,
|
||||
otlpEndpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
|
||||
otlpMetricsProtocol: process.env.OTEL_EXPORTER_OTLP_METRICS_PROTOCOL,
|
||||
otlpMetricsEndpoint: process.env.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT,
|
||||
otlpLogsProtocol: process.env.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL,
|
||||
otlpLogsEndpoint: process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT,
|
||||
metricExportInterval: process.env.OTEL_METRIC_EXPORT_INTERVAL
|
||||
? parseInt(process.env.OTEL_METRIC_EXPORT_INTERVAL, 10)
|
||||
: undefined,
|
||||
otlpInsecure: process.env.OTEL_EXPORTER_OTLP_INSECURE === "true",
|
||||
logBatchSize: process.env.OTEL_LOG_BATCH_SIZE ? Math.max(1, parseInt(process.env.OTEL_LOG_BATCH_SIZE, 10)) : undefined,
|
||||
logBatchTimeout: process.env.OTEL_LOG_BATCH_TIMEOUT
|
||||
? Math.max(1, parseInt(process.env.OTEL_LOG_BATCH_TIMEOUT, 10))
|
||||
: undefined,
|
||||
logMaxQueueSize: process.env.OTEL_LOG_MAX_QUEUE_SIZE
|
||||
? Math.max(1, parseInt(process.env.OTEL_LOG_MAX_QUEUE_SIZE, 10))
|
||||
: undefined,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets OpenTelemetry configuration with proper precedence.
|
||||
*
|
||||
* Configuration Precedence (lowest to highest):
|
||||
* 1. **Build-time**: Environment variables injected by esbuild at build time via .github/workflows/publish.yml
|
||||
* 2. **Runtime Environment Variables**: Environment variables from .env file or startup
|
||||
* 3. **Settings**: Values from settingsProvider (typically StateManager with UI or remote config)
|
||||
*
|
||||
* @param settingsProvider Optional provider for settings overrides (typically StateManager)
|
||||
* @returns OpenTelemetry configuration with all precedence levels applied
|
||||
* @see .env.example for development setup
|
||||
* @see .github/workflows/publish.yml for production environment variable injection
|
||||
*/
|
||||
function getOtelConfig(settingsProvider?: OtelSettingsProvider): OpenTelemetryClientConfig {
|
||||
// Start with base environment variable config (lowest precedence)
|
||||
const baseConfig = getBaseOtelConfig()
|
||||
|
||||
// If no settings provider, return base config
|
||||
if (!settingsProvider) {
|
||||
return baseConfig
|
||||
}
|
||||
|
||||
// Apply settings overrides from provider (highest precedence)
|
||||
const settingsOverrides: Partial<OpenTelemetryClientConfig> = {}
|
||||
|
||||
const settingsKeys: Array<keyof OpenTelemetryClientConfig> = [
|
||||
"enabled",
|
||||
"metricsExporter",
|
||||
"logsExporter",
|
||||
"otlpProtocol",
|
||||
"otlpEndpoint",
|
||||
"otlpMetricsProtocol",
|
||||
"otlpMetricsEndpoint",
|
||||
"otlpLogsProtocol",
|
||||
"otlpLogsEndpoint",
|
||||
"metricExportInterval",
|
||||
"otlpInsecure",
|
||||
"logBatchSize",
|
||||
"logBatchTimeout",
|
||||
"logMaxQueueSize",
|
||||
]
|
||||
|
||||
for (const key of settingsKeys) {
|
||||
const value = settingsProvider.getGlobalSettingsKey(key)
|
||||
if (value !== undefined) {
|
||||
settingsOverrides[key] = value as any
|
||||
}
|
||||
}
|
||||
return otelConfig
|
||||
|
||||
// Merge base config with settings overrides
|
||||
return { ...baseConfig, ...settingsOverrides }
|
||||
}
|
||||
|
||||
export function isOpenTelemetryConfigValid(config: OpenTelemetryClientConfig): config is OpenTelemetryClientValidConfig {
|
||||
@@ -170,10 +215,11 @@ export function isOpenTelemetryConfigValid(config: OpenTelemetryClientConfig): c
|
||||
*
|
||||
* Configuration does not change at runtime - requires VSCode reload to pick up new values.
|
||||
*
|
||||
* @param settingsProvider Optional provider for settings overrides (typically StateManager)
|
||||
* @returns Valid OpenTelemetry configuration or null if disabled/invalid
|
||||
* @see .env.example for configuration options
|
||||
*/
|
||||
export function getValidOpenTelemetryConfig(): OpenTelemetryClientValidConfig | null {
|
||||
const config = getOtelConfig()
|
||||
export function getValidOpenTelemetryConfig(settingsProvider?: OtelSettingsProvider): OpenTelemetryClientValidConfig | null {
|
||||
const config = getOtelConfig(settingsProvider)
|
||||
return isOpenTelemetryConfigValid(config) ? config : null
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user