Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions backend/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,53 @@ bootRun {
}
}

// Copy OTEL agent JAR to a predictable location for manual IntelliJ run configuration.
// Not used by bootRun or bootRunWithObservability (they resolve the agent from Gradle's dependency cache).
// Usage: ./gradlew server-app:copyOtelAgent → build/otel/opentelemetry-javaagent.jar
// Then add -javaagent:backend/app/build/otel/opentelemetry-javaagent.jar to your IntelliJ VM options.
// See: docs/observability/
tasks.register('copyOtelAgent', Copy) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see this actually used anywhere - it should imho either be called as part of something, mentioned in the docs, or removed

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's used by the IntelliJ IDEA runner in the other repo, though I'm not sure myself if it's required.

from configurations.otelAgent
into "${buildDir}/otel"
rename { 'opentelemetry-javaagent.jar' }
}
Comment on lines +301 to +310
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify Gradle project structure and task path
echo "=== Checking for settings.gradle files ==="
fd -t f 'settings\.gradle' --max-depth 3

echo -e "\n=== Checking settings.gradle content ==="
find . -maxdepth 3 -name 'settings.gradle*' -exec cat {} \;

echo -e "\n=== Checking backend directory structure ==="
ls -la backend/ 2>/dev/null | head -20

echo -e "\n=== Checking if server-app directory exists ==="
find . -maxdepth 3 -type d -name '*server*app*' -o -type d -name 'server-app'

echo -e "\n=== Checking for other Gradle task invocations ==="
rg 'gradlew.*copyOtelAgent|gradlew.*bootRun' --max-count 10

Repository: tolgee/tolgee-platform

Length of output: 8912


🏁 Script executed:

#!/bin/bash
# Verify buildDir usage and deprecation
echo "=== Checking Gradle version and buildDir patterns ==="
grep -r "gradle\|buildDir\|layout.buildDirectory" gradle.properties settings.gradle 2>/dev/null | head -20

echo -e "\n=== Checking buildDir usage in build.gradle files ==="
rg 'buildDir|layout\.buildDirectory' backend/ --type gradle --max-count 15

echo -e "\n=== Checking environment variable naming in bootRun tasks ==="
rg 'environment.*SPRING|spring\.' backend/app/build.gradle -A 2 -B 2

Repository: tolgee/tolgee-platform

Length of output: 2923


🏁 Script executed:

#!/bin/bash
# Verify the correct module naming in Gradle
echo "=== Checking gradle wrapper and build files ==="
find . -maxdepth 2 -name 'gradle.properties' -o -name 'build.gradle' | head -10 | xargs head -5

echo -e "\n=== Checking if 'app' module exists in backend ==="
test -f backend/app/build.gradle && echo "backend/app/build.gradle EXISTS" || echo "NOT FOUND"

echo -e "\n=== Checking gradle tasks available ==="
grep -E "tasks\.(register|create)" backend/app/build.gradle | grep -E 'copyOtel|bootRun' | head -5

Repository: tolgee/tolgee-platform

Length of output: 561


Minor: Deprecated buildDir usage.

buildDir is deprecated in Gradle 7.1+ in favor of layout.buildDirectory. The comment and task path ./gradlew server-app:copyOtelAgent are correct as-is—the backend/app directory is mapped to the :server-app project in settings.gradle.

Suggested fix
 tasks.register('copyOtelAgent', Copy) {
     from configurations.otelAgent
-    into "${buildDir}/otel"
+    into layout.buildDirectory.dir("otel")
     rename { 'opentelemetry-javaagent.jar' }
 }
🤖 Prompt for AI Agents
In `@backend/app/build.gradle` around lines 301 - 310, The task 'copyOtelAgent'
uses the deprecated buildDir; change the destination to use the project layout
API instead—replace the into "${buildDir}/otel" usage with the
layout.buildDirectory.dir("otel") (or an equivalent DirectoryProvider) so the
Copy task receives a modern Directory/Provider; keep the rest of the task (from
configurations.otelAgent, rename, and the task name copyOtelAgent) unchanged.


