Skip to content

Commit 916656a

Browse files
authored
Merge pull request #5 from doneyli/feature/langfuse-cloud-support
Add Langfuse Cloud support
2 parents d2a51e4 + 1c5abf0 commit 916656a

4 files changed

Lines changed: 429 additions & 160 deletions

File tree

README.md

Lines changed: 75 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,50 @@
11
# Claude Code + Langfuse: Session Observability Template
22

3-
Self-hosted Langfuse for capturing every Claude Code conversation — prompts, responses, tool calls, and session grouping.
3+
Capture every Claude Code conversation — prompts, responses, tool calls, and session grouping — using Langfuse Cloud or a self-hosted instance.
44

5-
This template provides a complete, production-ready setup for observing your Claude Code sessions using Langfuse. Everything runs locally in Docker, with automatic session tracking and incremental state management.
5+
This template provides a complete, production-ready setup for observing your Claude Code sessions using Langfuse. Choose between **Langfuse Cloud** (zero infrastructure) or **self-hosted** (everything runs locally in Docker), with automatic session tracking and incremental state management.
66

77
**Read the full story:** [I Built My Own Observability for Claude Code](https://doneyli.substack.com/p/i-built-my-own-observability-for) — why I built this, how it works, and screenshots of the setup in action.
88

9-
## Prerequisites
9+
## Choose Your Setup
1010

11-
- Docker and Docker Compose
12-
- Python 3.11 or higher
13-
- Claude Code CLI (desktop or terminal)
14-
- 4-6GB available RAM
15-
- 2-5GB available disk space
11+
| | Langfuse Cloud | Self-Hosted |
12+
|---|---|---|
13+
| **Infrastructure** | None (fully managed) | Docker on your machine |
14+
| **Prerequisites** | Python 3.11+ | Python 3.11+, Docker, 4-6GB RAM, 2-5GB disk |
15+
| **Setup time** | ~2 minutes | ~5 minutes |
16+
| **Data location** | Langfuse Cloud (EU or US) | Your machine only |
17+
| **Cost** | Free tier available | Free (self-hosted) |
1618

17-
## Quick Start
19+
## Quick Start — Option A: Langfuse Cloud
1820

19-
Follow these steps to get Langfuse observability running in under 5 minutes:
21+
Use this if you have (or want to create) a [Langfuse Cloud](https://cloud.langfuse.com) account. No Docker required.
22+
23+
**Prerequisites:** Python 3.11+, Claude Code CLI, a Langfuse Cloud account with API keys.
24+
25+
1. **Clone the repository**
26+
```bash
27+
git clone https://github.com/doneyli/claude-code-langfuse-template.git
28+
cd claude-code-langfuse-template
29+
```
30+
31+
2. **Install the hook (cloud mode)**
32+
```bash
33+
./scripts/install-hook.sh --cloud
34+
```
35+
You'll be prompted for your public key, secret key, and region (EU/US/custom).
36+
37+
3. **Verify the setup**
38+
```bash
39+
./scripts/validate-setup.sh --cloud --post
40+
```
41+
Then start a Claude Code conversation — traces will appear in your Langfuse Cloud dashboard.
42+
43+
## Quick Start — Option B: Self-Hosted
44+
45+
Use this to run everything locally in Docker. No external accounts needed.
46+
47+
**Prerequisites:** Docker and Docker Compose, Python 3.11+, Claude Code CLI, 4-6GB RAM, 2-5GB disk.
2048

2149
1. **Clone the repository**
2250
```bash
@@ -188,10 +216,10 @@ The Langfuse hook runs as a Claude Code **Stop hook** — it executes after each
188216
189217
│ HTTP POST
190218
191-
┌──────────────────┐
192-
│ Langfuse API │
193-
│ (localhost:3050)
194-
└──────┬───────────┘
219+
┌──────────────────────────────
220+
│ Langfuse API
221+
│ (localhost:3050 or Cloud)
222+
└──────┬───────────────────────
195223
196224
197225
┌──────────────────────────┐
@@ -228,6 +256,10 @@ To opt out for a specific project:
228256

229257
See `settings-examples/project-opt-out.json` for a complete example.
230258

259+
### Cloud Settings Example
260+
261+
If you're using Langfuse Cloud and want to see or tweak the generated configuration, see `settings-examples/cloud-settings.json`.
262+
231263
### Environment Variables
232264

233265
All configuration is managed through environment variables in `~/.claude/settings.json`:
@@ -389,6 +421,25 @@ lsof -i :3050
389421
# Either stop that service, or change the Langfuse port in docker-compose.yml
390422
```
391423

424+
### Cloud: API key rejected
425+
426+
**Symptom:** Traces don't appear in Langfuse Cloud, hook logs show 401/403 errors
427+
428+
**Check:**
429+
1. Keys must start with `pk-lf-` (public) and `sk-lf-` (secret)
430+
2. Verify keys match the correct project in your Langfuse Cloud dashboard
431+
3. Ensure `LANGFUSE_HOST` matches your region (`https://cloud.langfuse.com` for EU, `https://us.cloud.langfuse.com` for US)
432+
4. Re-run: `./scripts/install-hook.sh --cloud`
433+
434+
### Cloud: Cannot reach Langfuse Cloud
435+
436+
**Symptom:** `validate-setup.sh --cloud --post` warns that the API is not reachable
437+
438+
**Check:**
439+
1. Verify internet connectivity: `curl https://cloud.langfuse.com/api/public/health`
440+
2. Check for proxy/firewall blocking outbound HTTPS
441+
3. If using a custom URL, verify it's correct
442+
392443
### Traces not appearing
393444

394445
**Symptom:** Langfuse UI shows no traces after conversations
@@ -397,8 +448,9 @@ lsof -i :3050
397448
1. Is `TRACE_TO_LANGFUSE=true` in `~/.claude/settings.json`?
398449
2. Are the API keys correct?
399450
3. Check hook logs: `tail -f ~/.claude/state/langfuse_hook.log`
400-
4. Verify Docker services are running: `docker compose ps`
401-
5. Test Langfuse API: `curl http://localhost:3050/api/public/health`
451+
4. **Self-hosted:** Verify Docker services are running: `docker compose ps`
452+
5. **Self-hosted:** Test API: `curl http://localhost:3050/api/public/health`
453+
6. **Cloud:** Test API: `curl https://cloud.langfuse.com/api/public/health`
402454

403455
### Hook runs slowly
404456

@@ -459,10 +511,16 @@ docker compose up -d
459511

460512
### Security
461513

514+
**Self-hosted:**
462515
- All services run on `localhost` (not exposed to network)
463516
- Credentials are generated randomly on first setup
464517
- `.env` file is git-ignored (never commit credentials)
465-
- No telemetry is sent to external services (Langfuse self-hosted)
518+
- No telemetry is sent to external services
519+
520+
**Cloud:**
521+
- Traces are sent to Langfuse Cloud (EU or US region)
522+
- API keys are stored in `~/.claude/settings.json` (not committed to git)
523+
- Review [Langfuse's security & privacy documentation](https://langfuse.com/security) for data handling details
466524

467525
## Customization Ideas
468526

scripts/install-hook.sh

Lines changed: 118 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,107 @@ GREEN='\033[0;32m'
77
YELLOW='\033[1;33m'
88
NC='\033[0m' # No Color
99

10+
# Parse flags
11+
CLOUD_MODE=false
12+
if [[ "${1:-}" == "--cloud" ]]; then
13+
CLOUD_MODE=true
14+
fi
15+
1016
echo "====================================="
1117
echo "Claude Code Langfuse Hook Installer"
18+
if [ "$CLOUD_MODE" = true ]; then
19+
echo " (Langfuse Cloud mode)"
20+
fi
1221
echo "====================================="
1322
echo ""
1423

15-
# Check if .env exists
16-
if [ ! -f .env ]; then
17-
echo -e "${RED}Error: .env file not found${NC}"
18-
echo "Please run ./scripts/generate-env.sh first"
19-
exit 1
20-
fi
24+
if [ "$CLOUD_MODE" = true ]; then
25+
# --- Cloud mode: prompt for credentials interactively ---
26+
echo "Setting up Langfuse Cloud integration."
27+
echo ""
2128

22-
# Extract credentials from .env (safer than source - avoids code injection)
23-
get_env_value() {
24-
grep "^$1=" .env 2>/dev/null | cut -d= -f2- | head -1
25-
}
29+
# Prompt for public key
30+
read -rp "Enter your Langfuse public key (pk-lf-...): " LANGFUSE_PUBLIC_KEY
31+
if [[ -z "$LANGFUSE_PUBLIC_KEY" ]]; then
32+
echo -e "${RED}Error: Public key cannot be empty${NC}"
33+
exit 1
34+
fi
35+
if [[ ! "$LANGFUSE_PUBLIC_KEY" =~ ^pk-lf- ]]; then
36+
echo -e "${RED}Error: Public key must start with 'pk-lf-'${NC}"
37+
exit 1
38+
fi
2639

27-
LANGFUSE_INIT_PROJECT_PUBLIC_KEY=$(get_env_value "LANGFUSE_INIT_PROJECT_PUBLIC_KEY")
28-
LANGFUSE_INIT_PROJECT_SECRET_KEY=$(get_env_value "LANGFUSE_INIT_PROJECT_SECRET_KEY")
40+
# Prompt for secret key
41+
read -rp "Enter your Langfuse secret key (sk-lf-...): " LANGFUSE_SECRET_KEY
42+
if [[ -z "$LANGFUSE_SECRET_KEY" ]]; then
43+
echo -e "${RED}Error: Secret key cannot be empty${NC}"
44+
exit 1
45+
fi
46+
if [[ ! "$LANGFUSE_SECRET_KEY" =~ ^sk-lf- ]]; then
47+
echo -e "${RED}Error: Secret key must start with 'sk-lf-'${NC}"
48+
exit 1
49+
fi
2950

30-
if [ -z "$LANGFUSE_INIT_PROJECT_PUBLIC_KEY" ] || [ -z "$LANGFUSE_INIT_PROJECT_SECRET_KEY" ]; then
31-
echo -e "${RED}Error: Could not read API keys from .env${NC}"
32-
echo "Ensure LANGFUSE_INIT_PROJECT_PUBLIC_KEY and LANGFUSE_INIT_PROJECT_SECRET_KEY are set"
33-
exit 1
51+
# Prompt for region
52+
echo ""
53+
echo "Choose your Langfuse region:"
54+
echo " 1) EU (cloud.langfuse.com)"
55+
echo " 2) US (us.cloud.langfuse.com)"
56+
echo " 3) Custom URL"
57+
read -rp "Selection [1]: " REGION_CHOICE
58+
REGION_CHOICE="${REGION_CHOICE:-1}"
59+
60+
case "$REGION_CHOICE" in
61+
1)
62+
LANGFUSE_HOST="https://cloud.langfuse.com"
63+
;;
64+
2)
65+
LANGFUSE_HOST="https://us.cloud.langfuse.com"
66+
;;
67+
3)
68+
read -rp "Enter your Langfuse URL (e.g. https://my-langfuse.example.com): " LANGFUSE_HOST
69+
if [[ -z "$LANGFUSE_HOST" ]]; then
70+
echo -e "${RED}Error: URL cannot be empty${NC}"
71+
exit 1
72+
fi
73+
;;
74+
*)
75+
echo -e "${RED}Error: Invalid selection${NC}"
76+
exit 1
77+
;;
78+
esac
79+
80+
echo ""
81+
echo -e "${GREEN}✓ Cloud credentials configured${NC}"
82+
echo " Host: $LANGFUSE_HOST"
83+
echo " Public Key: $LANGFUSE_PUBLIC_KEY"
84+
echo ""
85+
86+
else
87+
# --- Self-hosted mode: read from .env ---
88+
# Check if .env exists
89+
if [ ! -f .env ]; then
90+
echo -e "${RED}Error: .env file not found${NC}"
91+
echo "Please run ./scripts/generate-env.sh first"
92+
echo ""
93+
echo "Or use --cloud for Langfuse Cloud: ./scripts/install-hook.sh --cloud"
94+
exit 1
95+
fi
96+
97+
# Extract credentials from .env (safer than source - avoids code injection)
98+
get_env_value() {
99+
grep "^$1=" .env 2>/dev/null | cut -d= -f2- | head -1
100+
}
101+
102+
LANGFUSE_PUBLIC_KEY=$(get_env_value "LANGFUSE_INIT_PROJECT_PUBLIC_KEY")
103+
LANGFUSE_SECRET_KEY=$(get_env_value "LANGFUSE_INIT_PROJECT_SECRET_KEY")
104+
LANGFUSE_HOST="http://localhost:3050"
105+
106+
if [ -z "$LANGFUSE_PUBLIC_KEY" ] || [ -z "$LANGFUSE_SECRET_KEY" ]; then
107+
echo -e "${RED}Error: Could not read API keys from .env${NC}"
108+
echo "Ensure LANGFUSE_INIT_PROJECT_PUBLIC_KEY and LANGFUSE_INIT_PROJECT_SECRET_KEY are set"
109+
exit 1
110+
fi
34111
fi
35112

