Compare commits

...
Author SHA1 Message Date
NightTrek 1c3021d9aa removed ai slop 2025-10-14 03:06:43 -07:00
NightTrek 69ab2d6b08 fix: Use StateManager getter methods instead of direct state access
The getOtelConfig() function was trying to access stateManager.state directly,
which doesn't exist. StateManager uses getter methods like getGlobalSettingsKey()
to access settings with proper precedence (remote config > task settings > global).

This bug prevented settings from being read correctly, causing the reinitialization
to use stale environment variable values instead of the updated settings.

Fixes the issue where test command would update settings but OpenTelemetry would
still send to the old endpoint.
2025-10-14 01:48:19 -07:00
NightTrek a1fe2a87d0 feat: Integrate OpenTelemetry provider reinitialization with remote config
- Add reinitialization trigger in applyRemoteConfig() for dynamic config updates
- Update test command to call reinitializeAllProviders() for testing without reload
- Add error handling with graceful degradation (log errors, continue with old config)
- Remove duplicate AbortCommit command registration in extension.ts
- Add test command to package.json commands list

This enables OpenTelemetry providers to reinitialize when remote config updates
settings, allowing dynamic configuration changes without requiring window reload.
Settings precedence: Build-time env vars < Runtime env vars < Settings (remote config)
2025-10-14 01:40:41 -07:00
NightTrek 0f1c834769 feat: Implement complete OpenTelemetry provider reinitialization
Combined PR #1 and PR #4 from the implementation plan.

