Skip to content

Commit 3879829

Browse files
feat(gemini-cli): add Google Gemini CLI OpenTelemetry example
Send Gemini CLI telemetry — traces, metrics, and logs — to Last9 via OpenTelemetry. Two paths documented: - Direct: GEMINI_TELEMETRY_* env vars set endpoint; standard OTEL_EXPORTER_OTLP_HEADERS provides auth (picked up by underlying OpenTelemetry JS SDK even though gemini-cli does not pass headers explicitly to its OTLP exporters). - Local OTel Collector: useful for batching or fan-out. Traces are opt-in via GEMINI_TELEMETRY_TRACES_ENABLED=true. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent d36adf9 commit 3879829

5 files changed

Lines changed: 216 additions & 0 deletions

File tree

gemini-cli/.env.example

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Last9 OTLP credentials — get these from:
2+
# https://app.last9.io/integrations?integration=OpenTelemetry
3+
4+
LAST9_OTLP_ENDPOINT=https://otlp.last9.io
5+
LAST9_OTLP_AUTH=Basic <your_credentials>

gemini-cli/.gitignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Environment/secrets
2+
.env
3+
.env.local
4+
.env.*.local
5+
6+
# OS
7+
.DS_Store
8+
Thumbs.db
9+
10+
# Logs
11+
*.log

