2025-06-13 20:28:18 -07:00
#!/usr/bin/env node
/ * *
* @ license
* Copyright 2025 Google LLC
* SPDX - License - Identifier : Apache - 2.0
* /
import path from 'path' ;
import fs from 'fs' ;
import { spawn , execSync } from 'child_process' ;
import {
OTEL _DIR ,
BIN _DIR ,
fileExists ,
waitForPort ,
ensureBinary ,
manageTelemetrySettings ,
registerCleanup ,
} from './telemetry_utils.js' ;
const OTEL _CONFIG _FILE = path . join ( OTEL _DIR , 'collector-gcp.yaml' ) ;
const OTEL _LOG _FILE = path . join ( OTEL _DIR , 'collector-gcp.log' ) ;
const getOtelConfigContent = ( projectId ) => `
receivers :
otlp :
protocols :
grpc :
endpoint : "localhost:4317"
processors :
batch :
timeout : 1 s
exporters :
googlecloud :
project : "${projectId}"
metric :
prefix : "custom.googleapis.com/gemini_cli"
log :
default _log _name : "gemini_cli"
debug :
verbosity : detailed
service :
telemetry :
logs :
level : "debug"
metrics :
level : "none"
pipelines :
traces :
receivers : [ otlp ]
processors : [ batch ]
exporters : [ googlecloud ]
metrics :
receivers : [ otlp ]
processors : [ batch ]
exporters : [ googlecloud , debug ]
logs :
receivers : [ otlp ]
processors : [ batch ]
exporters : [ googlecloud , debug ]
` ;
async function main ( ) {
console . log ( '✨ Starting Local Telemetry Exporter for Google Cloud ✨' ) ;
let collectorProcess ;
let collectorLogFd ;
const originalSandboxSetting = manageTelemetrySettings (
true ,
'http://localhost:4317' ,
) ;
registerCleanup (
( ) => [ collectorProcess ] . filter ( ( p ) => p ) , // Function to get processes
( ) => [ collectorLogFd ] . filter ( ( fd ) => fd ) , // Function to get FDs
originalSandboxSetting ,
) ;
const projectId = process . env . GOOGLE _CLOUD _PROJECT ;
if ( ! projectId ) {
console . error (
'🛑 Error: GOOGLE_CLOUD_PROJECT environment variable is not set.' ,
) ;
console . log ( 'Please set it to your Google Cloud Project ID and try again.' ) ;
process . exit ( 1 ) ;
}
console . log ( ` ✅ Using Google Cloud Project ID: ${ projectId } ` ) ;
console . log ( '\n🔑 Please ensure you are authenticated with Google Cloud:' ) ;
console . log (
' - Run `gcloud auth application-default login` OR ensure `GOOGLE_APPLICATION_CREDENTIALS` environment variable points to a valid service account key.' ,
) ;
console . log (
' - The account needs "Cloud Trace Agent", "Monitoring Metric Writer", and "Logs Writer" roles.' ,
) ;
if ( ! fileExists ( BIN _DIR ) ) fs . mkdirSync ( BIN _DIR , { recursive : true } ) ;
const otelcolPath = await ensureBinary (
'otelcol-contrib' ,
'open-telemetry/opentelemetry-collector-releases' ,
( version , platform , arch , ext ) =>
` otelcol-contrib_ ${ version } _ ${ platform } _ ${ arch } . ${ ext } ` ,
'otelcol-contrib' ,
false , // isJaeger = false
) . catch ( ( e ) => {
console . error ( ` 🛑 Error getting otelcol-contrib: ${ e . message } ` ) ;
return null ;
} ) ;
if ( ! otelcolPath ) process . exit ( 1 ) ;
console . log ( '🧹 Cleaning up old processes and logs...' ) ;
try {
execSync ( 'pkill -f "otelcol-contrib"' ) ;
console . log ( '✅ Stopped existing otelcol-contrib process.' ) ;
} catch ( _e ) {
/* no-op */
}
try {
fs . unlinkSync ( OTEL _LOG _FILE ) ;
console . log ( '✅ Deleted old GCP collector log.' ) ;
} catch ( e ) {
if ( e . code !== 'ENOENT' ) console . error ( e ) ;
}
if ( ! fileExists ( OTEL _DIR ) ) fs . mkdirSync ( OTEL _DIR , { recursive : true } ) ;
fs . writeFileSync ( OTEL _CONFIG _FILE , getOtelConfigContent ( projectId ) ) ;
console . log ( ` 📄 Wrote OTEL collector config to ${ OTEL _CONFIG _FILE } ` ) ;
console . log ( ` 🚀 Starting OTEL collector for GCP... Logs: ${ OTEL _LOG _FILE } ` ) ;
collectorLogFd = fs . openSync ( OTEL _LOG _FILE , 'a' ) ;
collectorProcess = spawn ( otelcolPath , [ '--config' , OTEL _CONFIG _FILE ] , {
stdio : [ 'ignore' , collectorLogFd , collectorLogFd ] ,
env : { ... process . env } ,
} ) ;
console . log (
` ⏳ Waiting for OTEL collector to start (PID: ${ collectorProcess . pid } )... ` ,
) ;
try {
await waitForPort ( 4317 ) ;
console . log ( ` ✅ OTEL collector started successfully on port 4317. ` ) ;
} catch ( err ) {
console . error ( ` 🛑 Error: OTEL collector failed to start on port 4317. ` ) ;
console . error ( err . message ) ;
if ( collectorProcess && collectorProcess . pid ) {
process . kill ( collectorProcess . pid , 'SIGKILL' ) ;
}
if ( fileExists ( OTEL _LOG _FILE ) ) {
console . error ( '📄 OTEL Collector Log Output:' ) ;
console . error ( fs . readFileSync ( OTEL _LOG _FILE , 'utf-8' ) ) ;
}
process . exit ( 1 ) ;
}
collectorProcess . on ( 'error' , ( err ) => {
console . error ( ` ${ collectorProcess . spawnargs [ 0 ] } process error: ` , err ) ;
process . exit ( 1 ) ;
} ) ;
console . log ( ` \n ✨ Local OTEL collector for GCP is running. ` ) ;
2025-06-14 07:49:21 -07:00
console . log (
'\n🚀 To send telemetry, run the Gemini CLI in a separate terminal window.' ,
) ;
2025-06-13 20:28:18 -07:00
console . log ( ` \n 📄 Collector logs are being written to: ${ OTEL _LOG _FILE } ` ) ;
console . log ( ` \n 📊 View your telemetry data in Google Cloud Console: ` ) ;
console . log (
` - Traces: https://console.cloud.google.com/traces/list?project= ${ projectId } ` ,
) ;
console . log (
` - Metrics: https://console.cloud.google.com/monitoring/metrics-explorer?project= ${ projectId } ` ,
) ;
console . log (
` - Logs: https://console.cloud.google.com/logs/query;query=logName%3D%22projects%2F ${ projectId } %2Flogs%2Fgemini_cli%22?project= ${ projectId } ` ,
) ;
console . log ( ` \n Press Ctrl+C to exit. ` ) ;
}
main ( ) ;