Changes:
- Add reinitializeIfNeeded() method to ITelemetryProvider interface
- Implement in NoOpTelemetryProvider (returns false)
- Implement config tracking in PostHogTelemetryProvider (logs but doesn't reinitialize yet)
- Implement full reinitialization in OpenTelemetryTelemetryProvider:
  - Track current config in OpenTelemetryClientProvider
  - Add hasConfigChanged() to detect config changes
  - Add reinitialize() to dispose and recreate providers
  - Update meter/logger references and clear cached instruments
- Add TelemetryService.reinitializeAllProviders() to call reinitializeIfNeeded() on all providers

This establishes the complete foundation for provider reinitialization.
Each provider decides internally whether reinitialization is needed based on config changes.

The OpenTelemetry provider now supports full reinitialization when config changes,
including disposing old providers, creating new ones, and updating all references.

Next steps (PR #2-3): Add settings schema and refactor config resolution to read from settings.
2025-10-14 01:34:18 -07:00
NightTrek 6043f7e864 feat: Add generic provider reinitialization interface
- Add reinitializeIfNeeded() method to ITelemetryProvider interface
- Implement stub in NoOpTelemetryProvider (returns false)
- Implement config tracking in PostHogTelemetryProvider (logs but doesn't reinitialize yet)
- Implement stub in OpenTelemetryTelemetryProvider (actual logic comes in PR #4)
- Add TelemetryService.reinitializeAllProviders() to call reinitializeIfNeeded() on all providers
- Add implementation plan document (OTEL-SETTINGS-IMPLEMENTATION-PLAN.md)

This establishes the foundation for provider reinitialization without any OpenTelemetry-specific changes.
Each provider will decide internally whether reinitialization is needed based on config changes.

Part of PR #1 in the OpenTelemetry settings integration plan.
2025-10-14 01:34:11 -07:00
NightTrek 7ac0f32c6b test: Add command to test OpenTelemetry settings precedence
Added a test command to verify that settings override environment variables.

Changes:
- Created test-otel-settings.ts with cline.testOtelSettings command
- Command sets openTelemetryOtlpProtocol to 'http/protobuf'
- Command sets openTelemetryOtlpEndpoint to 'http://localhost:4318'
- Registered command in extension.ts
- Added test file to biome.jsonc exclusions

Usage:
1. Run command: Cmd+Shift+P -> 'Cline: Test Otel Settings'
2. Reload window: Cmd+Shift+P -> 'Developer: Reload Window'
3. Check console for [OTEL DEBUG] output
4. Verify protocol changed from 'http/json' to 'http/protobuf'
5. Verify endpoint changed from 'https://otel.staging.cline.bot' to 'http://localhost:4318'

This proves settings have highest precedence over environment variables.
2025-10-14 01:05:59 -07:00
NightTrek 736c1c6462 feat: Add OpenTelemetry settings infrastructure
Implements PR #1 of the OpenTelemetry settings integration plan.

This PR establishes the complete settings infrastructure for OpenTelemetry configuration
with proper precedence: Build-time < Environment Variables < Settings.

Changes:
1. Settings Schema (state-keys.ts):
   - Added 14 OpenTelemetry configuration fields to Settings interface
   - Fields include: enabled, exporters, protocols, endpoints, batch sizes, etc.

2. Remote Config Schema (remote-config/schema.ts):
   - Added OpenTelemetry fields to RemoteConfigSchema
   - Enables remote configuration of OpenTelemetry settings

3. Remote Config Transformation (remote-config/utils.ts):
   - Added mapping logic to transform remote config to state shape
   - Ensures OpenTelemetry settings flow from remote config to StateManager

4. Config Resolution with Precedence (otel-config.ts):
   - Refactored getOtelConfig() to implement proper precedence
   - Build-time env vars (lowest) -> Runtime env vars -> Settings (highest)
   - Added clearOtelConfigCache() for cache invalidation
   - Dynamic StateManager integration with fallback for early startup

5. State Helpers (state-helpers.ts):
   - Added OpenTelemetry fields to readGlobalStateFromDisk()
   - Ensures settings are properly persisted and restored

Configuration Precedence:
- Build-time: Environment variables injected at build time (GitHub Actions)
- Runtime: Environment variables from .env or startup
- Settings: Values from StateManager (UI or remote config) - HIGHEST PRIORITY

This PR is immediately functional - users can set OpenTelemetry config via settings
and it will work on next extension restart. Dynamic reinitialization will be added
in a follow-up PR.
2025-10-13 23:11:17 -07:00
15 changed files with 429 additions and 13 deletions
+3 -1
View File
@@ -139,6 +139,7 @@
"!**/*.test.ts",
"!src/dev/**",
"!src/extension.ts",
"!src/test-otel-settings.ts",
"!src/integrations/git/commit-message-generator.ts",
"!src/integrations/terminal/**",
"!src/core/controller/ui/openWalkthrough.ts"
@@ -156,7 +157,8 @@
"!src/common.ts",
"!src/services/logging/distinctId.ts",
"!src/core/storage/utils/state-helpers.ts",
"!src/extension.ts"
"!src/extension.ts",
"!src/test-otel-settings.ts"
],
"plugins": [
"src/dev/grit/use-cache-service.grit"
+5
View File
@@ -198,6 +198,11 @@
"command": "cline.reconstructTaskHistory",
"title": "Reconstruct Task History",
"category": "Cline"
},
{
"command": "cline.testOtelSettings",
"title": "Test Otel Settings",
"category": "Cline"
}
],
"keybindings": [
+54
View File
@@ -1,5 +1,7 @@
import { RemoteConfig } from "@shared/remote-config/schema"
import { clearOtelConfigCache } from "@shared/services/config/otel-config"
import { GlobalStateAndSettings } from "@shared/storage/state-keys"
import { telemetryService } from "@/services/telemetry"
import { StateManager } from "../StateManager"
/**
@@ -24,6 +26,50 @@ export function transformRemoteConfigToStateShape(remoteConfig: RemoteConfig): P
}
}
// Map OpenTelemetry settings
if (remoteConfig.openTelemetryEnabled !== undefined) {
transformed.openTelemetryEnabled = remoteConfig.openTelemetryEnabled
}
if (remoteConfig.openTelemetryMetricsExporter !== undefined) {
transformed.openTelemetryMetricsExporter = remoteConfig.openTelemetryMetricsExporter
}
if (remoteConfig.openTelemetryLogsExporter !== undefined) {
transformed.openTelemetryLogsExporter = remoteConfig.openTelemetryLogsExporter
}
if (remoteConfig.openTelemetryOtlpProtocol !== undefined) {
transformed.openTelemetryOtlpProtocol = remoteConfig.openTelemetryOtlpProtocol
}
if (remoteConfig.openTelemetryOtlpEndpoint !== undefined) {
transformed.openTelemetryOtlpEndpoint = remoteConfig.openTelemetryOtlpEndpoint
}
if (remoteConfig.openTelemetryOtlpMetricsProtocol !== undefined) {
transformed.openTelemetryOtlpMetricsProtocol = remoteConfig.openTelemetryOtlpMetricsProtocol
}
if (remoteConfig.openTelemetryOtlpMetricsEndpoint !== undefined) {
transformed.openTelemetryOtlpMetricsEndpoint = remoteConfig.openTelemetryOtlpMetricsEndpoint
}
if (remoteConfig.openTelemetryOtlpLogsProtocol !== undefined) {
transformed.openTelemetryOtlpLogsProtocol = remoteConfig.openTelemetryOtlpLogsProtocol
}
if (remoteConfig.openTelemetryOtlpLogsEndpoint !== undefined) {
transformed.openTelemetryOtlpLogsEndpoint = remoteConfig.openTelemetryOtlpLogsEndpoint
}
if (remoteConfig.openTelemetryMetricExportInterval !== undefined) {
transformed.openTelemetryMetricExportInterval = remoteConfig.openTelemetryMetricExportInterval
}
if (remoteConfig.openTelemetryOtlpInsecure !== undefined) {
transformed.openTelemetryOtlpInsecure = remoteConfig.openTelemetryOtlpInsecure
}
if (remoteConfig.openTelemetryLogBatchSize !== undefined) {
transformed.openTelemetryLogBatchSize = remoteConfig.openTelemetryLogBatchSize
}
if (remoteConfig.openTelemetryLogBatchTimeout !== undefined) {
transformed.openTelemetryLogBatchTimeout = remoteConfig.openTelemetryLogBatchTimeout
}
if (remoteConfig.openTelemetryLogMaxQueueSize !== undefined) {
transformed.openTelemetryLogMaxQueueSize = remoteConfig.openTelemetryLogMaxQueueSize
}
// Map OpenAiCompatible provider settings
const openAiSettings = remoteConfig.providerSettings?.OpenAiCompatible
if (openAiSettings) {
@@ -90,4 +136,12 @@ export function applyRemoteConfig(remoteConfig?: RemoteConfig): void {
for (const [key, value] of Object.entries(transformed)) {
stateManager.setRemoteConfigField(key as keyof GlobalStateAndSettings, value)
}
// Reinitialize telemetry providers with new config
// Fire-and-forget to avoid blocking remote config application
clearOtelConfigCache()
telemetryService.reinitializeAllProviders().catch((error) => {
console.error("[RemoteConfig] Failed to reinitialize telemetry providers:", error)
// Continue with old config on error
})
}
+50
View File
@@ -249,6 +249,40 @@ export async function readGlobalStateFromDisk(context: ExtensionContext): Promis
const autoCondenseThreshold =
context.globalState.get<GlobalStateAndSettings["autoCondenseThreshold"]>("autoCondenseThreshold") // number from 0 to 1
const hooksEnabled = context.globalState.get<GlobalStateAndSettings["hooksEnabled"]>("hooksEnabled")
// OpenTelemetry configuration
const openTelemetryEnabled =
context.globalState.get<GlobalStateAndSettings["openTelemetryEnabled"]>("openTelemetryEnabled")
const openTelemetryMetricsExporter =
context.globalState.get<GlobalStateAndSettings["openTelemetryMetricsExporter"]>("openTelemetryMetricsExporter")
const openTelemetryLogsExporter =
context.globalState.get<GlobalStateAndSettings["openTelemetryLogsExporter"]>("openTelemetryLogsExporter")
const openTelemetryOtlpProtocol =
context.globalState.get<GlobalStateAndSettings["openTelemetryOtlpProtocol"]>("openTelemetryOtlpProtocol")
const openTelemetryOtlpEndpoint =
context.globalState.get<GlobalStateAndSettings["openTelemetryOtlpEndpoint"]>("openTelemetryOtlpEndpoint")
const openTelemetryOtlpMetricsProtocol = context.globalState.get<
GlobalStateAndSettings["openTelemetryOtlpMetricsProtocol"]
>("openTelemetryOtlpMetricsProtocol")
const openTelemetryOtlpMetricsEndpoint = context.globalState.get<
GlobalStateAndSettings["openTelemetryOtlpMetricsEndpoint"]
>("openTelemetryOtlpMetricsEndpoint")
const openTelemetryOtlpLogsProtocol =
context.globalState.get<GlobalStateAndSettings["openTelemetryOtlpLogsProtocol"]>("openTelemetryOtlpLogsProtocol")
const openTelemetryOtlpLogsEndpoint =
context.globalState.get<GlobalStateAndSettings["openTelemetryOtlpLogsEndpoint"]>("openTelemetryOtlpLogsEndpoint")
const openTelemetryMetricExportInterval = context.globalState.get<
GlobalStateAndSettings["openTelemetryMetricExportInterval"]
>("openTelemetryMetricExportInterval")
const openTelemetryOtlpInsecure =
context.globalState.get<GlobalStateAndSettings["openTelemetryOtlpInsecure"]>("openTelemetryOtlpInsecure")
const openTelemetryLogBatchSize =
context.globalState.get<GlobalStateAndSettings["openTelemetryLogBatchSize"]>("openTelemetryLogBatchSize")
const openTelemetryLogBatchTimeout =
context.globalState.get<GlobalStateAndSettings["openTelemetryLogBatchTimeout"]>("openTelemetryLogBatchTimeout")
const openTelemetryLogMaxQueueSize =
context.globalState.get<GlobalStateAndSettings["openTelemetryLogMaxQueueSize"]>("openTelemetryLogMaxQueueSize")
// Get mode-related configurations
const mode = context.globalState.get<GlobalStateAndSettings["mode"]>("mode")
@@ -574,6 +608,22 @@ export async function readGlobalStateFromDisk(context: ExtensionContext): Promis
// Feature flag - defaults to false
// For now, always return false to disable multi-root support by default
multiRootEnabled: !!multiRootEnabled,
// OpenTelemetry configuration
openTelemetryEnabled,
openTelemetryMetricsExporter,
openTelemetryLogsExporter,
openTelemetryOtlpProtocol,
openTelemetryOtlpEndpoint,
openTelemetryOtlpMetricsProtocol,
openTelemetryOtlpMetricsEndpoint,
openTelemetryOtlpLogsProtocol,
openTelemetryOtlpLogsEndpoint,
openTelemetryMetricExportInterval,
openTelemetryOtlpInsecure,
openTelemetryLogBatchSize,
openTelemetryLogBatchTimeout,
openTelemetryLogMaxQueueSize,
}
} catch (error) {
console.error("[StateHelpers] Failed to read global state:", error)
+3 -6
View File
@@ -7,7 +7,6 @@ import * as vscode from "vscode"
import { sendAccountButtonClickedEvent } from "./core/controller/ui/subscribeToAccountButtonClicked"
import { sendChatButtonClickedEvent } from "./core/controller/ui/subscribeToChatButtonClicked"
import { sendHistoryButtonClickedEvent } from "./core/controller/ui/subscribeToHistoryButtonClicked"
import { sendMcpButtonClickedEvent } from "./core/controller/ui/subscribeToMcpButtonClicked"
import { sendSettingsButtonClickedEvent } from "./core/controller/ui/subscribeToSettingsButtonClicked"
import { WebviewProvider } from "./core/webview"
import { createClineAPI } from "./exports"
@@ -38,6 +37,7 @@ import { LogoutReason } from "./services/auth/types"
import { telemetryService } from "./services/telemetry"
import { SharedUriHandler } from "./services/uri/SharedUriHandler"
import { ShowMessageType } from "./shared/proto/host/window"
import { registerTestOtelSettingsCommand } from "./test-otel-settings"
import { fileExistsAtPath } from "./utils/fs"
/*
Built using https://github.com/microsoft/vscode-webview-ui-toolkit
@@ -82,11 +82,8 @@ export async function activate(context: vscode.ExtensionContext) {
}),
)
context.subscriptions.push(
vscode.commands.registerCommand(commands.McpButton, () => {
sendMcpButtonClickedEvent()
}),
)
// Register test command for OpenTelemetry settings (for testing precedence)
registerTestOtelSettingsCommand(context)
context.subscriptions.push(
vscode.commands.registerCommand(commands.SettingsButton, () => {
@@ -127,6 +127,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")
}
@@ -1532,6 +1532,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
*/
@@ -19,6 +19,7 @@ import {
*/
export class OpenTelemetryClientProvider {
private static _instance: OpenTelemetryClientProvider | null = null
private static currentConfig: OpenTelemetryClientValidConfig | null = null
public static getInstance(): OpenTelemetryClientProvider {
if (!OpenTelemetryClientProvider._instance) {
@@ -47,9 +48,50 @@ export class OpenTelemetryClientProvider {
return process.env.TEL_DEBUG_DIAGNOSTICS === "true" || process.env.IS_DEV === "true"
}
/**
* Check if OpenTelemetry configuration has changed.
* Compares current config with new config from StateManager.
*/
public static hasConfigChanged(): boolean {
const newConfig = getValidOpenTelemetryConfig()
const changed = JSON.stringify(OpenTelemetryClientProvider.currentConfig) !== JSON.stringify(newConfig)
if (changed) {
console.log("[OTEL] Configuration has changed")
}
return changed
}
/**
* Reinitialize the OpenTelemetry client providers.
* Disposes existing providers and creates new ones with updated config.
*/
public static async reinitialize(): Promise<void> {
console.log("[OTEL] Reinitializing OpenTelemetry providers...")
const instance = OpenTelemetryClientProvider.getInstance()
// Dispose existing providers
await instance.dispose()
// Clear singleton instance
OpenTelemetryClientProvider._instance = null
OpenTelemetryClientProvider.currentConfig = null
// Clear config cache to force re-evaluation
// Note: clearOtelConfigCache will be exported from otel-config.ts
// For now, we'll just clear the instance which will trigger re-read on next getInstance()
console.log("[OTEL] OpenTelemetry providers reinitialized")
}
private constructor() {
this.config = getValidOpenTelemetryConfig()
// Store current config for comparison
OpenTelemetryClientProvider.currentConfig = this.config
if (!this.config) {
console.log("[OTEL DEBUG] OpenTelemetry is disabled or not configured")
return
@@ -193,6 +193,38 @@ export class OpenTelemetryTelemetryProvider implements ITelemetryProvider {
histogram.record(value, this.flattenProperties(attributes))
}
public async reinitializeIfNeeded(): Promise<boolean> {
// Check if config has changed
if (!OpenTelemetryClientProvider.hasConfigChanged()) {
return false
}
console.log("[OTEL] Config changed, reinitializing...")
try {
// Reinitialize the client provider
await OpenTelemetryClientProvider.reinitialize()
// Get new meter and logger from reinitialized provider
const meterProvider = OpenTelemetryClientProvider.getMeterProvider()
const loggerProvider = OpenTelemetryClientProvider.getLoggerProvider()
// Update our references
this.meter = meterProvider ? meterProvider.getMeter("cline") : null
this.logger = loggerProvider ? loggerProvider.getLogger("cline") : null
// Clear cached instruments since we have new meter
this.counters.clear()
this.histograms.clear()
console.log("[OTEL] Reinitialization complete")
return true
} catch (error) {
console.error("[OTEL] Reinitialization failed:", error)
return false
}
}
public async dispose(): Promise<void> {
// OpenTelemetry client provider handles shutdown
// Individual providers don't need to do anything
@@ -14,6 +14,7 @@ export class PostHogTelemetryProvider implements ITelemetryProvider {
private client: PostHog
private telemetrySettings: TelemetrySettings
private isSharedClient: boolean
private currentConfig: any = 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) {
+17
View File
@@ -80,6 +80,23 @@ export const RemoteConfigSchema = z.object({
// If the user is allowed to enable YOLO mode. Note this is different from the extension setting
// yoloModeEnabled, because we do not want to force YOLO enabled for the user.
yoloModeAllowed: z.boolean().optional(),
// OpenTelemetry configuration
openTelemetryEnabled: z.boolean().optional(),
openTelemetryMetricsExporter: z.string().optional(),
openTelemetryLogsExporter: z.string().optional(),
openTelemetryOtlpProtocol: z.string().optional(),
openTelemetryOtlpEndpoint: z.string().optional(),
openTelemetryOtlpMetricsProtocol: z.string().optional(),
openTelemetryOtlpMetricsEndpoint: z.string().optional(),
openTelemetryOtlpLogsProtocol: z.string().optional(),
openTelemetryOtlpLogsEndpoint: z.string().optional(),
openTelemetryMetricExportInterval: z.number().optional(),
openTelemetryOtlpInsecure: z.boolean().optional(),
openTelemetryLogBatchSize: z.number().optional(),
openTelemetryLogBatchTimeout: z.number().optional(),
openTelemetryLogMaxQueueSize: z.number().optional(),
// Other top-level settings can be added here later.
// Provider specific settings
+101 -6
View File
@@ -91,13 +91,13 @@ const isTestEnv = process.env.E2E_TEST === "true" || process.env.IS_TEST === "tr
let otelConfig: OpenTelemetryClientConfig | null = null
/**
* Gets or creates the OpenTelemetry configuration from environment variables.
* Gets or creates the OpenTelemetry configuration with proper precedence.
* 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
* 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 StateManager (can be set via UI or remote config)
*
* Supported Environment Variables:
* - OTEL_TELEMETRY_ENABLED: "1" to enable OpenTelemetry (default: off)
@@ -121,7 +121,8 @@ let otelConfig: OpenTelemetryClientConfig | null = null
*/
function getOtelConfig(): OpenTelemetryClientConfig {
if (!otelConfig) {
otelConfig = {
// Start with build-time/environment variable config (lowest precedence)
const baseConfig: OpenTelemetryClientConfig = {
enabled: process.env.OTEL_TELEMETRY_ENABLED === "1",
metricsExporter: process.env.OTEL_METRICS_EXPORTER,
logsExporter: process.env.OTEL_LOGS_EXPORTER,
@@ -145,10 +146,104 @@ function getOtelConfig(): OpenTelemetryClientConfig {
? Math.max(1, parseInt(process.env.OTEL_LOG_MAX_QUEUE_SIZE, 10))
: undefined,
}
// Try to get settings from StateManager (highest precedence)
// Note: StateManager may not be initialized yet during early extension startup
const settingsOverrides: Partial<OpenTelemetryClientConfig> = {}
try {
// Dynamic import to avoid circular dependencies
const { StateManager } = require("@/core/storage/StateManager")
const stateManager = StateManager.get()
// Apply settings overrides if they exist (using getter methods)
const enabled = stateManager.getGlobalSettingsKey("openTelemetryEnabled")
if (enabled !== undefined) {
settingsOverrides.enabled = enabled
}
const metricsExporter = stateManager.getGlobalSettingsKey("openTelemetryMetricsExporter")
if (metricsExporter !== undefined) {
settingsOverrides.metricsExporter = metricsExporter
}
const logsExporter = stateManager.getGlobalSettingsKey("openTelemetryLogsExporter")
if (logsExporter !== undefined) {
settingsOverrides.logsExporter = logsExporter
}
const otlpProtocol = stateManager.getGlobalSettingsKey("openTelemetryOtlpProtocol")
if (otlpProtocol !== undefined) {
settingsOverrides.otlpProtocol = otlpProtocol
}
const otlpEndpoint = stateManager.getGlobalSettingsKey("openTelemetryOtlpEndpoint")
if (otlpEndpoint !== undefined) {
settingsOverrides.otlpEndpoint = otlpEndpoint
}
const otlpMetricsProtocol = stateManager.getGlobalSettingsKey("openTelemetryOtlpMetricsProtocol")
if (otlpMetricsProtocol !== undefined) {
settingsOverrides.otlpMetricsProtocol = otlpMetricsProtocol
}
const otlpMetricsEndpoint = stateManager.getGlobalSettingsKey("openTelemetryOtlpMetricsEndpoint")
if (otlpMetricsEndpoint !== undefined) {
settingsOverrides.otlpMetricsEndpoint = otlpMetricsEndpoint
}
const otlpLogsProtocol = stateManager.getGlobalSettingsKey("openTelemetryOtlpLogsProtocol")
if (otlpLogsProtocol !== undefined) {
settingsOverrides.otlpLogsProtocol = otlpLogsProtocol
}
const otlpLogsEndpoint = stateManager.getGlobalSettingsKey("openTelemetryOtlpLogsEndpoint")
if (otlpLogsEndpoint !== undefined) {
settingsOverrides.otlpLogsEndpoint = otlpLogsEndpoint
}
const metricExportInterval = stateManager.getGlobalSettingsKey("openTelemetryMetricExportInterval")
if (metricExportInterval !== undefined) {
settingsOverrides.metricExportInterval = metricExportInterval
}
const otlpInsecure = stateManager.getGlobalSettingsKey("openTelemetryOtlpInsecure")
if (otlpInsecure !== undefined) {
settingsOverrides.otlpInsecure = otlpInsecure
}
const logBatchSize = stateManager.getGlobalSettingsKey("openTelemetryLogBatchSize")
if (logBatchSize !== undefined) {
settingsOverrides.logBatchSize = logBatchSize
}
const logBatchTimeout = stateManager.getGlobalSettingsKey("openTelemetryLogBatchTimeout")
if (logBatchTimeout !== undefined) {
settingsOverrides.logBatchTimeout = logBatchTimeout
}
const logMaxQueueSize = stateManager.getGlobalSettingsKey("openTelemetryLogMaxQueueSize")
if (logMaxQueueSize !== undefined) {
settingsOverrides.logMaxQueueSize = logMaxQueueSize
}
} catch (error) {
// StateManager not available yet (early startup) - use base config only
console.log("[OTEL] StateManager not available, using environment config only")
}
// Merge base config with settings overrides
otelConfig = { ...baseConfig, ...settingsOverrides }
}
return otelConfig
}
/**
* Clears the cached OpenTelemetry configuration.
* Should be called when settings change to force re-evaluation.
*/
export function clearOtelConfigCache(): void {
otelConfig = null
}
export function isOpenTelemetryConfigValid(config: OpenTelemetryClientConfig): config is OpenTelemetryClientValidConfig {
// Disable in test environment to enable mocking and stubbing
if (isTestEnv) {
+16
View File
@@ -173,6 +173,22 @@ export interface Settings {
actModeVercelAiGatewayModelInfo: ModelInfo | undefined
actModeOcaModelId: string | undefined
actModeOcaModelInfo: OcaModelInfo | undefined
// OpenTelemetry configuration
openTelemetryEnabled: boolean | undefined
openTelemetryMetricsExporter: string | undefined
openTelemetryLogsExporter: string | undefined
openTelemetryOtlpProtocol: string | undefined
openTelemetryOtlpEndpoint: string | undefined
openTelemetryOtlpMetricsProtocol: string | undefined
openTelemetryOtlpMetricsEndpoint: string | undefined
openTelemetryOtlpLogsProtocol: string | undefined
openTelemetryOtlpLogsEndpoint: string | undefined
openTelemetryMetricExportInterval: number | undefined
openTelemetryOtlpInsecure: boolean | undefined
openTelemetryLogBatchSize: number | undefined
openTelemetryLogBatchTimeout: number | undefined
openTelemetryLogMaxQueueSize: number | undefined
}
export interface Secrets {
+50
View File
@@ -0,0 +1,50 @@
import * as vscode from "vscode"
import { StateManager } from "@/core/storage/StateManager"
import { HostProvider } from "@/hosts/host-provider"
import { telemetryService } from "@/services/telemetry"
import { ShowMessageType } from "@/shared/proto/host/window"
import { clearOtelConfigCache } from "@/shared/services/config/otel-config"
export function registerTestOtelSettingsCommand(context: vscode.ExtensionContext) {
// biome-ignore lint/correctness/noUnusedVariables: Test utility for OpenTelemetry settings
// biome-ignore plugin: Test utility - direct vscode API usage is acceptable here
const disposable = vscode.commands.registerCommand("cline.testOtelSettings", async () => {
try {
const stateManager = StateManager.get()
// Set OpenTelemetry settings via StateManager
await stateManager.setGlobalStateBatch({
openTelemetryOtlpProtocol: "http/protobuf",
openTelemetryOtlpEndpoint: "http://localhost:4318",
})
// Clear the config cache so it will be re-read on next access
clearOtelConfigCache()
// Reinitialize telemetry providers with new config
await telemetryService.reinitializeAllProviders()
console.log("[TEST] OpenTelemetry settings updated:")
console.log("[TEST] - Protocol: http/protobuf")
console.log("[TEST] - Endpoint: http://localhost:4318")
console.log("[TEST] Config cache cleared")
console.log("[TEST] Telemetry providers reinitialized")
console.log("[TEST] Settings saved to disk")
// Show success message
await HostProvider.window.showMessage({
type: ShowMessageType.INFORMATION,
message:
"OpenTelemetry settings updated and providers reinitialized! Protocol: http/protobuf, Endpoint: http://localhost:4318",
})
} catch (error) {
await HostProvider.window.showMessage({
type: ShowMessageType.ERROR,
message: `Failed to update settings: ${error}`,
})
console.error("[TEST] Error updating settings:", error)
}
})
context.subscriptions.push(disposable)
}