36113
# Find Python 3.11+
@@ -128,9 +205,9 @@ if "hooks" not in settings:
128205
129206
# Add environment variables
130207
settings["env"]["TRACE_TO_LANGFUSE"] = "true"
131-
settings["env"]["LANGFUSE_PUBLIC_KEY"] = "$LANGFUSE_INIT_PROJECT_PUBLIC_KEY"
132-
settings["env"]["LANGFUSE_SECRET_KEY"] = "$LANGFUSE_INIT_PROJECT_SECRET_KEY"
133-
settings["env"]["LANGFUSE_HOST"] = "http://localhost:3050"
208+
settings["env"]["LANGFUSE_PUBLIC_KEY"] = "$LANGFUSE_PUBLIC_KEY"
209+
settings["env"]["LANGFUSE_SECRET_KEY"] = "$LANGFUSE_SECRET_KEY"
210+
settings["env"]["LANGFUSE_HOST"] = "$LANGFUSE_HOST"
134211
135212
# Add Stop hook if not already present
136213
if "Stop" not in settings["hooks"]:
@@ -179,15 +256,29 @@ echo ""
179256
echo "Configuration:"
180257
echo " Hook: $HOOK_DEST"
181258
echo " Settings: $SETTINGS_FILE"
182-
echo " Host: http://localhost:3050"
183-
echo " Public Key: $LANGFUSE_INIT_PROJECT_PUBLIC_KEY"
259+
echo " Host: $LANGFUSE_HOST"
260+
echo " Public Key: $LANGFUSE_PUBLIC_KEY"
184261
echo ""
185-
echo "Verification steps:"
186-
echo " 1. Ensure Docker is running"
187-
echo " 2. Start Langfuse: docker compose up -d"
188-
echo " 3. Wait 30-60 seconds for services to initialize"
189-
echo " 4. Start a Claude Code conversation"
190-
echo " 5. Check traces at http://localhost:3050"
262+
263+
if [ "$CLOUD_MODE" = true ]; then
264+
echo "Verification steps:"
265+
echo " 1. Start a Claude Code conversation"
266+
echo " 2. Check traces at $LANGFUSE_HOST"
267+
echo ""
268+
echo "Optional: Run validation"
269+
echo " ./scripts/validate-setup.sh --cloud --post"
270+
else
271+
echo "Verification steps:"
272+
echo " 1. Ensure Docker is running"
273+
echo " 2. Start Langfuse: docker compose up -d"
274+
echo " 3. Wait 30-60 seconds for services to initialize"
275+
echo " 4. Start a Claude Code conversation"
276+
echo " 5. Check traces at http://localhost:3050"
277+
echo ""
278+
echo "Optional: Run validation"
279+
echo " ./scripts/validate-setup.sh --post"
280+
fi
281+
191282
echo ""
192283
echo "Debug commands:"
193284
echo " View hook logs: tail -f ~/.claude/state/langfuse_hook.log"

0 commit comments

Comments
 (0)