From 3a945420aa6ec7c466e128840d6608db8e44476d Mon Sep 17 00:00:00 2001 From: Derek Misler Date: Thu, 4 Sep 2025 13:23:34 -0400 Subject: [PATCH 1/2] Start [2025-09-04 13:23:34] From 4757ff541cc57ad4e5a06a77cc0680a11e0bf803 Mon Sep 17 00:00:00 2001 From: Derek Misler Date: Thu, 4 Sep 2025 13:34:17 -0400 Subject: [PATCH 2/2] WIP [2025-09-05 10:12:58] --- .github/workflows/ci.yml | 2 - Dockerfile | 10 +- Taskfile.yml | 6 +- docs/TELEMETRY.md | 4 +- internal/telemetry/client.go | 146 +++++++++------------------ internal/telemetry/events.go | 29 ++---- internal/telemetry/global.go | 12 +-- internal/telemetry/http.go | 14 ++- internal/telemetry/telemetry_test.go | 110 +++++++------------- internal/telemetry/types.go | 19 +--- internal/telemetry/utils.go | 7 +- 11 files changed, 116 insertions(+), 243 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f81cba360..c38ba19d0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,7 +48,6 @@ jobs: - name: Run tests run: task test env: - TELEMETRY_ENABLED: true TELEMETRY_API_KEY: ${{ secrets.TELEMETRY_API_KEY }} TELEMETRY_ENDPOINT: ${{ secrets.TELEMETRY_ENDPOINT }} TELEMETRY_HEADER: ${{ secrets.TELEMETRY_HEADER }} @@ -93,7 +92,6 @@ jobs: - name: Build run: task build env: - TELEMETRY_ENABLED: true TELEMETRY_API_KEY: ${{ secrets.TELEMETRY_API_KEY }} TELEMETRY_ENDPOINT: ${{ secrets.TELEMETRY_ENDPOINT }} TELEMETRY_HEADER: ${{ secrets.TELEMETRY_HEADER }} diff --git a/Dockerfile b/Dockerfile index 25626e0d5..1b06648b2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,7 @@ COPY . ./ ARG GIT_TAG GIT_COMMIT BUILD_DATE RUN --mount=type=cache,target=/root/.cache \ --mount=type=cache,target=/go/pkg/mod \ - CGO_ENABLED=1 go build -trimpath -ldflags "-s -w -X 'github.com/docker/cagent/cmd/root.Version=$GIT_TAG' -X 'github.com/docker/cagent/cmd/root.Commit=$GIT_COMMIT' -X 'github.com/docker/cagent/cmd/root.BuildTime=$BUILD_DATE' -X 'github.com/docker/cagent/internal/telemetry.TelemetryEnabled=$TELEMETRY_ENABLED' -X 'github.com/docker/cagent/internal/telemetry.TelemetryEndpoint=$TELEMETRY_ENDPOINT' -X 'github.com/docker/cagent/internal/telemetry.TelemetryAPIKey=$TELEMETRY_API_KEY' -X 'github.com/docker/cagent/internal/telemetry.TelemetryHeader=$TELEMETRY_HEADER'" -o /agent . + CGO_ENABLED=1 go build -trimpath -ldflags "-s -w -X 'github.com/docker/cagent/cmd/root.Version=$GIT_TAG' -X 'github.com/docker/cagent/cmd/root.Commit=$GIT_COMMIT' -X 'github.com/docker/cagent/cmd/root.BuildTime=$BUILD_DATE' -X 'github.com/docker/cagent/internal/telemetry.TelemetryEndpoint=$TELEMETRY_ENDPOINT' -X 'github.com/docker/cagent/internal/telemetry.TelemetryAPIKey=$TELEMETRY_API_KEY' -X 'github.com/docker/cagent/internal/telemetry.TelemetryHeader=$TELEMETRY_HEADER'" -o /agent . FROM --platform=$BUILDPLATFORM golang:1.25.0-alpine3.22 AS builder-base WORKDIR /src @@ -23,12 +23,10 @@ ARG GIT_TAG GIT_COMMIT BUILD_DATE ARG TELEMETRY_API_KEY ARG TELEMETRY_ENDPOINT ARG TELEMETRY_HEADER -ARG TELEMETRY_ENABLED ENV TELEMETRY_API_KEY=${TELEMETRY_API_KEY} ENV TELEMETRY_ENDPOINT=${TELEMETRY_ENDPOINT} ENV TELEMETRY_HEADER=${TELEMETRY_HEADER} -ENV TELEMETRY_ENABLED=${TELEMETRY_ENABLED} FROM builder-base AS builder-darwin RUN apk add clang @@ -37,7 +35,7 @@ RUN --mount=type=bind,from=osxcross,src=/osxsdk,target=/xx-sdk \ --mount=type=cache,target=/root/.cache,id=docker-ai-$TARGETPLATFORM \ --mount=type=cache,target=/go/pkg/mod < 0 && customHttpClient[0] != nil { + httpClient = customHttpClient[0] + } else { + httpClient = &http.Client{Timeout: 30 * time.Second} + } + client := &Client{ - logger: logger, + logger: telemetryLogger, enabled: enabled, debugMode: debugMode, httpClient: httpClient, @@ -40,16 +73,13 @@ func NewClientWithHTTPClient(logger *slog.Logger, enabled, debugMode bool, versi apiKey: apiKey, header: header, version: version, - eventChan: make(chan EventWithContext, 1000), // Buffer for 1000 events - stopChan: make(chan struct{}), - done: make(chan struct{}), } if debugMode { hasEndpoint := endpoint != "" hasAPIKey := apiKey != "" hasHeader := header != "" - logger.Debug("Telemetry configuration", + telemetryLogger.Debug("Telemetry configuration", "enabled", enabled, "has_endpoint", hasEndpoint, "has_api_key", hasAPIKey, @@ -58,91 +88,5 @@ func NewClientWithHTTPClient(logger *slog.Logger, enabled, debugMode bool, versi ) } - // Start background event processor - go client.processEvents() - return client, nil } - -// IsEnabled returns whether telemetry is enabled -func (tc *Client) IsEnabled() bool { - tc.mu.RLock() - defer tc.mu.RUnlock() - return tc.enabled -} - -// Shutdown gracefully shuts down the telemetry client -func (tc *Client) Shutdown(ctx context.Context) error { - tc.RecordSessionEnd(ctx) - - if !tc.enabled { - return nil - } - - // Signal shutdown to background goroutine - close(tc.stopChan) - - // Wait for background processing to complete with timeout - select { - case <-tc.done: - return nil - case <-ctx.Done(): - return ctx.Err() - case <-time.After(5 * time.Second): - return fmt.Errorf("timeout waiting for telemetry shutdown") - } -} - -// processEvents runs in a background goroutine to process telemetry events -func (tc *Client) processEvents() { - defer close(tc.done) - - if tc.debugMode { - tc.logger.Debug("🔄 Background event processor started") - } - - for { - select { - case event := <-tc.eventChan: - if tc.debugMode { - tc.logger.Debug("🔄 Processing event from channel", "event_type", event.eventName) - } - tc.processEvent(event) - case <-tc.stopChan: - if tc.debugMode { - tc.logger.Debug("🛑 Background processor received stop signal") - } - // Drain remaining events before shutting down - for { - select { - case event := <-tc.eventChan: - if tc.debugMode { - tc.logger.Debug("🔄 Draining event during shutdown", "event_type", event.eventName) - } - tc.processEvent(event) - default: - if tc.debugMode { - tc.logger.Debug("🛑 Background processor shutting down") - } - return - } - } - } - } -} - -// processEvent handles individual events in the background goroutine -func (tc *Client) processEvent(eventCtx EventWithContext) { - // Track that we're processing this event - atomic.AddInt64(&tc.requestCount, 1) - defer atomic.AddInt64(&tc.requestCount, -1) - - event := tc.createEvent(eventCtx.eventName, eventCtx.properties) - - if tc.debugMode { - tc.printEvent(&event) - } - - // Always send the event (regardless of debug mode) - tc.sendEvent(&event) -} diff --git a/internal/telemetry/events.go b/internal/telemetry/events.go index 16ea2aae8..332df35be 100644 --- a/internal/telemetry/events.go +++ b/internal/telemetry/events.go @@ -2,11 +2,10 @@ package telemetry import ( "context" - "fmt" "time" ) -// Track records a structured telemetry event with type-safe properties (non-blocking) +// Track records a structured telemetry event with type-safe properties (synchronous) // This is the only method for telemetry tracking, all event-specific methods are wrappers around this one func (tc *Client) Track(ctx context.Context, structuredEvent StructuredEvent) { eventType := structuredEvent.GetEventType() @@ -20,33 +19,23 @@ func (tc *Client) Track(ctx context.Context, structuredEvent StructuredEvent) { return } - // Send event to background processor (non-blocking) if !tc.enabled { return } // Debug logging to track event flow if tc.debugMode { - tc.logger.Debug("📤 Queuing telemetry event", "event_type", eventType, "channel_length", len(tc.eventChan)) + tc.logger.Debug("Processing telemetry event synchronously", "event_type", eventType) } - select { - case tc.eventChan <- EventWithContext{ - eventName: string(eventType), - properties: properties, - }: - // Event queued successfully - if tc.debugMode { - tc.logger.Debug("✅ Event queued successfully", "event_type", eventType, "channel_length", len(tc.eventChan)) - } - default: - // Channel full - drop event to avoid blocking - if tc.debugMode { - fmt.Printf("⚠️ Telemetry event dropped (buffer full): %s\n", eventType) - } - // Log dropped event for visibility - tc.logger.Warn("Telemetry event dropped", "reason", "buffer_full", "event_name", eventType) + event := tc.createEvent(string(eventType), properties) + + if tc.debugMode { + tc.printEvent(&event) } + + // Always send the event synchronously + tc.sendEvent(&event) } // RecordSessionStart initializes session tracking diff --git a/internal/telemetry/global.go b/internal/telemetry/global.go index a63c82c5e..de4407522 100644 --- a/internal/telemetry/global.go +++ b/internal/telemetry/global.go @@ -30,14 +30,6 @@ var ( globalTelemetryDebugMode = false ) -// SetGlobalToolTelemetryClient sets the global client for tool telemetry -// This allows other packages to record tool events without context passing -// This is now optional - if not called, automatic initialization will happen -func SetGlobalToolTelemetryClient(client *Client, logger *slog.Logger) { - globalToolTelemetryClient = client - // Logger is now handled internally by automatic initialization -} - // GetGlobalTelemetryClient returns the global telemetry client for adding to context func GetGlobalTelemetryClient() *Client { ensureGlobalTelemetryInitialized() @@ -87,7 +79,9 @@ func ensureGlobalTelemetryInitialized() { globalToolTelemetryClient = client if debugMode { - logger.Info("Auto-initialized telemetry", "enabled", enabled, "debug", debugMode) + // Use the telemetry logger wrapper for consistency + telemetryLogger := NewTelemetryLogger(logger) + telemetryLogger.Info("Auto-initialized telemetry", "enabled", enabled, "debug", debugMode) } }) } diff --git a/internal/telemetry/http.go b/internal/telemetry/http.go index a944894fc..776c3bc96 100644 --- a/internal/telemetry/http.go +++ b/internal/telemetry/http.go @@ -45,31 +45,29 @@ func (tc *Client) printEvent(event *EventPayload) { tc.logger.Error("Failed to marshal telemetry event", "error", err) return } - tc.logger.Info("🔍 TELEMETRY EVENT", "event", string(output)) + tc.logger.Info("event", "event", string(output)) } // sendEvent sends a single event to Docker events API and handles logging func (tc *Client) sendEvent(event *EventPayload) { // Send to Docker events API if conditions are met if tc.apiKey != "" && tc.endpoint != "" && tc.enabled { - tc.logger.Debug("Sending telemetry event via HTTP", "event", event.Event, "endpoint", tc.endpoint) + tc.logger.Debug("Sending telemetry event via HTTP", "event_type", event.Event, "endpoint", tc.endpoint) // Perform HTTP request inline if err := tc.performHTTPRequest(event); err != nil { - tc.logger.Debug("Failed to send telemetry event to Docker API", "error", err, "event", event.Event) + tc.logger.Debug("Failed to send telemetry event to Docker API", "error", err, "event_type", event.Event) } else { - tc.logger.Debug("Successfully sent telemetry event via HTTP", "event", event.Event) + tc.logger.Debug("Successfully sent telemetry event via HTTP", "event_type", event.Event) } } else { tc.logger.Debug("Skipping HTTP telemetry event - missing endpoint or API key or disabled", - "event", event.Event, + "event_type", event.Event, "has_endpoint", tc.endpoint != "", "has_api_key", tc.apiKey != "", "enabled", tc.enabled) } - // Event processing (OpenTelemetry tracing handled in run.go) - // Log the event logArgs := []any{ "event", event.Event, @@ -85,7 +83,7 @@ func (tc *Client) sendEvent(event *EventPayload) { logArgs = append(logArgs, "session_id", sessionID) } - tc.logger.Debug("Telemetry event recorded", logArgs...) + tc.logger.Debug("Event recorded", logArgs...) // Enhanced debug logging with full event structure if tc.logger.Enabled(context.Background(), slog.LevelDebug) { diff --git a/internal/telemetry/telemetry_test.go b/internal/telemetry/telemetry_test.go index 1c396d646..9edd92b0f 100644 --- a/internal/telemetry/telemetry_test.go +++ b/internal/telemetry/telemetry_test.go @@ -17,6 +17,7 @@ import ( // MockHTTPClient captures HTTP requests for testing type MockHTTPClient struct { + *http.Client mu sync.Mutex requests []*http.Request bodies [][]byte @@ -25,13 +26,15 @@ type MockHTTPClient struct { // NewMockHTTPClient creates a new mock HTTP client with a default success response func NewMockHTTPClient() *MockHTTPClient { - return &MockHTTPClient{ + mock := &MockHTTPClient{ response: &http.Response{ StatusCode: 200, Body: io.NopCloser(bytes.NewReader([]byte(`{"success": true}`))), Header: make(http.Header), }, } + mock.Client = &http.Client{Transport: mock} + return mock } // SetResponse allows updating the mock response for testing different scenarios @@ -41,8 +44,8 @@ func (m *MockHTTPClient) SetResponse(resp *http.Response) { m.response = resp } -// Do implements http.Client.Do and captures the request -func (m *MockHTTPClient) Do(req *http.Request) (*http.Response, error) { +// RoundTrip implements http.RoundTripper and captures the request +func (m *MockHTTPClient) RoundTrip(req *http.Request) (*http.Response, error) { m.mu.Lock() defer m.mu.Unlock() @@ -89,26 +92,11 @@ func TestNewClient(t *testing.T) { // Test enabled client with mock HTTP client to capture HTTP calls // Note: debug mode does NOT disable HTTP calls - it only adds extra logging mockHTTP := NewMockHTTPClient() - client, err := NewClientWithHTTPClient(logger, true, true, "test-version", mockHTTP) + _, err := NewClient(logger, true, true, "test-version", mockHTTP.Client) if err != nil { t.Fatalf("Failed to create enabled client: %v", err) } - if !client.IsEnabled() { - t.Error("Expected client to be enabled") - } - // Test disabled client - client, err = NewClient(logger, false, false, "test-version") - if err != nil { - t.Fatalf("Failed to create disabled client: %v", err) - } - if client.IsEnabled() { - t.Error("Expected client to be disabled") - } -} - -func TestDisabledClient(t *testing.T) { - logger := slog.New(slog.NewTextHandler(os.Stderr, nil)) client, err := NewClient(logger, false, false, "test-version") if err != nil { t.Fatalf("Failed to create disabled client: %v", err) @@ -128,7 +116,7 @@ func TestDisabledClient(t *testing.T) { func TestSessionTracking(t *testing.T) { logger := slog.New(slog.NewTextHandler(os.Stderr, nil)) mockHTTP := NewMockHTTPClient() - client, err := NewClientWithHTTPClient(logger, true, true, "test-version", mockHTTP) + client, err := NewClient(logger, true, true, "test-version", mockHTTP.Client) if err != nil { t.Fatalf("Failed to create client: %v", err) } @@ -182,7 +170,7 @@ func TestSessionTracking(t *testing.T) { func TestCommandTracking(t *testing.T) { logger := slog.New(slog.NewTextHandler(os.Stderr, nil)) mockHTTP := NewMockHTTPClient() - client, err := NewClientWithHTTPClient(logger, true, true, "test-version", mockHTTP) + client, err := NewClient(logger, true, true, "test-version", mockHTTP.Client) if err != nil { t.Fatalf("Failed to create client: %v", err) } @@ -233,7 +221,7 @@ func TestCommandTracking(t *testing.T) { func TestCommandTrackingWithError(t *testing.T) { logger := slog.New(slog.NewTextHandler(os.Stderr, nil)) mockHTTP := NewMockHTTPClient() - client, err := NewClientWithHTTPClient(logger, true, true, "test-version", mockHTTP) + client, err := NewClient(logger, true, true, "test-version", mockHTTP.Client) if err != nil { t.Fatalf("Failed to create client: %v", err) } @@ -314,6 +302,15 @@ func TestGetTelemetryEnabled(t *testing.T) { if !GetTelemetryEnabled() { t.Error("Expected telemetry to be enabled when TELEMETRY_ENABLED=true") } + + // Test other values default to enabled (only "false" disables) + testCases := []string{"1", "yes", "on", "enabled", "anything", ""} + for _, value := range testCases { + os.Setenv("TELEMETRY_ENABLED", value) + if !GetTelemetryEnabled() { + t.Errorf("Expected telemetry to be enabled when TELEMETRY_ENABLED=%s", value) + } + } } // testError is a simple error implementation for testing @@ -377,7 +374,7 @@ func TestAllEventTypes(t *testing.T) { logger := slog.New(slog.NewTextHandler(os.Stderr, nil)) // Use mock HTTP client to avoid actual HTTP calls in tests mockHTTP := NewMockHTTPClient() - client, err := NewClientWithHTTPClient(logger, true, true, "test-version", mockHTTP) + client, err := NewClient(logger, true, true, "test-version", mockHTTP.Client) if err != nil { t.Fatalf("Failed to create client: %v", err) } @@ -745,12 +742,11 @@ func TestBuildCommandInfo(t *testing.T) { func TestGlobalTelemetryFunctions(t *testing.T) { // Save original global state originalClient := globalToolTelemetryClient - originalOnce := globalTelemetryOnce originalVersion := globalTelemetryVersion originalDebugMode := globalTelemetryDebugMode defer func() { globalToolTelemetryClient = originalClient - globalTelemetryOnce = originalOnce + globalTelemetryOnce = sync.Once{} // Reset to new instance globalTelemetryVersion = originalVersion globalTelemetryDebugMode = originalDebugMode }() @@ -784,7 +780,7 @@ func TestHTTPRequestVerification(t *testing.T) { mockHTTP := NewMockHTTPClient() // Create client with mock HTTP client, endpoint, and API key to trigger HTTP calls - client, err := NewClientWithHTTPClient(logger, true, true, "test-version", mockHTTP) + client, err := NewClient(logger, true, true, "test-version", mockHTTP.Client) if err != nil { t.Fatalf("Failed to create client: %v", err) } @@ -898,7 +894,7 @@ func TestHTTPRequestVerification(t *testing.T) { // Test that no HTTP calls are made when endpoint/apiKey are missing t.Run("NoHTTPWhenMissingCredentials", func(t *testing.T) { mockHTTP2 := NewMockHTTPClient() - client2, err := NewClientWithHTTPClient(logger, true, true, "test-version", mockHTTP2) + client2, err := NewClient(logger, true, true, "test-version", mockHTTP2.Client) if err != nil { t.Fatalf("Failed to create client: %v", err) } @@ -923,7 +919,7 @@ func TestHTTPRequestVerification(t *testing.T) { // Test that no HTTP calls are made when client is disabled t.Run("NoHTTPWhenDisabled", func(t *testing.T) { mockHTTP3 := NewMockHTTPClient() - client3, err := NewClientWithHTTPClient(logger, false, true, "test-version", mockHTTP3) + client3, err := NewClient(logger, false, true, "test-version", mockHTTP3.Client) if err != nil { t.Fatalf("Failed to create client: %v", err) } @@ -942,33 +938,6 @@ func TestHTTPRequestVerification(t *testing.T) { }) } -// TestShutdownFlushesEvents verifies that Shutdown drains the event queue -func TestShutdownFlushesEvents(t *testing.T) { - logger := slog.New(slog.NewTextHandler(os.Stderr, nil)) - mockHTTP := NewMockHTTPClient() - client, err := NewClientWithHTTPClient(logger, true, true, "test-version", mockHTTP) - if err != nil { - t.Fatalf("Failed to create client: %v", err) - } - - client.endpoint = "https://test-shutdown.com/api" - client.apiKey = "shutdown-key" - client.header = "test-header" - - ctx := context.Background() - client.Track(ctx, &CommandEvent{Action: "shutdown-test", Success: true}) - - // Shutdown should flush pending events - err = client.Shutdown(ctx) - if err != nil { - t.Fatalf("Shutdown failed: %v", err) - } - - if mockHTTP.GetRequestCount() == 0 { - t.Error("Expected at least 1 HTTP request to be sent during Shutdown flush") - } -} - // SlowMockHTTPClient creates artificial backpressure by adding delays type SlowMockHTTPClient struct { *MockHTTPClient @@ -982,9 +951,9 @@ func NewSlowMockHTTPClient(delay time.Duration) *SlowMockHTTPClient { } } -func (s *SlowMockHTTPClient) Do(req *http.Request) (*http.Response, error) { +func (s *SlowMockHTTPClient) RoundTrip(req *http.Request) (*http.Response, error) { time.Sleep(s.delay) // Add artificial delay - return s.MockHTTPClient.Do(req) + return s.MockHTTPClient.RoundTrip(req) } // TestEventBufferOverflowDropsEvents verifies that events are dropped when buffer is full @@ -993,7 +962,7 @@ func TestEventBufferOverflowDropsEvents(t *testing.T) { slowMock := NewSlowMockHTTPClient(50 * time.Millisecond) logger := slog.New(slog.NewTextHandler(os.Stderr, nil)) - client, err := NewClientWithHTTPClient(logger, true, true, "test-version", slowMock) + client, err := NewClient(logger, true, true, "test-version", slowMock.Client) if err != nil { t.Fatalf("Failed to create client: %v", err) } @@ -1002,28 +971,25 @@ func TestEventBufferOverflowDropsEvents(t *testing.T) { client.apiKey = "overflow-key" client.header = "test-header" - // Fill buffer completely by sending many events rapidly - bufferSize := cap(client.eventChan) // Use actual production buffer size (1000) + // With synchronous processing, there's no buffer overflow to test + // Events are processed immediately, so we just verify they all get processed + numEvents := 10 // Send a reasonable number for synchronous processing - // Send events very rapidly to overwhelm the slow processor - for i := 0; i < bufferSize+100; i++ { // Send way more than capacity + // Send events synchronously + for i := 0; i < numEvents; i++ { client.Track(context.Background(), &CommandEvent{ Action: "overflow-test", Success: true, }) } - // Give time for processing and potential overflow - time.Sleep(100 * time.Millisecond) - - // Verify the channel length is reasonable (either full or being processed) - channelLen := len(client.eventChan) - if channelLen > bufferSize { - t.Errorf("Event channel exceeded capacity: len=%d cap=%d", channelLen, bufferSize) + // With synchronous processing, all events should be processed immediately + expectedRequests := numEvents + if slowMock.GetRequestCount() != expectedRequests { + t.Errorf("Expected %d requests with synchronous processing, got %d", expectedRequests, slowMock.GetRequestCount()) } - // The test passes if we don't exceed capacity - this verifies overflow protection works - t.Logf("Buffer handled overflow correctly: len=%d cap=%d", channelLen, bufferSize) + t.Logf("Synchronous processing handled %d events correctly", numEvents) // Clean shutdown } @@ -1032,7 +998,7 @@ func TestEventBufferOverflowDropsEvents(t *testing.T) { func TestNon2xxHTTPResponseHandling(t *testing.T) { logger := slog.New(slog.NewTextHandler(os.Stderr, nil)) mockHTTP := NewMockHTTPClient() - client, err := NewClientWithHTTPClient(logger, true, true, "test-version", mockHTTP) + client, err := NewClient(logger, true, true, "test-version", mockHTTP.Client) if err != nil { t.Fatalf("Failed to create client: %v", err) } diff --git a/internal/telemetry/types.go b/internal/telemetry/types.go index 10fc68751..4c45161eb 100644 --- a/internal/telemetry/types.go +++ b/internal/telemetry/types.go @@ -1,7 +1,6 @@ package telemetry import ( - "log/slog" "net/http" "sync" "time" @@ -36,12 +35,6 @@ type EventPayload struct { Properties map[string]any `json:"properties,omitempty"` } -// EventWithContext wraps an event with its context for async processing -type EventWithContext struct { - eventName string - properties map[string]any -} - // COMMAND EVENTS // CommandEvent represents command execution events @@ -265,7 +258,7 @@ type HTTPClient interface { // Client provides simplified telemetry functionality for cagent type Client struct { - logger *slog.Logger + logger *telemetryLogger enabled bool debugMode bool // Print to stdout instead of sending httpClient HTTPClient @@ -275,14 +268,6 @@ type Client struct { version string // App version for User-Agent and events mu sync.RWMutex - // Session tracking (consolidated) + // Session tracking session SessionState - - // Async processing - eventChan chan EventWithContext - stopChan chan struct{} - done chan struct{} - - // HTTP request tracking - requestCount int64 // Atomic counter for active requests } diff --git a/internal/telemetry/utils.go b/internal/telemetry/utils.go index 0a4f68819..d8e8c8b7e 100644 --- a/internal/telemetry/utils.go +++ b/internal/telemetry/utils.go @@ -33,12 +33,13 @@ func getSystemInfo() (osName, osVersion, osLanguage string) { return osInfo, "", osLang } -// GetTelemetryEnabled checks if telemetry should be enabled based on environment or build-time config func GetTelemetryEnabled() bool { if env := os.Getenv("TELEMETRY_ENABLED"); env != "" { - return env == "true" + // Only disable if explicitly set to "false" + return env != "false" } - return TelemetryEnabled == "true" + // Default to true (telemetry enabled) + return true } func getTelemetryEndpoint() string {