Skip to content

Commit bb52e76

Browse files
committed
feat: add centralized DEBUG_ANALYTICS flag for dev-mode analytics logging
- Add DEBUG_ANALYTICS constant in common/src/env.ts as single source of truth - Update common/src/analytics.ts to log backend events in dev mode when flag is enabled - Update cli/src/utils/analytics.ts to use centralized flag instead of local variable - Flag is disabled by default, enable for debugging analytics events
1 parent 08ceb4a commit bb52e76

File tree

10 files changed

+82
-29
lines changed

10 files changed

+82
-29
lines changed

cli/src/chat.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -489,11 +489,13 @@ export const Chat = ({
489489
useEffect(() => {
490490
if (slashContext.active && !prevSlashActiveRef.current) {
491491
trackEvent(AnalyticsEvent.SLASH_MENU_ACTIVATED, {
492-
query: slashContext.query,
492+
queryLength: slashContext.query.length,
493+
matchCount: slashMatches.length,
494+
inputLength: inputValue.length,
493495
})
494496
}
495497
prevSlashActiveRef.current = slashContext.active
496-
}, [slashContext.active, slashContext.query])
498+
}, [slashContext.active, slashContext.query, slashMatches.length, inputValue.length])
497499

498500
// Reset suggestion menu indexes when context changes
499501
useEffect(() => {

cli/src/commands/init.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export function handleInitializationFlowLocally(): {
6969
trackEvent(AnalyticsEvent.KNOWLEDGE_FILE_UPDATED, {
7070
action: 'created',
7171
fileName: KNOWLEDGE_FILE_NAME,
72+
fileSizeBytes: Buffer.byteLength(INITIAL_KNOWLEDGE_FILE, 'utf8'),
7273
})
7374
}
7475

cli/src/commands/router.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export function runBashCommand(command: string) {
4848
const ghost = streamingAgents.size > 0 || isChainInProgress
4949
const id = crypto.randomUUID()
5050
const commandCwd = process.cwd()
51+
const startTime = Date.now()
5152

5253
if (ghost) {
5354
// Ghost mode: add to pending messages
@@ -85,11 +86,17 @@ export function runBashCommand(command: string) {
8586
const exitCode = 'exitCode' in value ? value.exitCode ?? 0 : 0
8687

8788
// Track terminal command completion
89+
const durationMs = Date.now() - startTime
8890
trackEvent(AnalyticsEvent.TERMINAL_COMMAND_COMPLETED, {
8991
command: command.split(' ')[0], // Just the command name, not args
9092
exitCode,
9193
success: exitCode === 0,
9294
ghost,
95+
durationMs,
96+
hasStdout: stdout.length > 0,
97+
hasStderr: stderr.length > 0,
98+
stdoutLength: stdout.length,
99+
stderrLength: stderr.length,
93100
})
94101

95102
if (ghost) {
@@ -143,12 +150,18 @@ export function runBashCommand(command: string) {
143150
error instanceof Error ? error.message : String(error)
144151

145152
// Track terminal command completion with error
153+
const durationMs = Date.now() - startTime
146154
trackEvent(AnalyticsEvent.TERMINAL_COMMAND_COMPLETED, {
147155
command: command.split(' ')[0], // Just the command name, not args
148156
exitCode: 1,
149157
success: false,
150158
ghost,
151-
error: true,
159+
durationMs,
160+
hasStdout: false,
161+
hasStderr: true,
162+
stdoutLength: 0,
163+
stderrLength: errorMessage.length,
164+
isException: true,
152165
})
153166

154167
if (ghost) {
@@ -261,13 +274,18 @@ export async function routeUserPrompt(
261274
if (!trimmed && pendingImages.length === 0) return
262275

263276
// Track user input complete
277+
// Count @ mentions (simple pattern match - more accurate than nothing)
278+
const mentionMatches = trimmed.match(/@\S+/g) || []
264279
trackEvent(AnalyticsEvent.USER_INPUT_COMPLETE, {
265280
inputLength: trimmed.length,
266281
mode: agentMode,
267282
inputMode,
268283
hasImages: pendingImages.length > 0,
269284
imageCount: pendingImages.length,
270285
isSlashCommand: isSlashCommand(trimmed),
286+
isBashCommand: trimmed.startsWith('!'),
287+
hasMentions: mentionMatches.length > 0,
288+
mentionCount: mentionMatches.length,
271289
})
272290

273291
// Handle bash mode commands
@@ -407,6 +425,8 @@ export async function routeUserPrompt(
407425
trackEvent(AnalyticsEvent.SLASH_COMMAND_USED, {
408426
command: commandDef.name,
409427
hasArgs: args.trim().length > 0,
428+
argsLength: args.trim().length,
429+
agentMode,
410430
})
411431

412432
// The command handler (via defineCommand/defineCommandWithArgs factories)
@@ -444,9 +464,12 @@ export async function routeUserPrompt(
444464

445465
// Unknown slash command - show error
446466
if (isSlashCommand(trimmed)) {
447-
// Track invalid/unknown command
467+
// Track invalid/unknown command (only log command name, not full input for privacy)
468+
const attemptedCmd = parseCommand(trimmed)
448469
trackEvent(AnalyticsEvent.INVALID_COMMAND, {
449-
command: trimmed,
470+
attemptedCommand: attemptedCmd,
471+
inputLength: trimmed.length,
472+
agentMode,
450473
})
451474

452475
setMessages((prev) => [

cli/src/hooks/use-auth-state.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ export const useAuthState = ({
8484
// Track successful login
8585
trackEvent(AnalyticsEvent.LOGIN, {
8686
userId: loggedInUser.id,
87+
hasEmail: Boolean(loggedInUser.email),
88+
hasName: Boolean(loggedInUser.name),
8789
})
8890

8991
// Reset the SDK client to pick up new credentials

cli/src/index.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -287,10 +287,16 @@ async function main(): Promise<void> {
287287
// Change process working directory
288288
process.chdir(newProjectPath)
289289

290-
// Track directory change
290+
// Track directory change (avoid logging full paths for privacy)
291+
// Use existsSync to check if .git exists in the new path
292+
const fs = await import('fs')
293+
const path = await import('path')
294+
const isGitRepo = fs.existsSync(path.join(newProjectPath, '.git'))
295+
const pathDepth = newProjectPath.split(path.sep).filter(Boolean).length
291296
trackEvent(AnalyticsEvent.CHANGE_DIRECTORY, {
292-
from: previousPath,
293-
to: newProjectPath,
297+
isGitRepo,
298+
pathDepth,
299+
isHomeDir: newProjectPath === os.homedir(),
294300
})
295301
// Update the project root in the module state
296302
setProjectRoot(newProjectPath)

cli/src/utils/analytics.ts

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,16 @@ import {
44
type AnalyticsClientWithIdentify,
55
type PostHogClientOptions,
66
} from '@codebuff/common/analytics-core'
7-
import { env as defaultEnv, IS_PROD as defaultIsProd } from '@codebuff/common/env'
7+
import {
8+
env as defaultEnv,
9+
IS_PROD as defaultIsProd,
10+
DEBUG_ANALYTICS,
11+
} from '@codebuff/common/env'
812

913
import type { AnalyticsEvent } from '@codebuff/common/constants/analytics-events'
1014

15+
import { logger } from './logger'
16+
1117
// Re-export types from core for backwards compatibility
1218
export type { AnalyticsClientWithIdentify as AnalyticsClient } from '@codebuff/common/analytics-core'
1319

@@ -42,15 +48,13 @@ export interface AnalyticsDeps {
4248
NEXT_PUBLIC_POSTHOG_HOST_URL?: string
4349
}
4450
isProd: boolean
45-
createClient: (apiKey: string, options: PostHogClientOptions) => AnalyticsClientWithIdentify
51+
createClient: (
52+
apiKey: string,
53+
options: PostHogClientOptions,
54+
) => AnalyticsClientWithIdentify
4655
generateAnonymousId?: () => string
4756
}
4857

49-
// Prints the events to console
50-
// It's very noisy, so recommended you set this to true
51-
// only when you're actively adding new analytics
52-
let DEBUG_DEV_EVENTS = false
53-
5458
// Anonymous ID used before user identification (for PostHog alias)
5559
let anonymousId: string | undefined
5660
// Real user ID after identification
@@ -65,7 +69,8 @@ function resolveDeps(): ResolvedAnalyticsDeps {
6569
env: injectedDeps?.env ?? defaultEnv,
6670
isProd: injectedDeps?.isProd ?? defaultIsProd,
6771
createClient: injectedDeps?.createClient ?? createPostHogClient,
68-
generateAnonymousId: injectedDeps?.generateAnonymousId ?? generateAnonymousId,
72+
generateAnonymousId:
73+
injectedDeps?.generateAnonymousId ?? generateAnonymousId,
6974
}
7075
}
7176

@@ -167,12 +172,8 @@ export function trackEvent(
167172
}
168173

169174
if (!isProd) {
170-
if (DEBUG_DEV_EVENTS) {
171-
console.log('Analytics event sent', {
172-
event,
173-
properties,
174-
distinctId,
175-
})
175+
if (DEBUG_ANALYTICS) {
176+
logger.debug({ event, properties, distinctId }, `[analytics] ${event}`)
176177
}
177178
return
178179
}
@@ -210,12 +211,11 @@ export function identifyUser(userId: string, properties?: Record<string, any>) {
210211
identified = true
211212

212213
if (!isProd) {
213-
if (DEBUG_DEV_EVENTS) {
214-
console.log('Identify event sent', {
215-
userId,
216-
previousAnonymousId,
217-
properties,
218-
})
214+
if (DEBUG_ANALYTICS) {
215+
logger.debug(
216+
{ userId, previousAnonymousId, properties },
217+
'[analytics] user identified',
218+
)
219219
}
220220
return
221221
}

common/src/analytics.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { createPostHogClient, type AnalyticsClient } from './analytics-core'
22
import { AnalyticsEvent } from './constants/analytics-events'
33
import type { Logger } from '@codebuff/common/types/contracts/logger'
4-
import { env } from '@codebuff/common/env'
4+
import { env, DEBUG_ANALYTICS } from '@codebuff/common/env'
55

66
let client: AnalyticsClient | undefined
77

@@ -43,6 +43,9 @@ export function trackEvent({
4343
}) {
4444
// Don't track events in non-production environments
4545
if (env.NEXT_PUBLIC_CB_ENVIRONMENT !== 'prod') {
46+
if (DEBUG_ANALYTICS) {
47+
logger.debug({ event, userId, properties }, `[analytics] ${event}`)
48+
}
4649
return
4750
}
4851

common/src/env.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,7 @@ export const IS_DEV = env.NEXT_PUBLIC_CB_ENVIRONMENT === 'dev'
1717
export const IS_TEST = env.NEXT_PUBLIC_CB_ENVIRONMENT === 'test'
1818
export const IS_PROD = env.NEXT_PUBLIC_CB_ENVIRONMENT === 'prod'
1919
export const IS_CI = process.env.CODEBUFF_GITHUB_ACTIONS === 'true'
20+
21+
// Debug flag for logging analytics events in dev mode
22+
// Set to true when actively debugging analytics - affects both CLI and backend
23+
export const DEBUG_ANALYTICS = false

packages/agent-runtime/src/main-prompt.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,11 @@ export async function mainPrompt(
7979
hasPrompt: !!prompt,
8080
hasContent: !!content,
8181
hasPromptParams: !!promptParams && Object.keys(promptParams).length > 0,
82+
promptParamsCount: promptParams ? Object.keys(promptParams).length : 0,
8283
fingerprintId,
84+
promptLength: prompt?.length ?? 0,
85+
contentLength: content?.length ?? 0,
86+
messageHistoryLength: mainAgentState.messageHistory.length,
8387
},
8488
logger,
8589
})

packages/billing/src/balance-calculator.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,7 @@ export async function consumeCredits(params: {
376376
userId,
377377
properties: {
378378
creditsConsumed: result.consumed,
379+
creditsRequested: creditsToConsume,
379380
fromPurchased: result.fromPurchased,
380381
source: 'consumeCredits',
381382
},
@@ -596,11 +597,18 @@ export async function consumeCreditsAndAddAgentStep(params: {
596597
userId,
597598
properties: {
598599
creditsConsumed: result.consumed,
600+
creditsRequested: credits,
599601
fromPurchased: result.fromPurchased,
600602
messageId,
601603
agentId,
602604
model,
603605
source: 'consumeCreditsAndAddAgentStep',
606+
inputTokens,
607+
outputTokens,
608+
reasoningTokens: reasoningTokens ?? 0,
609+
cacheReadInputTokens,
610+
latencyMs,
611+
byok,
604612
},
605613
logger,
606614
})

0 commit comments

Comments
 (0)