gemini-cli/README.md

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# Gemini CLI — OpenTelemetry to Last9
2+
3+
Send Google's [Gemini CLI](https://github.com/google-gemini/gemini-cli) telemetry — traces, metrics, and logs — to Last9 via OpenTelemetry.
4+
5+
Gemini CLI emits all three OpenTelemetry signal types. Keep all three on — combined volume is low and traces add per-call model latency / token attribution that metrics alone cannot.
6+
7+
- **Traces** — spans for tool calls, API requests, agent runs (set `GEMINI_TELEMETRY_TRACES_ENABLED=true`)
8+
- **Metrics** — session counts, token usage, latency, file ops, agent durations
9+
- **Logs** — structured events for prompts, API requests/responses, slash commands, file operations
10+
11+
## Prerequisites
12+
13+
- A Last9 account ([app.last9.io](https://app.last9.io))
14+
- OTLP credentials from **Integrations → OpenTelemetry** in the Last9 dashboard
15+
- Gemini CLI installed (`npm install -g @google/gemini-cli`)
16+
17+
## Option 1 — Direct Export to Last9 (no Collector)
18+
19+
Gemini CLI's OTLP exporters take `url` only, but the underlying OpenTelemetry JS SDK reads standard `OTEL_EXPORTER_OTLP_HEADERS` from env for authentication — so direct export works.
20+
21+
```bash
22+
# Gemini CLI telemetry switches
23+
export GEMINI_TELEMETRY_ENABLED=true
24+
export GEMINI_TELEMETRY_TARGET=local
25+
export GEMINI_TELEMETRY_OTLP_ENDPOINT="https://<your-last9-otlp-endpoint>"
26+
export GEMINI_TELEMETRY_OTLP_PROTOCOL=http
27+
export GEMINI_TELEMETRY_TRACES_ENABLED=true
28+
29+
# Standard OTel env — picked up by SDK for auth headers
30+
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic <your-last9-auth-token>"
31+
```
32+
33+
Reload and run:
34+
35+
```bash
36+
source ~/.zshrc
37+
gemini -p "explain this repo"
38+
```
39+
40+
## Option 2 — Local OpenTelemetry Collector
41+
42+
Useful for batching, retries, or fan-out to multiple backends.
43+
44+
1. Copy `.env.example` to `.env` and fill in your Last9 credentials:
45+
46+
```bash
47+
cp .env.example .env
48+
```
49+
50+
2. Start the collector:
51+
52+
```bash
53+
docker compose up -d
54+
```
55+
56+
3. Point Gemini CLI at the local collector:
57+
58+
```bash
59+
export GEMINI_TELEMETRY_ENABLED=true
60+
export GEMINI_TELEMETRY_TARGET=local
61+
export GEMINI_TELEMETRY_OTLP_ENDPOINT=http://localhost:4317
62+
export GEMINI_TELEMETRY_OTLP_PROTOCOL=grpc
63+
export GEMINI_TELEMETRY_TRACES_ENABLED=true
64+
unset OTEL_EXPORTER_OTLP_HEADERS # collector handles auth
65+
```
66+
67+
4. Run Gemini CLI:
68+
69+
```bash
70+
gemini
71+
```
72+
73+
## Configuration Reference
74+
75+
### Gemini CLI-specific
76+
77+
| Variable | Default | Purpose |
78+
|---|---|---|
79+
| `GEMINI_TELEMETRY_ENABLED` | `false` | Master toggle |
80+
| `GEMINI_TELEMETRY_TARGET` | `local` | `local` (OTLP) or `gcp` (Google Cloud) |
81+
| `GEMINI_TELEMETRY_OTLP_ENDPOINT` | `http://localhost:4317` | OTLP endpoint (base URL — `/v1/traces` etc. appended) |
82+
| `GEMINI_TELEMETRY_OTLP_PROTOCOL` | `grpc` | `grpc` or `http` |
83+
| `GEMINI_TELEMETRY_TRACES_ENABLED` | `false` | Set `true` to export spans (recommended) |
84+
| `GEMINI_TELEMETRY_LOG_PROMPTS` | `true` | Include prompt text in logs |
85+
| `GEMINI_TELEMETRY_OUTFILE` || Write to local file instead of OTLP |
86+
87+
### Standard OTel env (used for auth headers)
88+
89+
| Variable | Purpose |
90+
|---|---|
91+
| `OTEL_EXPORTER_OTLP_HEADERS` | `Authorization=Basic <token>` |
92+
| `OTEL_EXPORTER_OTLP_{TRACES,METRICS,LOGS}_HEADERS` | Per-signal override |
93+
94+
### `.env` variables (Collector path)
95+
96+
| Variable | Purpose |
97+
|---|---|
98+
| `LAST9_OTLP_ENDPOINT` | Last9 OTLP endpoint (e.g. `https://otlp.last9.io`) |
99+
| `LAST9_OTLP_AUTH` | Last9 Basic auth header (`Basic <base64>`) |
100+
101+
## Verification
102+
103+
After running a Gemini CLI session for a minute:
104+
105+
1. **Traces** — filter by `service.name = gemini-cli` in Last9 Traces Explorer. Span name: `llm_call`.
106+
2. **Metrics** — search for `gemini_cli_session_count_total`, `gemini_cli_api_request_count_total`, `gemini_cli_token_usage_total`
107+
3. **Logs** — filter by `service.name = gemini-cli` in Last9 Logs Explorer
108+
109+
<details>
110+
<summary>Notable metrics</summary>
111+
112+
| Metric | Type |
113+
|---|---|
114+
| `gemini_cli.session.count` | counter |
115+
| `gemini_cli.tool.call.count` | counter |
116+
| `gemini_cli.tool.call.latency` | histogram |
117+
| `gemini_cli.api.request.count` | counter |
118+
| `gemini_cli.api.request.latency` | histogram |
119+
| `gemini_cli.token.usage` | counter |
120+
| `gemini_cli.file.operation.count` | counter |
121+
| `gemini_cli.lines.changed` | counter |
122+
| `gemini_cli.agent.run.count` | counter |
123+
| `gemini_cli.agent.duration` | histogram |
124+
| `gemini_cli.startup.duration` | histogram |
125+
| `gemini_cli.memory.usage` | gauge |
126+
| `gemini_cli.cpu.usage` | gauge |
127+
| `gen_ai.client.token.usage` | counter (GenAI semconv) |
128+
| `gen_ai.client.operation.duration` | histogram (GenAI semconv) |
129+
130+
</details>
131+
132+
## Troubleshooting
133+
134+
- **No data in Last9** — confirm `echo $GEMINI_TELEMETRY_ENABLED` returns `true` in the shell that ran `gemini`. Restart shell after editing `~/.zshrc`.
135+
- **Authentication errors** — verify `OTEL_EXPORTER_OTLP_HEADERS` is `Authorization=Basic <token>` (key=value format, not HTTP colon syntax). Trailing whitespace breaks it.
136+
- **Traces missing** — make sure `GEMINI_TELEMETRY_TRACES_ENABLED=true` is set. Gemini CLI defaults to `false`, so spans only flow when you explicitly enable them.
137+
- **gRPC connection refused** — make sure `-p 4317:4317` is exposed (collector path) and you're using `grpc` protocol with `http://localhost:4317` (not `https`).

gemini-cli/docker-compose.yaml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
services:
2+
otel-collector:
3+
image: otel/opentelemetry-collector-contrib:0.144.0
4+
container_name: gemini-cli-otel-collector
5+
command: ["--config=/etc/otel/config.yaml"]
6+
volumes:
7+
- ./otel-collector-config.yaml:/etc/otel/config.yaml:ro
8+
env_file: .env
9+
environment:
10+
- DEPLOYMENT_ENV=${DEPLOYMENT_ENV:-development}
11+
ports:
12+
- "4317:4317" # gRPC receiver
13+
- "4318:4318" # HTTP receiver
14+
- "13133:13133" # Health check
15+
healthcheck:
16+
test: ["CMD", "wget", "--spider", "-q", "http://localhost:13133"]
17+
interval: 10s
18+
timeout: 5s
19+
retries: 3
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
receivers:
2+
# Receive logs, traces, and metrics from Gemini CLI via gRPC and HTTP
3+
otlp:
4+
protocols:
5+
grpc:
6+
endpoint: 0.0.0.0:4317
7+
http:
8+
endpoint: 0.0.0.0:4318
9+
10+
processors:
11+
batch:
12+
timeout: 5s
13+
send_batch_size: 1000
14+
15+
# Add organization-level resource attributes
16+
resource:
17+
attributes:
18+
- key: deployment.environment
19+
value: "${env:DEPLOYMENT_ENV}"
20+
action: upsert
21+
22+
exporters:
23+
otlp/last9:
24+
endpoint: "${env:LAST9_OTLP_ENDPOINT}"
25+
headers:
26+
"Authorization": "${env:LAST9_OTLP_AUTH}"
27+
28+
debug:
29+
verbosity: basic
30+
31+
service:
32+
pipelines:
33+
traces:
34+
receivers: [otlp]
35+
processors: [batch, resource]
36+
exporters: [otlp/last9, debug]
37+
metrics:
38+
receivers: [otlp]
39+
processors: [batch, resource]
40+
exporters: [otlp/last9, debug]
41+
logs:
42+
receivers: [otlp]
43+
processors: [batch, resource]
44+
exporters: [otlp/last9, debug]

0 commit comments

Comments
 (0)