// Run with local observability stack (docker-compose.local-observability-stack-dev.yaml)
// Drop-in replacement for bootRun: supports --args and all other BootRun options.
// See: docs/observability/
tasks.register('bootRunWithObservability', org.springframework.boot.gradle.tasks.run.BootRun) {
group = 'application'
description = 'Runs Tolgee with OpenTelemetry tracing, using the local Docker observability stack'

// Inherit classpath and main class from bootRun (configured by Spring Boot plugin)
classpath = bootRun.classpath
mainClass.set(bootRun.mainClass)

// OTEL configuration
environment 'OTEL_JAVAAGENT_ENABLED', 'true'
environment 'OTEL_SERVICE_NAME', 'tolgee-platform'
environment 'OTEL_EXPORTER_OTLP_ENDPOINT', 'http://localhost:4318'
environment 'OTEL_RESOURCE_ATTRIBUTES', 'deployment.environment=local-dev'
environment 'OTEL_LOGS_EXPORTER', 'none'
environment 'OTEL_METRICS_EXPORTER', 'none'
environment 'OTEL_INSTRUMENTATION_LOGBACK_MDC_ENABLED', 'true'
environment 'OTEL_SEMCONV_STABILITY_OPT_IN', 'http'

// Database configuration (use Docker stack's PostgreSQL/Redis)
environment 'spring.datasource.url', 'jdbc:postgresql://localhost:5432/tolgee'
environment 'spring.datasource.username', 'tolgee'
environment 'spring.datasource.password', 'tolgee'
environment 'spring.redis.host', 'localhost'
environment 'spring.redis.port', '6379'
environment 'spring.redis.password', 'tolgee'
environment 'tolgee.postgres-autostart.enabled', 'false'

doFirst {
// Resolve agent JAR at execution time (not configuration time)
jvmArgs "-javaagent:${configurations.otelAgent.singleFile.absolutePath}"
}
}

