From 914805f5ea43824e4c39e384febe5d6418cba043 Mon Sep 17 00:00:00 2001 From: czlonkowski <56956555+czlonkowski@users.noreply.github.com> Date: Wed, 8 Oct 2025 13:01:43 +0200 Subject: [PATCH] feat: add Docker/cloud environment detection to telemetry (v2.18.1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added isDocker and cloudPlatform fields to session_start telemetry events to enable measurement of the v2.17.1 user ID stability fix. Changes: - Added detectCloudPlatform() method to event-tracker.ts - Updated trackSessionStart() to include isDocker and cloudPlatform - Added 16 comprehensive unit tests for environment detection - Tests for all 8 cloud platforms (Railway, Render, Fly, Heroku, AWS, K8s, GCP, Azure) - Tests for Docker detection, local env, and combined scenarios - Version bumped to 2.18.1 - Comprehensive CHANGELOG entry Impact: - Enables validation of v2.17.1 boot_id-based user ID stability - Allows segmentation of metrics by environment - 100% backward compatible - only adds new fields - All tests passing, TypeScript compilation successful 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- CHANGELOG.md | 111 ++++++++++++ package.json | 2 +- package.runtime.json | 2 +- src/telemetry/event-tracker.ts | 18 ++ tests/unit/telemetry/event-tracker.test.ts | 193 +++++++++++++++++++++ 5 files changed, 324 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e9a1df..38bd475 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,117 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.18.1] - 2025-10-08 + +### 🔍 Telemetry Enhancement + +**Added Docker/cloud environment detection to session_start events.** + +This release enables measurement of the v2.17.1 user ID stability fix by tracking which users are in Docker/cloud environments. + +#### Problem + +The v2.17.1 fix for Docker/cloud user ID stability (boot_id-based IDs) could not be validated because telemetry didn't capture Docker/cloud environment flags. Analysis showed: +- Zero Docker/cloud users detected across all versions +- No way to measure if the fix is working +- Cannot determine what % of users are affected +- Cannot validate stable user IDs are being generated + +#### Added + +- **Docker Detection**: `isDocker` boolean flag in session_start events + - Detects `IS_DOCKER=true` environment variable + - Identifies container deployments using boot_id-based stable IDs + +- **Cloud Platform Detection**: `cloudPlatform` string in session_start events + - Detects 8 cloud platforms: Railway, Render, Fly.io, Heroku, AWS, Kubernetes, GCP, Azure + - Identifies which platform users are deploying to + - Returns `null` for local/non-cloud environments + +- **New Detection Method**: `detectCloudPlatform()` in event tracker + - Checks platform-specific environment variables + - Returns platform name or null + - Uses same logic as config-manager's cloud detection + +#### Changed + +- `trackSessionStart()` in `src/telemetry/event-tracker.ts` + - Now includes `isDocker` field (boolean) + - Now includes `cloudPlatform` field (string | null) + - Backward compatible - only adds new fields + +#### Testing + +- 16 new unit tests for environment detection +- Tests for Docker detection with IS_DOCKER flag +- Tests for all 8 cloud platform detections +- Tests for local environment (no flags) +- Tests for combined Docker + cloud scenarios +- 100% coverage for new detection logic + +#### Impact + +**Enables Future Analysis**: +- Measure % of users in Docker/cloud vs local +- Validate v2.17.1 boot_id-based user ID stability +- Segment retention metrics by environment +- Identify environment-specific issues +- Calculate actual Docker user duplicate rate reduction + +**Expected Insights** (once data collected): +- Actual % of Docker/cloud users in user base +- Validation that boot_id method is being used +- User ID stability improvements measurable +- Environment-specific error patterns +- Platform distribution of user base + +**No Breaking Changes**: +- Only adds new fields to existing events +- All existing code continues working +- Event validator handles new fields automatically +- 100% backward compatible + +#### Technical Details + +**Detection Logic**: +```typescript +isDocker: process.env.IS_DOCKER === 'true' +cloudPlatform: detectCloudPlatform() // Checks 8 env vars +``` + +**Platform Detection Priority**: +1. Railway: `RAILWAY_ENVIRONMENT` +2. Render: `RENDER` +3. Fly.io: `FLY_APP_NAME` +4. Heroku: `HEROKU_APP_NAME` +5. AWS: `AWS_EXECUTION_ENV` +6. Kubernetes: `KUBERNETES_SERVICE_HOST` +7. GCP: `GOOGLE_CLOUD_PROJECT` +8. Azure: `AZURE_FUNCTIONS_ENVIRONMENT` + +**Event Structure**: +```json +{ + "event": "session_start", + "properties": { + "version": "2.18.1", + "platform": "linux", + "arch": "x64", + "nodeVersion": "v20.0.0", + "isDocker": true, + "cloudPlatform": "railway" + } +} +``` + +#### Next Steps + +1. Deploy v2.18.1 to production +2. Wait 24-48 hours for data collection +3. Re-run telemetry analysis with environment segmentation +4. Validate v2.17.1 boot_id fix effectiveness +5. Calculate actual Docker user duplicate rate reduction + ## [2.18.0] - 2025-10-08 ### 🎯 Validation Warning System Redesign diff --git a/package.json b/package.json index 04aa706..352ec12 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "n8n-mcp", - "version": "2.18.0", + "version": "2.18.1", "description": "Integration between n8n workflow automation and Model Context Protocol (MCP)", "main": "dist/index.js", "bin": { diff --git a/package.runtime.json b/package.runtime.json index 57ad458..990e0b3 100644 --- a/package.runtime.json +++ b/package.runtime.json @@ -1,6 +1,6 @@ { "name": "n8n-mcp-runtime", - "version": "2.17.6", + "version": "2.18.1", "description": "n8n MCP Server Runtime Dependencies Only", "private": true, "dependencies": { diff --git a/src/telemetry/event-tracker.ts b/src/telemetry/event-tracker.ts index c0b989f..4c00a94 100644 --- a/src/telemetry/event-tracker.ts +++ b/src/telemetry/event-tracker.ts @@ -175,9 +175,27 @@ export class TelemetryEventTracker { platform: process.platform, arch: process.arch, nodeVersion: process.version, + isDocker: process.env.IS_DOCKER === 'true', + cloudPlatform: this.detectCloudPlatform(), }); } + /** + * Detect cloud platform from environment variables + * Returns platform name or null if not in cloud + */ + private detectCloudPlatform(): string | null { + if (process.env.RAILWAY_ENVIRONMENT) return 'railway'; + if (process.env.RENDER) return 'render'; + if (process.env.FLY_APP_NAME) return 'fly'; + if (process.env.HEROKU_APP_NAME) return 'heroku'; + if (process.env.AWS_EXECUTION_ENV) return 'aws'; + if (process.env.KUBERNETES_SERVICE_HOST) return 'kubernetes'; + if (process.env.GOOGLE_CLOUD_PROJECT) return 'gcp'; + if (process.env.AZURE_FUNCTIONS_ENVIRONMENT) return 'azure'; + return null; + } + /** * Track search queries */ diff --git a/tests/unit/telemetry/event-tracker.test.ts b/tests/unit/telemetry/event-tracker.test.ts index 1846185..2c94aa7 100644 --- a/tests/unit/telemetry/event-tracker.test.ts +++ b/tests/unit/telemetry/event-tracker.test.ts @@ -774,4 +774,197 @@ describe('TelemetryEventTracker', () => { expect(events[0].properties.context).toHaveLength(100); }); }); + + describe('trackSessionStart()', () => { + // Store original env vars + const originalEnv = { ...process.env }; + + afterEach(() => { + // Restore original env vars after each test + process.env = { ...originalEnv }; + eventTracker.clearEventQueue(); + }); + + it('should track session start with basic environment info', () => { + eventTracker.trackSessionStart(); + + const events = eventTracker.getEventQueue(); + expect(events).toHaveLength(1); + expect(events[0]).toMatchObject({ + user_id: 'test-user-123', + event: 'session_start', + }); + + const props = events[0].properties; + expect(props.version).toBeDefined(); + expect(typeof props.version).toBe('string'); + expect(props.platform).toBeDefined(); + expect(props.arch).toBeDefined(); + expect(props.nodeVersion).toBeDefined(); + expect(props.isDocker).toBe(false); + expect(props.cloudPlatform).toBeNull(); + }); + + it('should detect Docker environment', () => { + process.env.IS_DOCKER = 'true'; + eventTracker.trackSessionStart(); + + const events = eventTracker.getEventQueue(); + expect(events[0].properties.isDocker).toBe(true); + expect(events[0].properties.cloudPlatform).toBeNull(); + }); + + it('should detect Railway cloud platform', () => { + process.env.RAILWAY_ENVIRONMENT = 'production'; + eventTracker.trackSessionStart(); + + const events = eventTracker.getEventQueue(); + expect(events[0].properties.isDocker).toBe(false); + expect(events[0].properties.cloudPlatform).toBe('railway'); + }); + + it('should detect Render cloud platform', () => { + process.env.RENDER = 'true'; + eventTracker.trackSessionStart(); + + const events = eventTracker.getEventQueue(); + expect(events[0].properties.isDocker).toBe(false); + expect(events[0].properties.cloudPlatform).toBe('render'); + }); + + it('should detect Fly.io cloud platform', () => { + process.env.FLY_APP_NAME = 'my-app'; + eventTracker.trackSessionStart(); + + const events = eventTracker.getEventQueue(); + expect(events[0].properties.isDocker).toBe(false); + expect(events[0].properties.cloudPlatform).toBe('fly'); + }); + + it('should detect Heroku cloud platform', () => { + process.env.HEROKU_APP_NAME = 'my-app'; + eventTracker.trackSessionStart(); + + const events = eventTracker.getEventQueue(); + expect(events[0].properties.isDocker).toBe(false); + expect(events[0].properties.cloudPlatform).toBe('heroku'); + }); + + it('should detect AWS cloud platform', () => { + process.env.AWS_EXECUTION_ENV = 'AWS_ECS_FARGATE'; + eventTracker.trackSessionStart(); + + const events = eventTracker.getEventQueue(); + expect(events[0].properties.isDocker).toBe(false); + expect(events[0].properties.cloudPlatform).toBe('aws'); + }); + + it('should detect Kubernetes cloud platform', () => { + process.env.KUBERNETES_SERVICE_HOST = '10.0.0.1'; + eventTracker.trackSessionStart(); + + const events = eventTracker.getEventQueue(); + expect(events[0].properties.isDocker).toBe(false); + expect(events[0].properties.cloudPlatform).toBe('kubernetes'); + }); + + it('should detect GCP cloud platform', () => { + process.env.GOOGLE_CLOUD_PROJECT = 'my-project'; + eventTracker.trackSessionStart(); + + const events = eventTracker.getEventQueue(); + expect(events[0].properties.isDocker).toBe(false); + expect(events[0].properties.cloudPlatform).toBe('gcp'); + }); + + it('should detect Azure cloud platform', () => { + process.env.AZURE_FUNCTIONS_ENVIRONMENT = 'Production'; + eventTracker.trackSessionStart(); + + const events = eventTracker.getEventQueue(); + expect(events[0].properties.isDocker).toBe(false); + expect(events[0].properties.cloudPlatform).toBe('azure'); + }); + + it('should detect Docker + cloud platform combination', () => { + process.env.IS_DOCKER = 'true'; + process.env.RAILWAY_ENVIRONMENT = 'production'; + eventTracker.trackSessionStart(); + + const events = eventTracker.getEventQueue(); + expect(events[0].properties.isDocker).toBe(true); + expect(events[0].properties.cloudPlatform).toBe('railway'); + }); + + it('should handle local environment (no Docker, no cloud)', () => { + // Ensure no Docker or cloud env vars are set + delete process.env.IS_DOCKER; + delete process.env.RAILWAY_ENVIRONMENT; + delete process.env.RENDER; + delete process.env.FLY_APP_NAME; + delete process.env.HEROKU_APP_NAME; + delete process.env.AWS_EXECUTION_ENV; + delete process.env.KUBERNETES_SERVICE_HOST; + delete process.env.GOOGLE_CLOUD_PROJECT; + delete process.env.AZURE_FUNCTIONS_ENVIRONMENT; + + eventTracker.trackSessionStart(); + + const events = eventTracker.getEventQueue(); + expect(events[0].properties.isDocker).toBe(false); + expect(events[0].properties.cloudPlatform).toBeNull(); + }); + + it('should prioritize Railway over other cloud platforms', () => { + // Set multiple cloud env vars - Railway should win (first in detection chain) + process.env.RAILWAY_ENVIRONMENT = 'production'; + process.env.RENDER = 'true'; + process.env.FLY_APP_NAME = 'my-app'; + + eventTracker.trackSessionStart(); + + const events = eventTracker.getEventQueue(); + expect(events[0].properties.cloudPlatform).toBe('railway'); + }); + + it('should not track when disabled', () => { + mockIsEnabled.mockReturnValue(false); + process.env.IS_DOCKER = 'true'; + eventTracker.trackSessionStart(); + + const events = eventTracker.getEventQueue(); + expect(events).toHaveLength(0); + }); + + it('should treat IS_DOCKER=false as not Docker', () => { + process.env.IS_DOCKER = 'false'; + eventTracker.trackSessionStart(); + + const events = eventTracker.getEventQueue(); + expect(events[0].properties.isDocker).toBe(false); + }); + + it('should include version, platform, arch, and nodeVersion', () => { + eventTracker.trackSessionStart(); + + const events = eventTracker.getEventQueue(); + const props = events[0].properties; + + // Check all expected fields are present + expect(props).toHaveProperty('version'); + expect(props).toHaveProperty('platform'); + expect(props).toHaveProperty('arch'); + expect(props).toHaveProperty('nodeVersion'); + expect(props).toHaveProperty('isDocker'); + expect(props).toHaveProperty('cloudPlatform'); + + // Verify types + expect(typeof props.version).toBe('string'); + expect(typeof props.platform).toBe('string'); + expect(typeof props.arch).toBe('string'); + expect(typeof props.nodeVersion).toBe('string'); + expect(typeof props.isDocker).toBe('boolean'); + expect(props.cloudPlatform === null || typeof props.cloudPlatform === 'string').toBe(true); + }); + }); }); \ No newline at end of file