// ===== Apply custom tasks, if exist, from gitignored .local directory =====
def customTasksFile = rootProject.file('.local/gradle/backend-app.gradle')
if (customTasksFile.exists()) {
Expand Down
141 changes: 141 additions & 0 deletions docker/docker-compose.local-observability-stack-dev.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# Local Observability Stack for IntelliJ development.
# Run Tolgee in IntelliJ with OTEL tracing while observability services run in Docker.
# See: docs/observability/
name: tolgee-local-observability-stack-dev

services:
# Local Observability Stack (for development only)
tempo:
image: grafana/tempo:2.6.1
command: ["-config.file=/etc/tempo.yaml"]
volumes:
- ./local-observability-stack/tempo-config.yaml:/etc/tempo.yaml:ro
- tempo-data:/var/tempo
ports:
- "3200:3200"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3200/ready"]
interval: 10s
timeout: 5s
retries: 5

otel-collector:
image: otel/opentelemetry-collector-contrib:0.96.0
command: ["--config=/etc/otel-collector-config.yaml"]
volumes:
- ./local-observability-stack/otel-collector-config.yaml:/etc/otel-collector-config.yaml:ro
ports:
- "4317:4317"
- "4318:4318"
- "13133:13133"
depends_on:
tempo:
condition: service_healthy
# Note: otel-collector image is scratch-based without shell utilities.
# Health check is disabled, but service exposes health_check extension on :13133

loki:
image: grafana/loki:2.9.1
command: ["-config.file=/etc/loki/local-config.yaml"]
volumes:
- ./local-observability-stack/loki-config.yaml:/etc/loki/local-config.yaml:ro
- loki-data:/loki
ports:
- "3100:3100"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3100/ready"]
interval: 10s
timeout: 5s
retries: 5

promtail:
image: grafana/promtail:2.9.1
command: ["-config.file=/etc/promtail/config.yaml"]
volumes:
- ./local-observability-stack/promtail-config.yaml:/etc/promtail/config.yaml:ro
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- /var/run/docker.sock:/var/run/docker.sock
depends_on:
loki:
condition: service_healthy

prometheus:
image: prom/prometheus:v2.47.0
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.enable-remote-write-receiver'
volumes:
- ./local-observability-stack/prometheus.yaml:/etc/prometheus/prometheus.yml:ro
- prometheus-data:/prometheus
ports:
- "9090:9090"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:9090/-/ready"]
interval: 10s
timeout: 5s
retries: 5

grafana:
image: grafana/grafana:11.4.0
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=admin
- GF_AUTH_ANONYMOUS_ENABLED=true
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
- GF_AUTH_DISABLE_LOGIN_FORM=false
volumes:
- ./local-observability-stack/grafana/datasources.yaml:/etc/grafana/provisioning/datasources/datasources.yaml:ro
- grafana-data:/var/lib/grafana
ports:
- "3000:3000"
depends_on:
tempo:
condition: service_healthy
loki:
condition: service_healthy
prometheus:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/api/health"]
interval: 10s
timeout: 5s
retries: 5

# Tolgee Dependencies
postgres:
image: postgres:15
environment:
POSTGRES_USER: tolgee
POSTGRES_PASSWORD: tolgee
POSTGRES_DB: tolgee
volumes:
- postgres-data:/var/lib/postgresql/data
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U tolgee"]
interval: 10s
timeout: 5s
retries: 5

redis:
image: redis:7-alpine
command: ["redis-server", "--requirepass", "tolgee"]
volumes:
- redis-data:/data
ports:
- "6379:6379"
healthcheck:
test: ["CMD", "redis-cli", "-a", "tolgee", "ping"]
interval: 10s
timeout: 5s
retries: 5

volumes:
tempo-data:
loki-data:
grafana-data:
prometheus-data:
postgres-data:
redis-data:
76 changes: 65 additions & 11 deletions docs/local-observability-stack/1_Setting_Up_the_Tracing_Stack.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,31 @@

---

## 1.1 Build and Start the Stack
## Choose Your Setup

The tracing stack includes Tolgee with OpenTelemetry already configured. First, build the Docker image:
There are two ways to run Tolgee with the observability stack:

| | Option A: Full Docker Stack | Option B: Local Tolgee + Docker Observability |
|-------------------|--------------------------------------------------|----------------------------------------------------------|
| **Best for** | Quick setup, testing production-like environment | Active development, debugging with IDE, faster iteration |
| **Tolgee runs** | In Docker | Locally via Gradle |
| **Observability** | In Docker | In Docker |

Pick the option that fits your workflow and follow the corresponding section below.

---

## 1.1 Option A: Full Docker Stack

This runs everything in Docker, including Tolgee with OpenTelemetry already configured.

**Build the Docker image:**

```bash
./gradlew docker
```

Then start everything from the `docker` directory:
**Start the stack** from the `docker` directory:

```bash
cd docker && docker compose -f docker-compose.local-observability-stack.yaml up -d
Expand All @@ -30,15 +46,52 @@ This starts:
- **OpenTelemetry Collector** - receives traces and forwards them
- **PostgreSQL** and **Redis** - Tolgee dependencies

Verify all services are healthy:
**Verify all services are healthy:**

```bash
docker compose -f docker-compose.local-observability-stack.yaml ps
```

All services should show `Up` or `healthy`.

## 1.2 Access Grafana
---

## 1.2 Option B: Local Tolgee with Docker Observability

This runs Tolgee locally via Gradle while observability services and dependencies run in Docker. Useful for development when you want to debug Tolgee in your IDE.

**Start the observability stack** from the `docker` directory:

```bash
cd docker && docker compose -f docker-compose.local-observability-stack-dev.yaml up -d
```

This starts:
- **Grafana** (port 3000) - UI for viewing traces and logs
- **Tempo** - stores and queries traces
- **Loki** - stores and queries logs
- **Promtail** - ships logs from containers to Loki
- **Prometheus** - stores metrics generated from traces
- **OpenTelemetry Collector** - receives traces and forwards them
- **PostgreSQL** and **Redis** - Tolgee dependencies

**Run Tolgee locally with tracing enabled:**

```bash
./gradlew server-app:bootRunWithObservability --args='--spring.profiles.active=dev'
```

**Verify all services are healthy:**

```bash
docker compose -f docker-compose.local-observability-stack-dev.yaml ps
```

All services should show `Up` or `healthy`.

---

## 1.3 Access Grafana

Open http://localhost:3000 in your browser.

Expand All @@ -60,9 +113,9 @@ No login required - anonymous access is enabled for local development.

If you see "Datasource not found", the infrastructure may still be starting. Wait 30 seconds and refresh.

## 1.3 Verify Traces Are Generated
## 1.4 Verify Traces Are Generated

Tolgee is already running with tracing enabled as part of the Docker Compose stack we started earlier. Let's verify traces are being collected.
Tolgee should now be running with tracing enabled (either in Docker or locally, depending on your chosen option). Let's verify traces are being collected.

1. Make a request to Tolgee, e.g.:
```bash
Expand All @@ -78,10 +131,11 @@ Tolgee is already running with tracing enabled as part of the Docker Compose sta

![Trace results showing Tolgee requests](images/1-4-grafana-trace-results.png)

If no traces appear:
- Check that the Tolgee container is healthy: `docker compose -f docker-compose.local-observability-stack.yaml ps`
- Check Tolgee logs for OpenTelemetry messages: `docker compose -f docker-compose.local-observability-stack.yaml logs app`
- Verify the OTel Collector is running: `docker compose -f docker-compose.local-observability-stack.yaml logs otel-collector`
If no traces appear, check the following (use the compose file matching your chosen option):

- Check that services are healthy: `docker compose -f <your-compose-file>.yaml ps`
- Check Tolgee logs for OpenTelemetry messages (Option A only): `docker compose -f docker-compose.local-observability-stack.yaml logs app`
- Verify the OTel Collector is running: `docker compose -f <your-compose-file>.yaml logs otel-collector`
- Wait 10-15 seconds for traces to be ingested

---
Expand Down
8 changes: 4 additions & 4 deletions docs/observability/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ Three capabilities for debugging and monitoring:

### Two Ways to Run with Tracing

| Method | Use Case | How |
|--------|----------|-----|
| `./gradlew bootRun` | Local development, quick iteration | `OTEL_JAVAAGENT_ENABLED=true ./gradlew bootRun` |
| Docker | Testing the full stack, CI, production-like | Set `OTEL_JAVAAGENT_ENABLED=true` in docker-compose environment (already done in `docker-compose.local-observability-stack.yaml`) |
| Method | Use Case | How |
|--------------------------------|---------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `./gradlew server-app:bootRun` | Local development, quick iteration | `docker compose -f docker-compose.local-observability-stack-dev.yaml up -d && ./gradlew server-app:bootRunWithObservability --args='--spring.profiles.active=dev'` |
| Docker (full stack) | Testing the full stack, CI, production-like | `docker compose -f docker-compose.local-observability-stack.yaml up -d` |
Comment on lines +34 to +37
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n docs/observability/README.md | head -50

Repository: tolgee/tolgee-platform

Length of output: 3363


Align the "Method" label with the command shown.

The Method column shows ./gradlew server-app:bootRun, but the command uses ./gradlew server-app:bootRunWithObservability. Update the Method label to match.

✏️ Suggested edit
-| `./gradlew server-app:bootRun` | Local development, quick iteration          | `docker compose -f docker-compose.local-observability-stack-dev.yaml up -d && ./gradlew server-app:bootRunWithObservability --args='--spring.profiles.active=dev'` |
+| `./gradlew server-app:bootRunWithObservability` | Local development, quick iteration          | `docker compose -f docker-compose.local-observability-stack-dev.yaml up -d && ./gradlew server-app:bootRunWithObservability --args='--spring.profiles.active=dev'` |
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
| Method | Use Case | How |
|--------------------------------|---------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `./gradlew server-app:bootRun` | Local development, quick iteration | `docker compose -f docker-compose.local-observability-stack-dev.yaml up -d && ./gradlew server-app:bootRunWithObservability --args='--spring.profiles.active=dev'` |
| Docker (full stack) | Testing the full stack, CI, production-like | `docker compose -f docker-compose.local-observability-stack.yaml up -d` |
| Method | Use Case | How |
|-----------------------------------------------------|---------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `./gradlew server-app:bootRunWithObservability` | Local development, quick iteration | `docker compose -f docker-compose.local-observability-stack-dev.yaml up -d && ./gradlew server-app:bootRunWithObservability --args='--spring.profiles.active=dev'` |
| Docker (full stack) | Testing the full stack, CI, production-like | `docker compose -f docker-compose.local-observability-stack.yaml up -d` |
🤖 Prompt for AI Agents
In `@docs/observability/README.md` around lines 34 - 37, Update the "Method"
column entry that currently reads `./gradlew server-app:bootRun` to match the
actual command used (`./gradlew server-app:bootRunWithObservability`) in the
table row where the How cell contains `./gradlew
server-app:bootRunWithObservability --args='--spring.profiles.active=dev'`, so
the Method label and the command string are consistent.


Both methods use the same agent version from `gradle.properties`.

Expand Down
Loading