Showing preview only (1,404K chars total). Download the full file or copy to clipboard to get everything.
Repository: grafana/alloy-scenarios
Branch: main
Commit: fb8fd5ed16bb
Files: 430
Total size: 1.3 MB
Directory structure:
gitextract_o8zi_7a5/
├── .coda/
│ ├── coda-start.service
│ ├── coda-start.sh
│ └── packer-install.sh
├── .cursor/
│ ├── docker-example.mdc
│ └── k8s-example.mdc
├── .github/
│ ├── k8s-scenarios.json
│ ├── scenario-list.txt
│ └── workflows/
│ ├── check-image-versions.yml
│ ├── validate-k8s-scenarios.yml
│ └── validate-scenarios.yml
├── .gitignore
├── CLAUDE.md
├── LICENSE
├── README.md
├── app-instrumentation/
│ └── logging/
│ └── popular-logging-frameworks/
│ ├── README.md
│ ├── alloy/
│ │ ├── config.alloy
│ │ └── helper.alloy
│ ├── cpp/
│ │ ├── CMakeLists.txt
│ │ ├── Dockerfile
│ │ └── main.cpp
│ ├── csharp/
│ │ ├── Dockerfile
│ │ ├── LoggingExample.csproj
│ │ └── Program.cs
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ ├── go/
│ │ ├── Dockerfile
│ │ ├── go.mod
│ │ ├── go.sum
│ │ └── main.go
│ ├── java/
│ │ ├── App.java
│ │ ├── Dockerfile
│ │ └── logback.xml
│ ├── javascript/
│ │ ├── Dockerfile
│ │ └── app.js
│ ├── loki-config.yaml
│ ├── php/
│ │ ├── Dockerfile
│ │ └── app.php
│ └── python/
│ ├── Dockerfile
│ └── app.py
├── aws-firehose-logs/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.yml
│ ├── firehose_sender.py
│ └── loki-config.yaml
├── blackbox-probing/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ └── prom-config.yaml
├── cloudwatch-metrics/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.yml
│ ├── prom-config.yaml
│ └── seed-metrics.py
├── coda
├── continuous-profiling/
│ ├── README.md
│ ├── app/
│ │ ├── go.mod
│ │ └── main.go
│ ├── config.alloy
│ ├── docker-compose.coda.yml
│ └── docker-compose.yml
├── docker-monitoring/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.yml
│ ├── grafana/
│ │ └── datasources/
│ │ └── default.yml
│ └── loki-config.yaml
├── elasticsearch-monitoring/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ └── prom-config.yaml
├── faro-frontend-observability/
│ ├── README.md
│ ├── app/
│ │ └── index.html
│ ├── config.alloy
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ └── loki-config.yaml
├── game-of-tracing/
│ ├── AGENTS.md
│ ├── CLAUDE.md
│ ├── README.md
│ ├── SPAN_LINKS.md
│ ├── ai_opponent/
│ │ ├── CLAUDE.md
│ │ ├── Dockerfile
│ │ ├── README.md
│ │ ├── ai_server.py
│ │ ├── requirements.txt
│ │ └── telemetry.py
│ ├── app/
│ │ ├── CLAUDE.md
│ │ ├── Dockerfile
│ │ ├── game_config.py
│ │ ├── location_server.py
│ │ ├── requirements.txt
│ │ ├── run_game.py
│ │ └── telemetry.py
│ ├── config-otel.yaml
│ ├── config.alloy
│ ├── docker-compose-otel.yml
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ ├── grafana/
│ │ ├── dashboards/
│ │ │ ├── War of Kingdoms-1747821967780.json
│ │ │ └── dashboards.yaml
│ │ └── datasources/
│ │ └── defaults.yml
│ ├── loki-config.yaml
│ ├── prom-config.yaml
│ ├── pyroscope-config.yaml
│ ├── tempo-config.yaml
│ └── war_map/
│ ├── CLAUDE.md
│ ├── Dockerfile
│ ├── app.py
│ ├── requirements.txt
│ ├── static/
│ │ └── css/
│ │ └── style.css
│ ├── telemetry.py
│ └── templates/
│ ├── index.html
│ ├── layout.html
│ ├── map.html
│ ├── map_picker.html
│ ├── replay.html
│ └── replay_session.html
├── gelf-log-ingestion/
│ ├── README.md
│ ├── app/
│ │ └── main.py
│ ├── config.alloy
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ └── loki-config.yaml
├── image-versions.env
├── k8s/
│ ├── README.md
│ ├── events/
│ │ ├── README.md
│ │ ├── alloy-config.yaml
│ │ ├── alloy-deployment.yaml
│ │ ├── alloy-rbac.yaml
│ │ ├── grafana-values.yml
│ │ ├── kind.yml
│ │ └── loki-values.yml
│ ├── logs/
│ │ ├── README.md
│ │ ├── grafana-values.yml
│ │ ├── k8s-monitoring-values.yml
│ │ ├── killercoda/
│ │ │ └── loki-values.yml
│ │ ├── kind.yml
│ │ └── loki-values.yml
│ ├── metrics/
│ │ ├── README.md
│ │ ├── grafana-values.yml
│ │ ├── k8s-monitoring-values.yml
│ │ ├── kind.yml
│ │ └── prometheus-values.yml
│ ├── profiling/
│ │ ├── README.md
│ │ ├── grafana-values.yml
│ │ ├── k8s-monitoring-values.yml
│ │ ├── kind.yml
│ │ └── pyroscope-values.yml
│ └── tracing/
│ ├── README.md
│ ├── grafana-values.yml
│ ├── k8s-monitoring-values.yml
│ ├── kind.yml
│ └── tempo-values.yml
├── kafka/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ ├── gen_log.sh
│ └── loki-config.yaml
├── linux/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.yml
│ ├── loki-config.yaml
│ └── prom-config.yaml
├── log-api-gateway/
│ ├── README.md
│ ├── app/
│ │ └── producer.py
│ ├── config.alloy
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ └── loki-config.yaml
├── log-secret-filtering/
│ ├── README.md
│ ├── app/
│ │ └── main.py
│ ├── config.alloy
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ └── loki-config.yaml
├── logs-file/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ ├── loki-config.yaml
│ └── main.py
├── logs-tcp/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ ├── loki-config.yaml
│ └── simulator.py
├── mail-house/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ ├── loki-config.yaml
│ └── main.py
├── memcached-monitoring/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ └── prom-config.yaml
├── mysql-monitoring/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ └── prom-config.yaml
├── nginx-monitoring/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.yml
│ ├── loki-config.yaml
│ ├── nginx.conf
│ └── prom-config.yaml
├── otel-basic-tracing/
│ ├── README.md
│ ├── app/
│ │ ├── Dockerfile
│ │ ├── app.py
│ │ └── requirements.txt
│ ├── config-otel.yaml
│ ├── config.alloy
│ ├── docker-compose-otel.yml
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ ├── prom-config.yaml
│ └── tempo-config.yaml
├── otel-examples/
│ ├── README.md
│ ├── cost-control/
│ │ ├── README.md
│ │ ├── app/
│ │ │ ├── Dockerfile
│ │ │ ├── app.py
│ │ │ └── requirements.txt
│ │ ├── config-otel.yaml
│ │ ├── config.alloy
│ │ ├── docker-compose.coda.yml
│ │ ├── docker-compose.yml
│ │ ├── loki-config.yaml
│ │ └── tempo-config.yaml
│ ├── count-connector/
│ │ ├── README.md
│ │ ├── app/
│ │ │ ├── Dockerfile
│ │ │ ├── app.py
│ │ │ └── requirements.txt
│ │ ├── config-otel.yaml
│ │ ├── config.alloy
│ │ ├── docker-compose.coda.yml
│ │ ├── docker-compose.yml
│ │ ├── loki-config.yaml
│ │ ├── prom-config.yaml
│ │ └── tempo-config.yaml
│ ├── filelog-processing/
│ │ ├── README.md
│ │ ├── app/
│ │ │ └── generate_logs.py
│ │ ├── config-otel.yaml
│ │ ├── config.alloy
│ │ ├── docker-compose.coda.yml
│ │ ├── docker-compose.yml
│ │ └── loki-config.yaml
│ ├── host-metrics/
│ │ ├── README.md
│ │ ├── config-otel.yaml
│ │ ├── config.alloy
│ │ ├── docker-compose.coda.yml
│ │ ├── docker-compose.yml
│ │ └── prom-config.yaml
│ ├── kafka-buffer/
│ │ ├── README.md
│ │ ├── app/
│ │ │ ├── Dockerfile
│ │ │ ├── app.py
│ │ │ └── requirements.txt
│ │ ├── config-otel.yaml
│ │ ├── config.alloy
│ │ ├── docker-compose.coda.yml
│ │ ├── docker-compose.yml
│ │ └── tempo-config.yaml
│ ├── multi-pipeline-fanout/
│ │ ├── README.md
│ │ ├── app/
│ │ │ ├── Dockerfile
│ │ │ ├── app.py
│ │ │ └── requirements.txt
│ │ ├── config-otel.yaml
│ │ ├── config.alloy
│ │ ├── docker-compose.coda.yml
│ │ ├── docker-compose.yml
│ │ ├── prom-config.yaml
│ │ └── tempo-config.yaml
│ ├── ottl-transform/
│ │ ├── README.md
│ │ ├── app/
│ │ │ ├── Dockerfile
│ │ │ ├── app.py
│ │ │ └── requirements.txt
│ │ ├── config-otel.yaml
│ │ ├── config.alloy
│ │ ├── docker-compose.coda.yml
│ │ ├── docker-compose.yml
│ │ ├── loki-config.yaml
│ │ └── tempo-config.yaml
│ ├── pii-redaction/
│ │ ├── README.md
│ │ ├── app/
│ │ │ ├── Dockerfile
│ │ │ ├── app.py
│ │ │ └── requirements.txt
│ │ ├── config-otel.yaml
│ │ ├── config.alloy
│ │ ├── docker-compose.coda.yml
│ │ ├── docker-compose.yml
│ │ ├── loki-config.yaml
│ │ └── tempo-config.yaml
│ ├── resource-enrichment/
│ │ ├── README.md
│ │ ├── app/
│ │ │ ├── Dockerfile
│ │ │ ├── app.py
│ │ │ └── requirements.txt
│ │ ├── config-otel.yaml
│ │ ├── config.alloy
│ │ ├── docker-compose.coda.yml
│ │ ├── docker-compose.yml
│ │ ├── prom-config.yaml
│ │ └── tempo-config.yaml
│ └── routing-multi-tenant/
│ ├── README.md
│ ├── app/
│ │ ├── generate_logs.py
│ │ └── requirements.txt
│ ├── config-otel.yaml
│ ├── config.alloy
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ └── loki-config.yaml
├── otel-metrics-pipeline/
│ ├── README.md
│ ├── app/
│ │ └── main.py
│ ├── config-otel.yaml
│ ├── config.alloy
│ ├── docker-compose-otel.yml
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ └── prom-config.yaml
├── otel-span-metrics/
│ ├── README.md
│ ├── app/
│ │ ├── load.py
│ │ ├── main.py
│ │ └── requirements.txt
│ ├── config-otel.yaml
│ ├── config.alloy
│ ├── docker-compose-otel.yml
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ ├── prom-config.yaml
│ └── tempo-config.yaml
├── otel-tail-sampling/
│ ├── README.md
│ ├── app/
│ │ ├── Dockerfile
│ │ ├── app.py
│ │ └── requirements.txt
│ ├── config-otel.yaml
│ ├── config.alloy
│ ├── docker-compose-otel.yml
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ ├── prom-config.yaml
│ └── tempo-config.yaml
├── otel-tracing-service-graphs/
│ ├── README.md
│ ├── app/
│ │ ├── Dockerfile
│ │ ├── app.py
│ │ └── requirements.txt
│ ├── config-otel.yaml
│ ├── config.alloy
│ ├── docker-compose-otel.yml
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ ├── prom-config.yaml
│ └── tempo-config.yaml
├── postgres-monitoring/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ └── prom-config.yaml
├── rabbitmq-monitoring/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ ├── enabled_plugins
│ ├── loki-config.yaml
│ ├── prom-config.yaml
│ └── rabbitmq.conf
├── redis-monitoring/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ └── prom-config.yaml
├── renovate.json
├── routing/
│ ├── README.MD
│ ├── config.alloy
│ ├── docker-compose.yaml
│ └── support/
│ ├── grafana/
│ │ └── datasources.yml
│ ├── loki/
│ │ └── server.yaml
│ └── promtail/
│ ├── myCustomLog.txt
│ └── promtail-config.yml
├── run-example.sh
├── self-monitoring/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.yaml
│ └── loki-config.yaml
├── snmp/
│ ├── Readme.md
│ ├── config.alloy
│ ├── docker-compose.yml
│ ├── loki-config.yaml
│ ├── prom-config.yaml
│ └── snmp.yml
├── syslog/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ ├── loki-config.yaml
│ ├── rsyslog.conf
│ └── syslog_simulator.py
├── systemd-journal/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.yml
│ └── loki-config.yaml
├── trace-delivery/
│ ├── README.md
│ ├── app/
│ │ ├── Dockerfile
│ │ ├── app.py
│ │ └── requirements.txt
│ ├── config-otel.yaml
│ ├── config.alloy
│ ├── docker-compose-otel.yml
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ ├── prom-config.yaml
│ └── tempo-config.yaml
├── vault-secrets/
│ ├── README.md
│ ├── auth/
│ │ └── htpasswd
│ ├── config.alloy
│ ├── docker-compose.yml
│ ├── nginx.conf
│ ├── prom-config.yaml
│ └── rotate.sh
├── windows/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.yml
│ ├── loki-config.yaml
│ └── prom-config.yaml
└── windows-events/
├── README.md
├── config.alloy
├── docker-compose.yml
└── loki-config.yaml
================================================
FILE CONTENTS
================================================
================================================
FILE: .coda/coda-start.service
================================================
[Unit]
Description=Coda Alloy Scenario Start
After=network-online.target docker.service
Wants=network-online.target
Requires=docker.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/coda-start.sh
WorkingDirectory=/opt/alloy-scenarios
StandardOutput=journal
StandardError=journal
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
================================================
FILE: .coda/coda-start.sh
================================================
#!/usr/bin/env bash
set -euo pipefail
SCENARIO_FILE="/etc/coda/scenario"
REPO_DIR="/opt/alloy-scenarios"
# Wait for the scenario file to be written by user_data
echo "Waiting for ${SCENARIO_FILE}..."
timeout=120
elapsed=0
while [[ ! -f "$SCENARIO_FILE" ]]; do
sleep 2
elapsed=$((elapsed + 2))
if [[ $elapsed -ge $timeout ]]; then
echo "Timed out waiting for ${SCENARIO_FILE} after ${timeout}s" >&2
exit 1
fi
done
SCENARIO="$(cat "$SCENARIO_FILE")"
echo "Scenario: ${SCENARIO}"
# Pull latest changes from main so new scenarios are always available.
# Explicitly fetch+reset main to handle AMIs built from non-main branches.
echo "Updating alloy-scenarios repo..."
git -C "$REPO_DIR" fetch origin main 2>/dev/null \
&& git -C "$REPO_DIR" checkout main 2>/dev/null \
&& git -C "$REPO_DIR" reset --hard origin/main 2>/dev/null \
|| echo "Warning: git update failed, using baked version"
# Start the scenario (builds images on demand)
exec "$REPO_DIR/coda" start "$SCENARIO"
================================================
FILE: .coda/packer-install.sh
================================================
#!/usr/bin/env bash
# Packer provisioner: set up coda CLI and systemd services on an AMI.
#
# Expects the alloy-scenarios repo to already be cloned to /opt/alloy-scenarios.
# This script is called by the consuming Packer template after cloning.
#
# It intentionally does NOT pre-build scenario images. Scenarios are built
# on demand by `coda start`, so new scenarios work without re-baking the AMI.
set -euo pipefail
INSTALL_DIR="${1:-/opt/alloy-scenarios}"
echo "==> Adding host aliases for alloy"
grep -qxF '127.0.0.1 alloy' /etc/hosts || echo '127.0.0.1 alloy' >> /etc/hosts
echo "==> Symlinking coda CLI"
chmod +x "${INSTALL_DIR}/coda"
ln -sf "${INSTALL_DIR}/coda" /usr/local/bin/coda
echo "==> Pre-pulling common base images"
# Only pull widely-shared base images to speed up first boot.
# Scenario-specific images are built on demand by `coda start`.
docker pull "python:3.11-slim" || true
docker pull "apache/kafka:3.9.0" || true
echo "==> Installing systemd services"
cp "${INSTALL_DIR}/.coda/coda-start.service" /etc/systemd/system/coda-start.service
install -m 0755 "${INSTALL_DIR}/.coda/coda-start.sh" /usr/local/bin/coda-start.sh
systemctl daemon-reload
echo "==> Done"
================================================
FILE: .cursor/docker-example.mdc
================================================
---
description: creating a new alloy docker example
globs:
alwaysApply: false
---
# Grafana Alloy Docker Example Template
This template provides a comprehensive structure for creating a new Grafana Alloy example using Docker Compose. It includes all the necessary components to monitor your application or system with the LGMT stack (Loki, Grafana, Metrics/Prometheus, Tempo).
## Directory Structure
```
your-example-name/
├── config.alloy # Alloy configuration file
├── docker-compose.yml # Docker Compose configuration
├── loki-config.yaml # Loki configuration
├── prom-config.yaml # Prometheus configuration
├── tempo-config.yaml # Tempo configuration (optional)
├── README.md # Documentation for your example
└── [additional files...] # Any additional files needed for your example
```
## Docker Compose Template
Below is a template for your `docker-compose.yml` file that includes all components of the LGMT stack. You can customize it based on your specific needs.
```yaml
version: '3.8'
services:
# Loki for log aggregation
loki:
image: grafana/loki:${GRAFANA_LOKI_VERSION:-3.6.7}
ports:
- 3100:3100/tcp
volumes:
- ./loki-config.yaml:/etc/loki/local-config.yaml
command: -config.file=/etc/loki/local-config.yaml
# Prometheus for metrics collection
prometheus:
image: prom/prometheus:${PROMETHEUS_VERSION:-v3.10.0}
command:
- --web.enable-remote-write-receiver
- --web.enable-otlp-receiver
- --enable-feature=native-histograms
- --enable-feature=exemplar-storage
- --config.file=/etc/prometheus/prometheus.yml
ports:
- 9090:9090/tcp
volumes:
- ./prom-config.yaml:/etc/prometheus/prometheus.yml
# Memcached for Tempo
memcached:
image: memcached:1.6.29
container_name: memcached
ports:
- "11211:11211"
environment:
- MEMCACHED_MAX_MEMORY=64m # Set the maximum memory usage
- MEMCACHED_THREADS=4 # Number of threads to use
# Tempo initialization (required for file permissions)
tempo-init:
image: &tempoImage grafana/tempo:${GRAFANA_TEMPO_VERSION:-2.10.1}
user: root
entrypoint:
- "chown"
- "10001:10001"
- "/var/tempo"
volumes:
- ./tempo-data:/var/tempo
# Tempo for tracing
tempo:
image: *tempoImage
command: ["-config.file=/etc/tempo.yaml"]
ports:
- 3200:3200/tcp # tempo
- 4317:4317/tcp # otlp grpc
- 4318:4318/tcp # otlp http
- 14268:14268/tcp # jaeger thrift http
- 14250:14250/tcp # jaeger grpc
- 6831:6831/udp # jaeger thrift compact
- 6832:6832/udp # jaeger thrift binary
- 9411:9411/tcp # zipkin
volumes:
- ./tempo-config.yaml:/etc/tempo.yaml
- ./tempo-data:/var/tempo
depends_on:
- tempo-init
- memcached
- prometheus
# Grafana for visualization
grafana:
image: grafana/grafana:${GRAFANA_VERSION:-12.4.0}
environment:
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
- GF_AUTH_ANONYMOUS_ENABLED=true
- GF_AUTH_BASIC_ENABLED=false
- GF_INSTALL_PLUGINS=https://storage.googleapis.com/integration-artifacts/grafana-exploretraces-app/grafana-exploretraces-app-latest.zip;grafana-traces-app
ports:
- 3000:3000/tcp
entrypoint:
- sh
- -euc
- |
mkdir -p /etc/grafana/provisioning/datasources
cat <<EOF > /etc/grafana/provisioning/datasources/ds.yaml
apiVersion: 1
datasources:
- name: Loki
type: loki
access: proxy
orgId: 1
url: http://loki:3100
basicAuth: false
isDefault: false
version: 1
editable: false
- name: Prometheus
type: prometheus
orgId: 1
url: http://prometheus:9090
basicAuth: false
isDefault: true
version: 1
editable: false
- name: Tempo
type: tempo
access: proxy
orgId: 1
url: http://tempo:3200
basicAuth: false
isDefault: false
version: 1
editable: false
jsonData:
serviceMap:
datasourceUid: 'Prometheus'
nodeGraph:
enabled: true
EOF
/run.sh
depends_on:
- prometheus
- tempo
# Alloy for telemetry pipeline
alloy:
image: grafana/alloy:${GRAFANA_ALLOY_VERSION:-v1.14.0}
ports:
- 12345:12345 # Alloy HTTP server
volumes:
- ./config.alloy:/etc/alloy/config.alloy
- /var/run/docker.sock:/var/run/docker.sock # For Docker monitoring (optional)
command: run --server.http.listen-addr=0.0.0.0:12345 --storage.path=/var/lib/alloy/data /etc/alloy/config.alloy
```
## Configuration Files
### Loki Configuration (loki-config.yaml)
```yaml
auth_enabled: false
limits_config:
allow_structured_metadata: true
volume_enabled: true
server:
http_listen_port: 3100
common:
ring:
instance_addr: 0.0.0.0
kvstore:
store: inmemory
replication_factor: 1
path_prefix: /tmp/loki
schema_config:
configs:
- from: 2020-05-15
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
storage_config:
tsdb_shipper:
active_index_directory: /tmp/loki/index
cache_location: /tmp/loki/index_cache
filesystem:
directory: /tmp/loki/chunks
pattern_ingester:
enabled: true
ingester:
max_chunk_age: 2h
```
### Prometheus Configuration (prom-config.yaml)
```yaml
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'alloy'
static_configs:
- targets: ['alloy:12345']
otlp:
# Recommended attributes to be promoted to labels.
promote_resource_attributes:
- service.instance.id
- service.name
- service.namespace
- service.version
- cloud.availability_zone
- cloud.region
- container.name
- deployment.environment
- deployment.environment.name
- k8s.cluster.name
- k8s.container.name
- k8s.namespace.name
- k8s.pod.name
storage:
tsdb:
out_of_order_time_window: 30m
```
### Tempo Configuration (tempo-config.yaml)
```yaml
server:
http_listen_port: 3200
log_level: info
cache:
background:
writeback_goroutines: 5
caches:
- roles:
- frontend-search
memcached:
addresses: dns+memcached:11211
query_frontend:
search:
duration_slo: 5s
throughput_bytes_slo: 1.073741824e+09
metadata_slo:
duration_slo: 5s
throughput_bytes_slo: 1.073741824e+09
trace_by_id:
duration_slo: 100ms
metrics:
max_duration: 200h # maximum duration of a metrics query, increase for local setups
query_backend_after: 5m
duration_slo: 5s
throughput_bytes_slo: 1.073741824e+09
distributor:
receivers:
jaeger:
protocols:
thrift_http:
endpoint: "tempo:14268"
grpc:
endpoint: "tempo:14250"
thrift_binary:
endpoint: "tempo:6832"
thrift_compact:
endpoint: "tempo:6831"
zipkin:
endpoint: "tempo:9411"
otlp:
protocols:
grpc:
endpoint: "tempo:4317"
http:
endpoint: "tempo:4318"
opencensus:
endpoint: "tempo:55678"
ingester:
max_block_duration: 5m
compactor:
compaction:
block_retention: 720h
# Note: The metrics_generator section below can be enabled for built-in service graphs.
# Alternatively, use Alloy's servicegraph connector as shown in alloy-service-graphs example.
# metrics_generator:
# registry:
# external_labels:
# source: tempo
# cluster: docker-compose
# storage:
# path: /var/tempo/generator/wal
# remote_write:
# - url: http://prometheus:9090/api/v1/write
# send_exemplars: true
# traces_storage:
# path: /var/tempo/generator/traces
# processor:
# local_blocks:
# filter_server_spans: false
# flush_to_storage: true
storage:
trace:
backend: local
wal:
path: /var/tempo/wal
local:
path: /var/tempo/blocks
# Note: Service graph generation is commented out to allow using Alloy for this purpose.
# overrides:
# defaults:
# metrics_generator:
# processors: [service-graphs, span-metrics, local-blocks]
# generate_native_histograms: both
```
### Alloy Configuration with Service Graph Generation (config.alloy)
```river
/*
* Alloy Configuration for OpenTelemetry Trace Collection with Service Graph Generation
*/
// Receive OpenTelemetry traces
otelcol.receiver.otlp "default" {
http {}
grpc {}
output {
traces = [otelcol.processor.batch.default.input]
}
}
// Batch processor to improve performance
otelcol.processor.batch "default" {
output {
traces = [
otelcol.connector.servicegraph.default.input,
otelcol.exporter.otlp.tempo.input,
]
}
}
// Service Graph Generator
otelcol.connector.servicegraph "default" {
metrics_flush_interval = "10s"
dimensions = ["http.method"]
output {
metrics = [otelcol.exporter.otlphttp.prometheus.input]
}
}
// Send service graph metrics to Prometheus via OTLP
otelcol.exporter.otlphttp "prometheus" {
client {
endpoint = "http://prometheus:9090/api/v1/otlp"
tls {
insecure = true
}
}
}
// Send traces to Tempo for storage and visualization
otelcol.exporter.otlp "tempo" {
client {
endpoint = "tempo:4317"
tls {
insecure = true
}
}
}
```
## README Template
The README.md file for your example should include:
1. A brief description of what the example demonstrates
2. Instructions for running the example
3. What to expect after running the example
4. Any additional steps or configuration needed
Example:
```markdown
# Your Example Name
Brief description of what this example demonstrates and its purpose.
## Overview
The example includes:
- Component 1 (brief description)
- Component 2 (brief description)
- ...
## Running the Demo
1. Clone the repository:
```
git clone https://github.com/grafana/alloy-scenarios.git
cd alloy-scenarios
```
2. Navigate to this example directory:
```
cd your-example-name
```
3. Run using Docker Compose:
```
docker compose up -d
```
Or use the centralized image management:
```
cd ..
./run-example.sh your-example-name
```
4. Access Grafana at http://localhost:3000
## What to Expect
Describe what the user should see after running the example, including:
- What metrics/logs are being collected
- Any dashboards that are automatically set up
- How to interact with the example
## Service Graphs (if applicable)
If your example includes service graph visualization capabilities:
1. Open Grafana (http://localhost:3000)
2. Navigate to Explore
3. Select the Tempo data source
4. Click on the "Service Graph" tab
5. You should see a visual representation of the relationships between services
## Architecture
```
┌────────────┐ ┌──────────┐ ┌───────┐ ┌─────────┐
│ Component1 │────▶│ Component2│─────▶│Component3│──▶│ Grafana │
└────────────┘ └──────────┘ └───┬───┘ └─────────┘
│ ▲
▼ │
┌─────────┐ │
│Component4│───────────┘
└─────────┘
```
Brief explanation of the architecture and data flow.
## Additional Configuration
Any additional steps or configuration that might be needed.
```
## Customizing Your Example
To create your own example:
1. Create a new directory with your example name at the root of the repository
2. Copy the template files from this template
3. Customize the files for your specific use case
4. Update the README.md with specific instructions for your example
5. Add your example to the main README.md table with a link and description
================================================
FILE: .cursor/k8s-example.mdc
================================================
---
description:
globs:
alwaysApply: false
---
# Grafana Alloy Kubernetes Example Template
This template provides a comprehensive structure for creating a new Grafana Alloy example using Kubernetes. It is based on the Kubernetes Monitoring Helm chart which abstracts the need to configure Loki and deploys with best practices for monitoring Kubernetes clusters.
## Directory Structure
```
your-k8s-example-name/
├── k8s-monitoring-values.yml # K8s monitoring helm chart values
├── loki-values.yml # Loki helm chart values
├── grafana-values.yml # Grafana helm chart values
├── kind.yml # Kind cluster configuration (optional)
├── README.md # Documentation for your example
└── [additional files...] # Any additional files needed for your example
```
## Kubernetes Configuration Files
### Kind Cluster Configuration (kind.yml)
If you're using Kind for local development, you can use this configuration:
```yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 30000
hostPort: 30000
```
### K8s Monitoring Helm Chart Values (k8s-monitoring-values.yml)
This file configures the Kubernetes Monitoring Helm chart with Alloy settings:
```yaml
---
cluster:
name: example-monitoring
destinations:
- name: loki
type: loki
url: http://loki-gateway.meta.svc.cluster.local/loki/api/v1/push
# Cluster Events Collection
clusterEvents:
enabled: true
collector: alloy-logs
namespaces:
- meta
- default
# Node Logs Collection
nodeLogs:
enabled: true
collector: alloy-logs
# Pod Logs Collection
podLogs:
enabled: true
gatherMethod: kubernetesApi
collector: alloy-logs
labelsToKeep: ["app_kubernetes_io_name","container","instance","job","level","namespace","service_name","service_namespace","deployment_environment","deployment_environment_name"]
structuredMetadata:
pod: pod # Set structured metadata "pod" from label "pod"
namespaces:
- meta
- default
# Node Metrics Collection
nodeMetrics:
enabled: true
collector: alloy-metrics
# Pod Metrics Collection
podMetrics:
enabled: true
collector: alloy-metrics
namespaces:
- meta
- default
# Kubernetes API Server Metrics
kubernetesMetrics:
enabled: true
collector: alloy-metrics
# Traces Collection (if applicable)
traces:
enabled: true
collector: alloy-receiver
namespaces:
- meta
- default
# Profiles Collection (if applicable)
profiles:
enabled: true
collector: alloy-profiles
namespaces:
- meta
- default
# Collectors Configuration
alloy-singleton:
enabled: false
alloy-metrics:
enabled: true
alloy:
clustering:
enabled: true
alloy-logs:
enabled: true
alloy:
mounts:
varlog: true
clustering:
enabled: true
alloy-profiles:
enabled: true
alloy:
clustering:
enabled: true
alloy-receiver:
enabled: true
alloy:
clustering:
enabled: true
```
### Loki Helm Chart Values (loki-values.yml)
Configuration for the Loki Helm chart:
```yaml
---
loki:
auth_enabled: false
commonConfig:
replication_factor: 1
schemaConfig:
configs:
- from: 2024-01-01
store: tsdb
object_store: s3
schema: v13
index:
prefix: loki_index_
period: 24h
ingester:
chunk_encoding: snappy
tracing:
enabled: true
pattern_ingester:
enabled: true
limits_config:
allow_structured_metadata: true
volume_enabled: true
ruler:
enable_api: true
querier:
max_concurrent: 4
minio:
enabled: true
deploymentMode: SingleBinary
singleBinary:
replicas: 1
resources:
limits:
cpu: 4
memory: 4Gi
requests:
cpu: 2
memory: 2Gi
extraEnv:
- name: GOMEMLIMIT
value: 3750MiB
chunksCache:
writebackSizeLimit: 10MB
# Zero out replica counts of other deployment modes
backend:
replicas: 0
read:
replicas: 0
write:
replicas: 0
ingester:
replicas: 0
querier:
replicas: 0
queryFrontend:
replicas: 0
queryScheduler:
replicas: 0
distributor:
replicas: 0
compactor:
replicas: 0
indexGateway:
replicas: 0
bloomCompactor:
replicas: 0
bloomGateway:
replicas: 0
```
### Grafana Helm Chart Values (grafana-values.yml)
Configuration for the Grafana Helm chart:
```yaml
---
persistence:
type: pvc
enabled: true
# DO NOT DO THIS IN PRODUCTION USECASES
adminUser: admin
adminPassword: adminadminadmin
# CONSIDER USING AN EXISTING SECRET
# admin:
# existingSecret: ""
# userKey: admin-user
# passwordKey: admin-password
service:
enabled: true
type: ClusterIP
datasources:
datasources.yaml:
apiVersion: 1
datasources:
- name: Loki
type: loki
access: proxy
orgId: 1
url: http://loki-gateway.meta.svc.cluster.local:80
basicAuth: false
isDefault: false
version: 1
editable: false
- name: Prometheus
type: prometheus
access: proxy
orgId: 1
url: http://prometheus-server.meta.svc.cluster.local:80
basicAuth: false
isDefault: true
version: 1
editable: false
- name: Tempo
type: tempo
access: proxy
orgId: 1
url: http://tempo.meta.svc.cluster.local:80
basicAuth: false
isDefault: false
version: 1
editable: false
```
## README Template
Here's a template for your example's README.md:
```markdown
# Your Kubernetes Example Name
Brief description of what this example demonstrates and its purpose.
## Prerequisites
- Kubernetes cluster (or Kind for local development)
- Helm (v3.x)
- kubectl
## Setup
### 1. Create a Kubernetes Cluster (Optional, if using Kind)
```bash
kind create cluster --config kind.yml
```
### 2. Create a Namespace for Monitoring
```bash
kubectl create namespace meta
```
### 3. Install Loki
Add the Grafana Helm repository if you haven't already:
```bash
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update
```
Install Loki:
```bash
helm install --values loki-values.yml loki grafana/loki -n meta
```
### 4. Install Grafana
```bash
helm install --values grafana-values.yml grafana grafana/grafana --namespace meta
```
### 5. Install Kubernetes Monitoring (with Alloy)
```bash
helm install --values ./k8s-monitoring-values.yml k8s grafana/k8s-monitoring -n meta
```
## Accessing the Dashboard
### Port Forward Grafana
```bash
kubectl port-forward -n meta svc/grafana 3000:80
```
Navigate to http://localhost:3000 in your browser. The default credentials are:
- Username: admin
- Password: adminadminadmin
## What to Expect
Describe what the user should see after setting up the example, including:
- What metrics/logs are being collected
- Any dashboards that are automatically set up
- How to interact with the example
## Cleanup
To remove the deployed resources:
```bash
helm uninstall k8s -n meta
helm uninstall grafana -n meta
helm uninstall loki -n meta
kubectl delete namespace meta
```
If you created a Kind cluster:
```bash
kind delete cluster
```
```
## Customizing Your Example
To create your own example:
1. Create a new directory with your example name at the root of the repository
2. Copy the template files from this template
3. Customize the files for your specific use case
4. Update the README.md with specific instructions for your example
5. Add your example to the main README.md table with a link and description
## Typical Use Cases for Kubernetes Examples
1. **Logs Collection**: Collecting and analyzing logs from applications running in Kubernetes
2. **Metrics Monitoring**: Monitoring application and infrastructure metrics
3. **Tracing**: Distributed tracing for microservices
4. **Profiling**: Performance profiling of applications
5. **Combined Observability**: Demonstrating how to use all telemetry types together
## Special Considerations for Kubernetes
- **Resource Limits**: Adjust resource requests and limits based on your cluster capacity
- **Persistent Storage**: Configure appropriate storage classes for your environment
- **Security**: In production environments, use proper authentication methods
- **Network Policies**: Consider adding network policies if required for your environment
================================================
FILE: .github/k8s-scenarios.json
================================================
{
"metrics": [
{ "release": "prometheus", "chart": "prometheus-community/prometheus", "values": "prometheus-values.yml" },
{ "release": "grafana", "chart": "grafana/grafana", "values": "grafana-values.yml" },
{ "release": "k8s", "chart": "grafana/k8s-monitoring", "values": "k8s-monitoring-values.yml", "version": "^4.0.0" }
],
"logs": [
{ "release": "loki", "chart": "grafana/loki", "values": "loki-values.yml" },
{ "release": "grafana", "chart": "grafana/grafana", "values": "grafana-values.yml" },
{ "release": "k8s", "chart": "grafana/k8s-monitoring", "values": "k8s-monitoring-values.yml", "version": "^4.0.0" }
],
"tracing": [
{ "release": "tempo", "chart": "grafana/tempo", "values": "tempo-values.yml" },
{ "release": "grafana", "chart": "grafana/grafana", "values": "grafana-values.yml" },
{ "release": "k8s", "chart": "grafana/k8s-monitoring", "values": "k8s-monitoring-values.yml", "version": "^4.0.0" }
],
"profiling": [
{ "release": "pyroscope", "chart": "grafana/pyroscope", "values": "pyroscope-values.yml" },
{ "release": "grafana", "chart": "grafana/grafana", "values": "grafana-values.yml" },
{ "release": "k8s", "chart": "grafana/k8s-monitoring", "values": "k8s-monitoring-values.yml", "version": "^4.0.0" }
],
"events": [
{ "release": "loki", "chart": "grafana/loki", "values": "loki-values.yml" },
{ "release": "grafana", "chart": "grafana/grafana", "values": "grafana-values.yml" }
]
}
================================================
FILE: .github/scenario-list.txt
================================================
aws-firehose-logs
blackbox-probing
continuous-profiling
docker-monitoring
elasticsearch-monitoring
faro-frontend-observability
game-of-tracing
gelf-log-ingestion
kafka
linux
log-api-gateway
log-secret-filtering
logs-file
logs-tcp
mail-house
memcached-monitoring
mysql-monitoring
nginx-monitoring
otel-basic-tracing
otel-metrics-pipeline
otel-span-metrics
otel-tail-sampling
otel-tracing-service-graphs
postgres-monitoring
redis-monitoring
routing
self-monitoring
snmp
syslog
systemd-journal
trace-delivery
vault-secrets
windows
windows-events
================================================
FILE: .github/workflows/check-image-versions.yml
================================================
name: check-image-versions
# Drift guard: every ${VAR:-default} fallback in a docker-compose file
# must match the value of VAR in image-versions.env.
#
# Without this check, renovate's docker manager (which updates fallbacks
# in compose files) and the customManager in renovate.json (which
# updates image-versions.env) can fall out of lockstep — leaving anyone
# who runs `docker compose up` without `--env-file image-versions.env`
# on stale versions.
on:
pull_request:
paths:
- '**/docker-compose.yml'
- '**/docker-compose.yaml'
- '**/docker-compose.coda.yml'
- '**/docker-compose.coda.yaml'
- 'image-versions.env'
- '.github/workflows/check-image-versions.yml'
push:
branches: [main]
permissions:
contents: read
jobs:
check:
name: Compose fallbacks vs image-versions.env
runs-on: ubuntu-latest
timeout-minutes: 3
steps:
- name: Harden runner
uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1
with:
egress-policy: block
allowed-endpoints: >
api.github.com:443
github.com:443
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Compare fallbacks against image-versions.env
run: |
set -euo pipefail
# Build a map of VAR=value from image-versions.env
declare -A want
while IFS='=' read -r k v; do
[[ "$k" =~ ^[A-Z_]+_VERSION$ ]] || continue
want[$k]="$v"
done < <(grep -E '^[A-Z_]+_VERSION=' image-versions.env)
echo "Tracking ${#want[@]} version variables:"
for k in "${!want[@]}"; do
echo " $k=${want[$k]}"
done
echo
# Scan every fallback. Pattern: ${VAR:-default}
mismatches=0
while IFS= read -r -d '' f; do
while IFS= read -r line; do
if [[ "$line" =~ \$\{([A-Z_]+_VERSION):-([^}]+)\} ]]; then
var="${BASH_REMATCH[1]}"
fallback="${BASH_REMATCH[2]}"
expected="${want[$var]:-}"
if [ -z "$expected" ]; then
echo "::warning file=$f::unknown variable $var (not in image-versions.env)"
continue
fi
if [ "$fallback" != "$expected" ]; then
echo "::error file=$f::\${$var:-$fallback} should be \${$var:-$expected}"
mismatches=$((mismatches+1))
fi
fi
done < "$f"
done < <(find . -type f \
\( -name 'docker-compose.yml' -o -name 'docker-compose.yaml' \
-o -name 'docker-compose.coda.yml' -o -name 'docker-compose.coda.yaml' \) \
-not -path '*/k8s/*' -not -path '*/.git/*' -print0)
if [ "$mismatches" -gt 0 ]; then
echo
echo "::error::Found $mismatches drift(s). Update either the fallback in the compose file or image-versions.env."
exit 1
fi
echo "OK — all fallbacks match image-versions.env"
================================================
FILE: .github/workflows/validate-k8s-scenarios.yml
================================================
name: validate-k8s-scenarios
# Lightweight validation for k8s scenarios under k8s/. Mirrors the
# defense-in-depth posture of validate-scenarios.yml (docker), but
# without paying the cost of a real cluster on every PR:
#
# validate (every PR): helm template + kubeconform per chart per scenario.
# Renders offline, validates against k8s API schemas.
# kind-integration: opt-in via workflow_dispatch only. Boots kind,
# helm-installs all charts, waits for pods Ready.
#
# Defense-in-depth (same as the docker workflow):
# - permissions: contents: read (no token write, no secrets)
# - harden-runner egress allowlist (compromised tool can't phone home)
# - third-party actions SHA-pinned (tag pushes can't sneak in)
# - direct binary downloads, version-pinned (helm, kubeconform)
# - github-hosted ephemeral runners
# - pull_request, not pull_request_target
on:
pull_request:
paths:
- 'k8s/**'
- '.github/k8s-scenarios.json'
- '.github/workflows/validate-k8s-scenarios.yml'
workflow_dispatch:
inputs:
kind_integration:
description: 'Run the kind-cluster integration job after validation'
type: boolean
default: false
scenario:
description: 'Which scenario(s) to run kind-integration for ("all" or comma-separated subset, e.g. "metrics,logs")'
type: string
default: 'all'
permissions:
contents: read
concurrency:
group: validate-k8s-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
env:
HELM_VERSION: 'v4.1.4'
KUBECONFORM_VERSION: 'v0.6.7'
KUBERNETES_VERSION: '1.31.0'
jobs:
# ──────────────────────────────────────────────────────────────────
# validate: helm template + kubeconform per chart for each of the
# 4 scenarios. Pure offline — no API server, no real cluster.
# ──────────────────────────────────────────────────────────────────
validate:
name: Validate ${{ matrix.scenario }}
runs-on: ubuntu-latest
timeout-minutes: 8
strategy:
fail-fast: false
matrix:
scenario: [metrics, logs, tracing, profiling, events]
steps:
- name: Harden runner
uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1
with:
egress-policy: block
allowed-endpoints: >
api.github.com:443
github.com:443
objects.githubusercontent.com:443
release-assets.githubusercontent.com:443
raw.githubusercontent.com:443
get.helm.sh:443
grafana.github.io:443
prometheus-community.github.io:443
charts.bitnami.com:443
pypi.org:443
files.pythonhosted.org:443
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install helm + kubeconform + yamllint
run: |
set -euo pipefail
# Helm — pinned by version. Upstream tarball, verify by sha would
# be ideal but Helm doesn't publish stable per-tag checksums in a
# consumable way; pinning the version + restricting egress is the
# workable compromise.
curl -fsSL "https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz" \
| tar -xz -C /tmp
sudo install -m 0755 /tmp/linux-amd64/helm /usr/local/bin/helm
# kubeconform — pinned. The release archive contains just the
# binary; we extract it directly.
curl -fsSL "https://github.com/yannh/kubeconform/releases/download/${KUBECONFORM_VERSION}/kubeconform-linux-amd64.tar.gz" \
| sudo tar -xz -C /usr/local/bin/ kubeconform
# yamllint — preinstalled python3 + pip on ubuntu-latest.
sudo pip install --quiet yamllint
helm version --short
kubeconform -v
yamllint --version
- name: Helm repo bootstrap
run: |
set -euo pipefail
helm repo add grafana https://grafana.github.io/helm-charts
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
- name: yamllint values files
# Loose ruleset — values files commonly use long datasource URLs and
# don't need a leading `---`. Run as advisory: don't fail the job on
# style; we want it for hygiene signal, not blocking.
continue-on-error: true
run: |
yamllint -d "{extends: relaxed, rules: {line-length: disable, document-start: disable}}" \
k8s/${{ matrix.scenario }}/
- name: Helm template + kubeconform per chart
run: |
set -euo pipefail
mkdir -p /tmp/rendered
fail=0
# Iterate the scenario's chart list. helm template against the
# remote chart triggers values.schema.json validation upstream
# (most grafana charts ship a schema), then kubeconform validates
# the rendered Kubernetes API objects against the target version.
while IFS= read -r entry; do
release=$(jq -r '.release' <<<"$entry")
chart=$(jq -r '.chart' <<<"$entry")
values=$(jq -r '.values' <<<"$entry")
version=$(jq -r '.version // ""' <<<"$entry")
values_path="k8s/${{ matrix.scenario }}/$values"
ver_arg=()
[ -n "$version" ] && ver_arg=(--version "$version")
echo "::group::helm template $release ($chart${version:+ @$version})"
out="/tmp/rendered/${{ matrix.scenario }}-$release.yaml"
if ! helm template "$release" "$chart" "${ver_arg[@]}" \
-f "$values_path" > "$out" 2> "/tmp/rendered/${{ matrix.scenario }}-$release.err"; then
echo "::error::helm template failed for $release"
cat "/tmp/rendered/${{ matrix.scenario }}-$release.err"
fail=1
echo "::endgroup::"
continue
fi
lines=$(wc -l < "$out")
echo "Rendered $lines lines to $out"
echo "::endgroup::"
echo "::group::kubeconform $release"
# -ignore-missing-schemas: skip CRDs whose schemas aren't in the
# datree catalog (catching built-in K8s API drift is the real
# signal; CRD validation is the chart maintainer's responsibility).
if ! kubeconform -strict -summary \
-kubernetes-version "$KUBERNETES_VERSION" \
-schema-location default \
-schema-location 'https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json' \
-ignore-missing-schemas \
"$out"; then
echo "::error::kubeconform failed for $release"
fail=1
fi
echo "::endgroup::"
done < <(jq -c --arg s "${{ matrix.scenario }}" '.[$s][]' .github/k8s-scenarios.json)
exit $fail
- name: Upload rendered manifests
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: rendered-${{ matrix.scenario }}
path: /tmp/rendered/
retention-days: 7
# ──────────────────────────────────────────────────────────────────
# kind-integration: Boots a real kind cluster and helm-installs all
# charts for the scenario. Heavy — only on workflow_dispatch.
# ──────────────────────────────────────────────────────────────────
kind-integration:
name: Kind integration ${{ matrix.scenario }}
if: github.event_name == 'workflow_dispatch' && inputs.kind_integration == true
runs-on: ubuntu-latest
timeout-minutes: 25
strategy:
fail-fast: false
matrix:
scenario: [metrics, logs, tracing, profiling, events]
steps:
- name: Harden runner
uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1
with:
egress-policy: block
# Adds image registries on top of the validate allowlist —
# helm install actually pulls images for kind to schedule.
allowed-endpoints: >
api.github.com:443
github.com:443
objects.githubusercontent.com:443
release-assets.githubusercontent.com:443
raw.githubusercontent.com:443
get.helm.sh:443
grafana.github.io:443
prometheus-community.github.io:443
charts.bitnami.com:443
registry-1.docker.io:443
auth.docker.io:443
production.cloudflare.docker.com:443
ghcr.io:443
quay.io:443
cdn.quay.io:443
grafana.com:443
mcr.microsoft.com:443
public.ecr.aws:443
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Filter by scenario input
id: filter
# User-supplied workflow_dispatch input is passed via env, NOT
# interpolated directly into the run block, to prevent
# template-injection (zizmor: template-injection rule).
# matrix.scenario IS safe to interpolate directly because it's
# constrained to the static list above.
env:
USER_SCENARIO: ${{ inputs.scenario }}
MATRIX_SCENARIO: ${{ matrix.scenario }}
run: |
set -euo pipefail
if [ "$USER_SCENARIO" = "all" ]; then
echo "run=true" >> "$GITHUB_OUTPUT"
exit 0
fi
if grep -qx "$MATRIX_SCENARIO" <(tr ',' '\n' <<<"$USER_SCENARIO"); then
echo "run=true" >> "$GITHUB_OUTPUT"
else
echo "run=false" >> "$GITHUB_OUTPUT"
echo "::notice::Skipping $MATRIX_SCENARIO (not in user-selected subset '$USER_SCENARIO')"
fi
- name: Install helm
if: steps.filter.outputs.run == 'true'
run: |
curl -fsSL "https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz" \
| tar -xz -C /tmp
sudo install -m 0755 /tmp/linux-amd64/helm /usr/local/bin/helm
- name: Create kind cluster
if: steps.filter.outputs.run == 'true'
uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0
with:
config: k8s/${{ matrix.scenario }}/kind.yml
cluster_name: ${{ matrix.scenario }}
- name: Helm bootstrap + install all charts
if: steps.filter.outputs.run == 'true'
run: |
set -euo pipefail
helm repo add grafana https://grafana.github.io/helm-charts
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
kubectl create namespace meta || true
while IFS= read -r entry; do
release=$(jq -r '.release' <<<"$entry")
chart=$(jq -r '.chart' <<<"$entry")
values=$(jq -r '.values' <<<"$entry")
version=$(jq -r '.version // ""' <<<"$entry")
values_path="k8s/${{ matrix.scenario }}/$values"
ver_arg=()
[ -n "$version" ] && ver_arg=(--version "$version")
echo "::group::helm install $release ($chart)"
helm install "$release" "$chart" "${ver_arg[@]}" \
-f "$values_path" -n meta --create-namespace \
--wait --timeout 5m
echo "::endgroup::"
done < <(jq -c --arg s "${{ matrix.scenario }}" '.[$s][]' .github/k8s-scenarios.json)
- name: Wait for pods Ready in meta namespace
if: steps.filter.outputs.run == 'true'
run: |
if ! kubectl wait --for=condition=Ready pods --all -n meta --timeout=10m; then
echo "::error::Pods did not become Ready"
kubectl get pods -n meta -o wide
kubectl describe pods -n meta
exit 1
fi
kubectl get pods -n meta -o wide
================================================
FILE: .github/workflows/validate-scenarios.yml
================================================
name: validate-scenarios
# Boots every scenario whose files were touched by the PR, after a CVE
# scan of every image the scenario will run. Designed to make renovate
# dependency PRs reviewable on signal rather than diff-eyeballing alone.
#
# Defense-in-depth (intentional, not paranoia):
# - permissions: contents: read — no token write, no secrets
# - third-party actions SHA-pinned — tag pushes can't sneak in
# - trivy advisory scan before boot — known-bad images flagged in PR
# - github-hosted ephemeral runners — runner state is not persisted
#
# Triggered on pull_request (NOT pull_request_target): fork PRs run
# without secrets, which is the safe default. Updating this file
# requires the same scrutiny as updating any third-party action SHA.
on:
pull_request:
paths:
- '*/docker-compose.yml'
- '*/docker-compose.yaml'
- '*/docker-compose.coda.yml'
- '*/Dockerfile'
- '*/config.alloy'
- '*/app/**'
- '*/*/Dockerfile'
- '*/*/requirements.txt'
- '*/*/package.json'
- '*/*/*.csproj'
- 'image-versions.env'
- '.github/scenario-list.txt'
- '.github/workflows/validate-scenarios.yml'
# Manual trigger — runs the full matrix without the sampling cap, so a
# maintainer can validate a cross-cutting change (e.g. an LGMT bump
# that touches every scenario) before merging. PRs auto-sample when
# affected count exceeds MATRIX_CAP; workflow_dispatch always runs all.
workflow_dispatch: {}
env:
# Maximum scenarios to validate on a PR before sampling kicks in.
# Picked so a typical big update finishes within ~30 min wall-clock
# at the configured max-parallel; bypassed by workflow_dispatch.
MATRIX_CAP: '8'
permissions:
contents: read
concurrency:
# `pull_request.number || run_id` keeps PR runs grouped (and superseded
# by force-pushes) while still giving every workflow_dispatch run its
# own slot — manual full-matrix runs shouldn't cancel each other.
group: validate-scenarios-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
jobs:
# ──────────────────────────────────────────────────────────────────
# detect: Map changed files to top-level scenario directories.
# Pure shell — no third-party action — to keep the supply-chain
# surface minimal.
# ──────────────────────────────────────────────────────────────────
detect:
name: Detect affected scenarios
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
scenarios: ${{ steps.filter.outputs.scenarios }}
count: ${{ steps.filter.outputs.count }}
count_full: ${{ steps.filter.outputs.count_full }}
sampled: ${{ steps.filter.outputs.sampled }}
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Compute affected scenarios
id: filter
env:
EVENT_NAME: ${{ github.event_name }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set -euo pipefail
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
# Manual run: validate every scenario in the canonical list.
# No diff to compute; sampling cap is bypassed.
cp .github/scenario-list.txt /tmp/affected.txt
else
# The base sha may not be in the local clone with a shallow
# checkout; fetch-depth: 0 avoids that, but be belt-and-braces.
git fetch origin "$BASE_SHA" "$HEAD_SHA" --depth=200 2>/dev/null || true
# Map every changed file to its first path segment. Empty lines
# come from root-level files (no segment); awk drops those.
git diff --name-only "$BASE_SHA" "$HEAD_SHA" \
| awk -F/ 'NF>1 {print $1}' \
| sort -u > /tmp/segments.txt
# Intersect with the canonical scenario list. `|| true` keeps
# the pipeline alive when there's no overlap (e.g. a PR that
# only touches docs/CI).
grep -Fxf /tmp/segments.txt .github/scenario-list.txt \
| sort -u > /tmp/affected.txt || true
fi
count_full=$(wc -l < /tmp/affected.txt | tr -d ' ')
sampled=false
# Sampling cap: when a single PR touches more than MATRIX_CAP
# scenarios (typical for image-versions.env / shared-base
# changes), validate a deterministic representative subset
# rather than the full matrix. Maintainers can run the full
# matrix via workflow_dispatch before merging if signal on
# every scenario is wanted.
#
# Determinism: sort by the SHA-256 of "<scenario><commit>".
# Same commit → same subset, so re-runs are stable. Different
# commits get different subsets, so coverage rotates over
# time across many big-update PRs.
if [ "$EVENT_NAME" != "workflow_dispatch" ] \
&& [ "$count_full" -gt "$MATRIX_CAP" ]; then
sampled=true
commit_hash="${HEAD_SHA:-$GITHUB_SHA}"
while read -r line; do
[ -z "$line" ] && continue
key=$(printf "%s%s" "$line" "$commit_hash" \
| sha256sum | head -c 16)
printf "%s\t%s\n" "$key" "$line"
done < /tmp/affected.txt \
| sort | head -n "$MATRIX_CAP" | cut -f2 > /tmp/active.txt
else
cp /tmp/affected.txt /tmp/active.txt
fi
count=$(wc -l < /tmp/active.txt | tr -d ' ')
scenarios=$(jq -Rsc 'split("\n") | map(select(length>0))' /tmp/active.txt)
echo "scenarios=$scenarios" >> "$GITHUB_OUTPUT"
echo "count=$count" >> "$GITHUB_OUTPUT"
echo "count_full=$count_full" >> "$GITHUB_OUTPUT"
echo "sampled=$sampled" >> "$GITHUB_OUTPUT"
{
echo "## Affected scenarios"
echo
if [ "$count_full" = "0" ]; then
echo "_None — PR does not touch any scenario directory._"
elif [ "$sampled" = "true" ]; then
echo "**$count_full** scenarios affected; sampled **$count** for validation (cap is \`$MATRIX_CAP\`)."
echo
echo "Trigger \`workflow_dispatch\` on this branch to validate the full matrix."
echo
echo "Sampled subset:"
echo '```'
cat /tmp/active.txt
echo '```'
echo
echo "<details><summary>Full affected list ($count_full)</summary>"
echo
echo '```'
cat /tmp/affected.txt
echo '```'
echo
echo "</details>"
else
echo "Count: \`$count\`"
echo
echo '```'
cat /tmp/active.txt
echo '```'
fi
} >> "$GITHUB_STEP_SUMMARY"
if [ "$sampled" = "true" ]; then
echo "::warning::Sampled $count of $count_full affected scenarios. Run workflow_dispatch on this branch to validate them all."
fi
# ──────────────────────────────────────────────────────────────────
# scan: For each affected scenario, resolve every image reference
# via `docker compose config --images`, then trivy-scan each one.
# Hard-fails on HIGH/CRITICAL CVEs that have a fix available.
# ──────────────────────────────────────────────────────────────────
scan:
name: Scan images
needs: detect
if: needs.detect.outputs.count != '0'
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
fail-fast: false
max-parallel: 6
matrix:
scenario: ${{ fromJSON(needs.detect.outputs.scenarios) }}
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Compute today's UTC date for cache key
id: date
run: echo "today=$(date -u +%Y-%m-%d)" >> "$GITHUB_OUTPUT"
- name: Restore trivy DB cache
# Trivy fetches a fresh vulnerability DB on every cold scan
# (~30 MB, ~5-10 s per scenario from mirror.gcr.io). Caching
# the DB shaves the cold-pull off every matrix entry after the
# first one of the day. Key rotates daily so the DB stays
# fresh; the restore-keys fallback is intentional — even a
# stale-by-hours DB is far better than a cold fetch.
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: /tmp/trivy-cache
key: trivy-db-${{ steps.date.outputs.today }}
restore-keys: |
trivy-db-
- name: Resolve images for ${{ matrix.scenario }}
id: images
run: |
set -euo pipefail
# Try .yml first, fall back to .yaml (some scenarios use either).
compose_file=""
for ext in yml yaml; do
f="${{ matrix.scenario }}/docker-compose.$ext"
if [ -f "$f" ]; then compose_file="$f"; break; fi
done
if [ -z "$compose_file" ]; then
echo "No docker-compose found for ${{ matrix.scenario }}" >&2
exit 1
fi
# `docker compose config --images` returns service-name defaults
# like `game-of-tracing-ai-opponent` for `build:`-only services
# — those don't exist in any registry, so trivy fails with
# UNAUTHORIZED. Filter to services with an explicit `image:`
# field (third-party registry artifacts only). Locally-built
# images aren't directly scanned; their FROM base image (e.g.
# python:3.11-slim) lives in the Dockerfile and is tracked
# separately by renovate's docker manager.
docker compose -f "$compose_file" \
--env-file image-versions.env \
config --format json \
| jq -r '.services | to_entries[]
| select(.value.image != null)
| .value.image' \
| sort -u > /tmp/images.txt
echo "Images to scan:"
cat /tmp/images.txt
if [ ! -s /tmp/images.txt ]; then
echo "::notice::No third-party images to scan in this scenario (all services build locally)."
fi
- name: Trivy scan each image (advisory)
# Run trivy via its own docker image (digest-pinned). No
# docker.sock mount: trivy pulls the target image itself rather
# than reaching into the host's docker — keeps the trivy
# container from gaining root-equivalent access on the runner.
#
# Advisory mode: HIGH/CRITICAL findings are reported via the job
# log + step summary table + ::warning:: annotations, but the
# step always exits 0. These are demo scenarios; upstream LGMT
# images regularly carry HIGH-with-fix findings between releases
# and blocking every PR until they catch up isn't useful. Treat
# the report as a signal to bump base images, not a merge gate.
env:
# Suppress ANSI escapes so the log + summary parse cleanly
NO_COLOR: '1'
run: |
set -euo pipefail
TRIVY_IMAGE='aquasec/trivy:0.66.0@sha256:086971aaf400beebd94e8300fd8ea623774419597169156cec56eec5b00dfb1e'
# Pre-pull once so loop iterations don't re-resolve.
docker pull "$TRIVY_IMAGE"
mkdir -p /tmp/trivy-cache
report_log=/tmp/trivy-output.log
: > "$report_log"
while IFS= read -r img; do
[ -z "$img" ] && continue
echo "::group::Scanning $img"
echo "=== $img ===" >> "$report_log"
# `|| true` so a non-zero trivy exit (had findings) doesn't
# abort the loop — we want to scan every image.
docker run --rm \
-e NO_COLOR=1 \
-v /tmp/trivy-cache:/root/.cache/trivy \
"$TRIVY_IMAGE" image \
--severity HIGH,CRITICAL \
--ignore-unfixed \
--no-progress \
--timeout 5m \
"$img" 2>&1 | tee -a "$report_log" || true
echo "::endgroup::"
done < /tmp/images.txt
# Per-image summary table for the PR's step summary.
{
echo "## CVE scan: ${{ matrix.scenario }}"
echo
if [ ! -s /tmp/images.txt ]; then
echo "_No third-party images to scan (all services build locally)._"
else
echo "| Image | HIGH | CRITICAL |"
echo "|---|---:|---:|"
current=""
h=0; c=0
while IFS= read -r line; do
if [[ "$line" =~ ^===\ (.+)\ ===$ ]]; then
if [ -n "$current" ]; then
echo "| \`$current\` | $h | $c |"
fi
current="${BASH_REMATCH[1]}"
h=0; c=0
elif [[ "$line" =~ Total:\ [0-9]+\ \(HIGH:\ ([0-9]+),\ CRITICAL:\ ([0-9]+)\) ]]; then
h=$((h + ${BASH_REMATCH[1]}))
c=$((c + ${BASH_REMATCH[2]}))
fi
done < "$report_log"
if [ -n "$current" ]; then
echo "| \`$current\` | $h | $c |"
fi
echo
echo "_HIGH+CRITICAL counts are unfixed CVEs with patches available upstream. Findings here don't block merge — see the job log for the full per-CVE table. Upgrade base images via the relevant renovate PR when fixes appear in a published release._"
fi
} >> "$GITHUB_STEP_SUMMARY"
# Emit a single ::warning:: if anything was found, so the PR
# gets an inline annotation pointing at the job summary.
if grep -qE 'Total:\ [^0]' "$report_log"; then
echo "::warning::trivy found HIGH/CRITICAL unfixed CVEs in scanned images for ${{ matrix.scenario }}. See job summary for per-image counts and the log for details."
fi
# ──────────────────────────────────────────────────────────────────
# smoke: For each affected scenario, boot it via run-example.sh,
# wait until something healthy answers (Grafana, then Alloy, then
# Prometheus), then tear down.
# ──────────────────────────────────────────────────────────────────
smoke:
name: Smoke test
needs: [detect, scan]
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
max-parallel: 4
matrix:
scenario: ${{ fromJSON(needs.detect.outputs.scenarios) }}
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Boot ${{ matrix.scenario }}
run: |
set -euo pipefail
chmod +x ./run-example.sh
./run-example.sh "${{ matrix.scenario }}"
- name: Wait for a healthy endpoint (Grafana, Alloy, or Prometheus)
run: |
set -euo pipefail
# Probe in priority order. Most scenarios expose Grafana on
# :3000; self-monitoring exposes Alloy on :12345 instead;
# routing remaps Alloy. Grafana wins when present, else any
# ready endpoint counts as bring-up success.
probes=(
"http://localhost:3000/api/health"
"http://localhost:12345/-/ready"
"http://localhost:9090/-/ready"
)
deadline=$(( $(date +%s) + 180 )) # 3 min total
while [ "$(date +%s)" -lt "$deadline" ]; do
for url in "${probes[@]}"; do
code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 3 "$url" 2>/dev/null || true)
if [ "$code" = "200" ]; then
echo "Healthy: $url"
exit 0
fi
done
sleep 5
done
echo "::error::No probe endpoint became healthy within 3 min"
exit 1
- name: Verify no exited containers
run: |
set -euo pipefail
cd "${{ matrix.scenario }}"
# `docker compose ps --status exited` lists any container that
# crashed during bring-up. An empty list is the pass case.
exited=$(docker compose ps --status exited --format '{{.Name}}' || true)
if [ -n "$exited" ]; then
echo "::error::Exited containers detected:"
echo "$exited"
exit 1
fi
- name: Dump container logs on failure
if: failure()
run: |
cd "${{ matrix.scenario }}"
docker compose logs --no-color || true
- name: Tear down
if: always()
run: |
cd "${{ matrix.scenario }}"
docker compose down --volumes --remove-orphans || true
================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
================================================
FILE: CLAUDE.md
================================================
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
This is a collection of self-contained demonstration scenarios for **Grafana Alloy**, the telemetry collection and processing pipeline. Each scenario lives in its own top-level directory and showcases a specific monitoring use case using the **LGMT stack** (Loki, Grafana, Metrics/Prometheus, Tempo).
## Running Scenarios
```bash
# Option 1: Direct (uses default image versions in docker-compose.yml)
cd <scenario-dir> && docker compose up -d
# Option 2: Centralized image versions (from repo root)
./run-example.sh <scenario-dir>
# Stop a scenario
cd <scenario-dir> && docker compose down
```
Image versions are centralized in `image-versions.env` at the repo root. Docker-compose files reference these via `${VAR:-default}` syntax.
Kubernetes scenarios (under `k8s/`) use Helm charts instead of Docker Compose — see their individual READMEs.
## Scenario Structure
Every Docker-based scenario follows this layout:
```
scenario-name/
├── docker-compose.yml # LGMT stack + Alloy (infrastructure only)
├── docker-compose.coda.yml # Demo app services (run via coda CLI or -f flag)
├── config.alloy # Alloy pipeline configuration (River/HCL syntax)
├── loki-config.yaml # Loki backend config
├── prom-config.yaml # Prometheus backend config
├── tempo-config.yaml # Tempo config (if tracing is involved)
├── README.md # What the scenario demonstrates and how to use it
└── app/ # Optional demo application (typically Python/Flask)
```
## Alloy Configuration Language
`config.alloy` files use Alloy's River syntax (HCL-like). Pipelines follow a consistent pattern:
1. **Receivers/Sources** — ingest data (`loki.source.*`, `otelcol.receiver.*`, `prometheus.exporter.*`)
2. **Processors/Transformers** — parse, relabel, batch (`loki.process.*`, `discovery.relabel`, `otelcol.processor.*`)
3. **Writers/Exporters** — send to backends (`loki.write.*`, `prometheus.remote_write.*`, `otelcol.exporter.*`)
Components are wired together by passing outputs to inputs (e.g., `forward_to = [loki.write.default.receiver]`).
## Creating a New Scenario
Templates exist in `.cursor/docker-example.mdc` (Docker) and `.cursor/k8s-example.mdc` (Kubernetes) with full boilerplate for all config files.
Checklist for a new scenario:
1. Create a new top-level directory named after the scenario
2. Include `docker-compose.yml`, `config.alloy`, backend configs, and `README.md`
3. Use `${VAR:-default}` for image versions matching `image-versions.env` keys
4. Grafana service should auto-provision datasources via entrypoint script (see template)
5. Add the scenario to the main `README.md` table
6. Alloy UI is available at `http://localhost:12345` for debugging pipelines
## Key Conventions
- Grafana runs on port 3000 with anonymous admin auth enabled (no login required)
- Alloy HTTP server runs on port 12345
- Python demo apps use OpenTelemetry SDK for instrumentation (`telemetry.py` pattern)
- Backend configs (loki, prometheus, tempo) are minimal single-instance dev configs — not production-ready
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: README.md
================================================
<p align="center">
<img src="./img/banner.png" alt="Grafana Alloy Scenarios Banner" width="300"/>
</p>
# Grafana Alloy Scenarios
A collection of self-contained, runnable scenarios demonstrating how to use [Grafana Alloy](https://grafana.com/docs/alloy/) for telemetry collection and processing. Each scenario includes a full LGMT stack (Loki, Grafana, Mimir, Tempo) with pre-configured dashboards so you can explore immediately.
## Getting Started
### Prerequisites
- [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/)
### Run a scenario
```bash
# Option 1: Navigate to the scenario directory
cd <scenario-dir> && docker compose up -d
# Option 2: Use centralized image management (from repo root)
./run-example.sh <scenario-directory>
```
The centralized approach manages all Docker image versions in a single `image-versions.env` file, making it easy to update images across all scenarios.
### Access the stack
Once a scenario is running:
- **Grafana**: [http://localhost:3000](http://localhost:3000) (no login required)
- **Alloy UI**: [http://localhost:12345](http://localhost:12345) (pipeline debugging)
### Run with the Coda app overlay
Each scenario includes a `docker-compose.coda.yml` file that defines the demo application services separately from the infrastructure stack. This lets you run just the observability backend on its own, or layer in the app when you're ready:
```bash
# Infrastructure only
cd <scenario-dir> && docker compose up -d
# Infrastructure + demo app
cd <scenario-dir> && docker compose -f docker-compose.yml -f docker-compose.coda.yml up -d
```
If you have the `coda` CLI installed, it manages the app overlay automatically:
```bash
coda start <scenario-dir> # Start app containers
coda stop <scenario-dir> # Stop app containers
coda status <scenario-dir> # Show container status
coda list # List all available scenarios
```
### Stop a scenario
```bash
cd <scenario-dir> && docker compose down
```
## Scenarios
### Logs
| Scenario | Description |
| -------- | ----------- |
| [GELF log ingestion](gelf-log-ingestion/) | Ingest structured logs from applications using the GELF (Graylog Extended Log Format) protocol over UDP. |
| [Kafka logs](kafka/) | Consume and process logs from Apache Kafka topics. |
| [Log API gateway](log-api-gateway/) | Use Alloy as a centralized log gateway that accepts logs via a Loki-compatible push API endpoint. |
| [Log routing](routing/) | Route logs from multiple sources to different Loki tenants based on log content and origin. |
| [Log secret filtering](log-secret-filtering/) | Automatically redact sensitive credentials and secrets from logs using pattern matching before storage. |
| [Logs from file](logs-file/) | Monitor and tail log files using Alloy. |
| [Logs over TCP](logs-tcp/) | Receive and process TCP logs in JSON format. |
| [Popular logging frameworks](app-instrumentation/logging/popular-logging-frameworks/) | Parse logs from popular logging frameworks across 7 programming languages. |
| [Structured log parsing](mail-house/) | Parse structured logs into labels and structured metadata. |
| [Syslog monitoring](syslog/) | Monitor non-RFC5424 compliant syslog messages using `rsyslog` and Alloy. |
### Tracing
| Scenario | Description |
| -------- | ----------- |
| [Distributed tracing](trace-delivery/) | Learn distributed tracing through a sofa delivery workflow from order to doorstep. |
| [Game of tracing](game-of-tracing/) | An interactive strategy game teaching distributed tracing, sampling, and service graphs. |
| [OpenTelemetry basic tracing](otel-basic-tracing/) | Collect and visualize OpenTelemetry traces using Alloy and Tempo. |
| [OpenTelemetry service graphs](otel-tracing-service-graphs/) | Generate service graphs using the Alloy `servicegraph` connector. |
| [OpenTelemetry span metrics](otel-span-metrics/) | Generate RED metrics (Request rate, Error rate, Duration) from OpenTelemetry traces using the span metrics connector. |
| [OpenTelemetry tail sampling](otel-tail-sampling/) | Apply tail sampling policies to OpenTelemetry traces with Alloy and Tempo. |
### Metrics
| Scenario | Description |
| -------- | ----------- |
| [Blackbox probing](blackbox-probing/) | Monitor endpoint availability and response times using synthetic HTTP probes. |
| [OTel metrics pipeline](otel-metrics-pipeline/) | Forward OpenTelemetry metrics from applications through Alloy with batching and transformation into Prometheus. |
### Profiling
| Scenario | Description |
| -------- | ----------- |
| [Continuous profiling](continuous-profiling/) | Collect and visualize CPU, memory, and goroutine profiles from Go applications using Grafana Pyroscope. |
### Secrets and configuration
| Scenario | Description |
| -------- | ----------- |
| [Vault secrets](vault-secrets/) | Pull `prometheus.remote_write` basic_auth credentials from HashiCorp Vault at runtime using `remote.vault`, with hot-reload on rotation. |
### Frontend
| Scenario | Description |
| -------- | ----------- |
| [Faro frontend observability](faro-frontend-observability/) | Collect frontend web telemetry (logs, errors, web vitals) from browser applications using the Faro Web SDK. |
### Cloud Monitoring
| Scenario | Description |
| -------- | ----------- |
| [CloudWatch metrics](cloudwatch-metrics/) | Pull AWS CloudWatch metrics into Prometheus via `prometheus.exporter.cloudwatch`. Uses LocalStack for offline reproducibility — no AWS account required. |
### Infrastructure Monitoring
| Scenario | Description |
| -------- | ----------- |
| [Docker monitoring](docker-monitoring/) | Monitor Docker container metrics and logs. |
| [Monitor Linux](linux/) | Monitor a Linux server's system metrics using Alloy. |
| [Monitor Windows](windows/) | Monitor Windows system metrics and Event Logs. |
| [Self-monitoring](self-monitoring/) | Configure Alloy to monitor itself, collecting its own metrics and logs. |
| [SNMP monitoring](snmp/) | Monitor SNMP devices using the Alloy SNMP exporter. |
### Database and Cache Monitoring
| Scenario | Description |
| -------- | ----------- |
| [Elasticsearch monitoring](elasticsearch-monitoring/) | Monitor Elasticsearch cluster health, node status, and performance metrics. |
| [Memcached monitoring](memcached-monitoring/) | Monitor Memcached instance metrics including connections, memory usage, and command performance. |
| [MySQL monitoring](mysql-monitoring/) | Monitor MySQL database server metrics and performance indicators. |
| [PostgreSQL monitoring](postgres-monitoring/) | Monitor PostgreSQL transaction statistics, connections, and server configuration. |
| [RabbitMQ monitoring](rabbitmq-monitoring/) | Monitor RabbitMQ queue, connection, and channel metrics plus broker container logs. |
| [Redis monitoring](redis-monitoring/) | Monitor Redis instance metrics including connections, memory usage, and command throughput. |
### Kubernetes
| Scenario | Description |
| -------- | ----------- |
| [Kubernetes](k8s/) | A series of scenarios demonstrating Alloy setup using the Kubernetes monitoring Helm chart. See subdirectories for telemetry-specific examples. |
### OTel Engine Examples (Experimental)
Alloy v1.14+ includes an experimental **OTel Engine** that runs standard OpenTelemetry Collector YAML configs directly. These scenarios use `alloy otel` instead of River/HCL syntax. See the [OTel examples README](otel-examples/) for details.
| Scenario | Description |
| -------- | ----------- |
| [File log processing](otel-examples/filelog-processing/) | Collect and parse mixed-format log files using the OTel `filelog` receiver with operator chains. |
| [PII redaction](otel-examples/pii-redaction/) | Scrub credit cards, emails, and IPs from traces and logs using OTTL `replace_pattern`. |
| [Multi-tenant routing](otel-examples/routing-multi-tenant/) | Route logs to different Loki tenants based on resource attributes using fan-out and filter. |
| [Cost control](otel-examples/cost-control/) | Drop health checks, filter debug logs, and apply probabilistic sampling to cut telemetry volume. |
| [Resource enrichment](otel-examples/resource-enrichment/) | Auto-attach host, OS, and Docker metadata to all signals via `resourcedetection`. |
| [Count connector](otel-examples/count-connector/) | Derive request rate and error rate metrics from traces and logs using the `count` connector. |
| [OTTL transform cookbook](otel-examples/ottl-transform/) | A cookbook of OTTL patterns: JSON parsing, severity mapping, attribute promotion, truncation. |
| [Host metrics](otel-examples/host-metrics/) | Collect CPU, memory, disk, and network metrics using the `hostmetrics` receiver. |
| [Multi-pipeline fan-out](otel-examples/multi-pipeline-fanout/) | Send traces to two backends with different processing per destination. |
| [Kafka buffer](otel-examples/kafka-buffer/) | Buffer traces through Kafka for durability and backpressure handling. |
## Contributing
Contributions of scenarios or improvements to scenarios are welcome. You can contribute in several ways:
### Suggest a scenario
If you have an idea for a scenario but don't have time to implement it:
1. Open an [issue](https://github.com/grafana/alloy-scenarios/issues/new) with the label `scenario-suggestion`
2. Describe the scenario and what it would demonstrate
3. Explain why this would be valuable to the community
4. Outline any special requirements or considerations
### Contribute a scenario
If you'd like to contribute a complete scenario:
1. Fork this repository and create a branch
2. Create a directory in the root of this repository with a descriptive name for your scenario
3. Follow the [scenario template](#scenario-template) below
4. Submit a pull request with your scenario
### Improve a scenario
To improve a scenario:
1. Fork this repository and create a branch
2. Make your improvements to the scenario
3. Submit a pull request with a clear description of your changes
### Scenario template
When creating a scenario, include the following files:
- `docker-compose.yml` - Docker Compose file with the LGMT stack
- `docker-compose.coda.yml` - Docker Compose override with the demo app services (for use with the `coda` CLI or `-f` flag)
- `config.alloy` - Alloy configuration file for the scenario
- `README.md` - Documentation explaining the scenario
- Any additional files needed for your scenario, such as scripts or data files
### Scenario checklist
Before submitting your scenario, ensure that you have:
- [ ] Created a directory in the root of this repository with a descriptive name
- [ ] Included a docker-compose.yml file with the necessary components, such as LGMT stack or subset
- [ ] Created a complete config.alloy file that demonstrates the monitoring approach
- [ ] Written a README.md with:
- A clear description of what the scenario demonstrates
- Prerequisites for running the demo
- Step-by-step instructions for running the demo
- Expected output and what to look for
- Screenshots if applicable
- Explanation of key configuration elements
- [ ] Added the scenario to the table in this README.md
- [ ] Ensured the scenario works with the centralized image management system
- [ ] Verified all components start correctly with `docker compose up -d`
### Best practices for scenarios
- Keep the scenario focused on demonstrating one concept
- Use clear, descriptive component and variable names
- Add comments to explain complex parts of your Alloy configuration
- Consider including a "Customizing" section in your README.md
- Provide sample queries for Grafana/Prometheus/Loki/Tempo that work with your scenario
- Use environment variables for versions and configurable parameters
## Get help
If you have questions about creating a scenario or need help with Alloy:
- Join the [Grafana Labs Community Forums](https://community.grafana.com/)
- Check the [Grafana Alloy documentation](https://grafana.com/docs/alloy/)
## License
This repository is licensed under the Apache License, Version 2.0. Refer to [LICENSE](LICENSE) for the full license text.
================================================
FILE: app-instrumentation/logging/popular-logging-frameworks/README.md
================================================
# App Instrumentation - Structured Logging with Alloy Parsing
This directory contains a comprehensive **Alloy tutorial** demonstrating how to parse structured logs from 7 popular programming languages using modern logging frameworks. Each language uses industry-standard structured logging libraries, and all logs are processed through a unified Alloy pipeline for collection, parsing, and storage in Loki.
## 🎯 Tutorial Objectives
- **Learn Alloy log parsing**: Understand how to parse different log formats using `loki.process` stages
- **Multi-language support**: Handle logs from 7 different programming languages in a single pipeline
- **Structured logging**: Demonstrate modern logging practices with structured data
- **Real-world scenarios**: Show practical log parsing for containerized applications
## Languages and Modern Logging Frameworks
| Language | Logging Framework | Type | Key Features | Docker Base Image |
|----------|------------------|------|--------------|-------------------|
| **JavaScript** | `Pino` | JSON structured | High performance, child loggers, ndjson output | `node:22-alpine` |
| **Python** | `logging` module | Structured text | Built-in standard library with custom formatting | `python:3.12-slim` |
| **Java** | `SLF4J + Logback` | Structured text | Parameterized messages, MDC context, thread info | `openjdk:26-slim` |
| **C#** | `Microsoft.Extensions.Logging` | Structured text | .NET standard framework, event IDs, structured data | `mcr.microsoft.com/dotnet/*:9.0` |
| **C++** | `spdlog` | Structured text | High performance, source location, thread-safe | `ubuntu:24.04` |
| **Go** | `Zap` | JSON structured | High performance, named loggers, structured fields | `golang:1.23-alpine` |
| **PHP** | `Monolog` | Structured text | Context arrays, processors, multiple handlers | `php:8.3-cli-alpine` |
## Directory Structure
```
app-instrumentation/logging/popular-logging-frameworks/
├── alloy/
│ ├── config.alloy # Main Alloy configuration
│ └── helper.alloy # Language-specific log parsers
├── javascript/
│ ├── app.js # Pino structured logging
│ └── Dockerfile
├── python/
│ ├── app.py # Python logging with custom format
│ └── Dockerfile
├── java/
│ ├── App.java # SLF4J + Logback
│ ├── logback.xml
│ └── Dockerfile
├── csharp/
│ ├── Program.cs # Microsoft.Extensions.Logging
│ ├── LoggingExample.csproj
│ └── Dockerfile
├── cpp/
│ ├── main.cpp # spdlog structured logging
│ ├── CMakeLists.txt
│ └── Dockerfile
├── go/
│ ├── main.go # Zap JSON logging
│ ├── go.mod
│ ├── go.sum
│ └── Dockerfile
├── php/
│ ├── app.php # Monolog with context
│ └── Dockerfile
├── docker-compose.yml # Complete stack with Loki + Grafana
├── loki-config.yaml
└── README.md
```
## 🔍 Alloy Parsing Features Demonstrated
### Core Alloy Components Used
- **`loki.source.docker`**: Automatic Docker container log discovery
- **`loki.process`**: Multi-stage log parsing pipeline
- **`discovery.docker`**: Container metadata extraction
- **`discovery.relabel`**: Label transformation and routing
### Advanced Parsing Techniques
Each language parser demonstrates different Alloy parsing capabilities:
- **Regex parsing** (`stage.regex`): Extract structured fields from text logs
- **JSON parsing** (`stage.json`): Handle native JSON log formats
- **Multiline handling** (`stage.multiline`): Process stack traces and exception logs
- **Label management** (`stage.labels`): Efficient indexing for filtering
- **Structured metadata** (`stage.structured_metadata`): Searchable non-indexed data
- **Timestamp parsing** (`stage.timestamp`): Multiple timestamp format support
- **Template formatting** (`stage.template`): Custom output formatting
- **Conditional logic**: Level conversion, error prioritization
### Language-Specific Parsing Examples
| Language | Primary Challenge | Alloy Solution |
|----------|------------------|----------------|
| **JavaScript (Pino)** | JSON numeric levels | Template stage for level conversion |
| **Python** | Custom text format | Regex extraction with line numbers |
| **Java (Logback)** | Multi-line stack traces | Multiline stage + regex parsing |
| **C#** | Event IDs and namespaces | Regex parsing with structured metadata |
| **C++** | Source location details | Complex regex for file:line extraction |
| **Go (Zap)** | Unix timestamps | Timestamp parsing with fractional seconds |
| **PHP (Monolog)** | Nested JSON context | Multiple JSON parsing stages |
## 🚀 Quick Start Tutorial
### Step 1: Clone the Repository
```bash
git clone https://github.com/grafana/alloy-scenarios.git
cd app-instrumentation/logging/popular-logging-frameworks
```
### Step 2: Launch the Complete Stack
```bash
# Build and run all applications with Alloy + Loki + Grafana
docker compose up --build
# Run in detached mode to see clean output
docker compose up --build -d
```
This starts:
- **7 language applications** generating structured logs
- **Alloy** parsing and forwarding logs to Loki
- **Loki** storing parsed logs with labels and metadata
- **Grafana** for log visualization and querying
### Step 3: Explore the Logs
- Head to http://localhost:3000/a/grafana-lokiexplore-app to see the logs in Grafana
- Each language has its own service name / app so you can identify which languge you would like to see the parsed logs for
## 📚 Learning Outcomes
After completing this tutorial, you'll understand:
### Alloy Concepts
- **Multi-stage processing**: How to chain `loki.process` stages for complex parsing
- **Component composition**: Using `import.file` to modularize configurations
- **Discovery patterns**: Automatic service discovery with Docker integration
- **Label vs. metadata strategy**: When to use indexed labels vs. structured metadata
### Log Parsing Techniques
- **Regex mastery**: Complex pattern matching for text log formats
- **JSON handling**: Extracting nested fields from structured logs
- **Timestamp parsing**: Supporting multiple timestamp formats across languages
- **Multiline processing**: Handling stack traces and exception logs
- **Conditional formatting**: Template logic for log transformation
### Real-World Patterns
- **Language-specific challenges**: Understanding unique parsing requirements per language
- **Performance considerations**: Efficient labeling and metadata strategies
- **Observability best practices**: Structured logging principles across tech stacks
- **Container log collection**: Production-ready log aggregation patterns
## 🔧 Configuration Details
### Language-Specific Parsing Challenges
Each language presents unique parsing requirements:
#### JavaScript (Pino)
```alloy
// Challenge: Numeric log levels (10, 20, 30, 40, 50, 60)
stage.template {
source = "level"
template = "{{- if eq .level_num \"30\" -}}info{{- else if eq .level_num \"50\" -}}error{{- end -}}"
}
```
#### Java (Logback)
```alloy
// Challenge: Multi-line stack traces
stage.multiline {
firstline = "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}"
}
```
#### Go (Zap)
```alloy
// Challenge: Unix timestamp with fractional seconds
stage.timestamp {
source = "ts"
format = "1750342991.0445938"
}
```
================================================
FILE: app-instrumentation/logging/popular-logging-frameworks/alloy/config.alloy
================================================
// ###############################
// #### Main Logging Configuration ####
// ###############################
// Import the custom log parsing helper module.
// This gives us access to the "app_logs_parser" component that handles
// language-specific log parsing for Python, JavaScript, Go, Java, C#, PHP, and C++.
import.file "helper" {
filename = "/etc/alloy/helper.alloy"
}
// Discover Docker containers running on the local Docker daemon.
// This component continuously monitors the Docker socket for container changes,
// providing real-time discovery of log sources without manual configuration.
discovery.docker "linux" {
host = "unix:///var/run/docker.sock" // Connect to local Docker daemon via Unix socket
}
// Transform Docker container metadata into useful labels for log routing.
// This creates a "service_name" label from the container name, which is used
// by our parsing stages to determine which language parser to apply.
discovery.relabel "logs_integrations_docker" {
targets = [] // Start with empty targets (will be populated by discovery.docker)
// Extract container name and use it as service identifier
// Example: container "/python-app" becomes service_name="python-app"
rule {
source_labels = ["__meta_docker_container_name"] // Docker provides this metadata
regex = "/(.*)" // Remove leading slash from container name
target_label = "service_name" // Create clean service identifier
}
}
// Instantiate our custom log parser with output destination.
// This creates the processing pipeline that will parse logs from all supported languages
// and forward them to Loki for storage and querying.
helper.app_logs_parser "default" {
write_to = [loki.write.local.receiver] // Send parsed logs to our Loki instance
}
// Collect logs from all discovered Docker containers.
// This is the main log collection engine that streams container logs in real-time
// and feeds them into our language-specific parsing pipeline.
loki.source.docker "default" {
host = "unix:///var/run/docker.sock" // Connect to Docker daemon
targets = discovery.docker.linux.targets // Use discovered containers
labels = {"platform" = "docker"} // Add platform label to all logs
relabel_rules = discovery.relabel.logs_integrations_docker.rules // Apply container name transformation
forward_to = [helper.app_logs_parser.default.parser_input] // Send raw logs to our parser
}
// Configure Loki write endpoint for log storage.
// This is where all parsed and enriched logs are finally stored for querying,
// alerting, and analysis in Grafana or other tools.
loki.write "local" {
endpoint {
url = "http://loki:3100/loki/api/v1/push" // Loki's standard push API endpoint
}
}
================================================
FILE: app-instrumentation/logging/popular-logging-frameworks/alloy/helper.alloy
================================================
declare "app_logs_parser" {
// argument.write_to is a required argument that specifies where parsed
// log lines are sent.
//
// The value of the argument is retrieved in this file with
// argument.write_to.value.
argument "write_to" {
optional = false
}
// loki.process.app_logs_parser is our component which executes the parsing,
// passing parsed logs to argument.write_to.value.
loki.process "app_logs_parser" {
// ## Python Processing ##
// Let only python logs pass through this stage. This is done via the label match on the service_name label.
stage.match {
pipeline_name = "python"
selector = "{service_name=\"python\"}"
// Extract the timestamp, file, line number, level, and message from the log line.
// Python logs format: "2025-06-17 09:54:15,283 - main.py:25 - INFO - Starting application"
stage.regex {
expression = "^(?P<timestamp>\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2},\\d{3}) - (?P<file>[^:]+):(?P<line_num>\\d+) - (?P<level>[^ ]+) - (?P<msg>.*)"
}
// Set the file and level as labels for efficient filtering and querying in Loki.
// Labels are indexed and should be used for high-cardinality filtering.
stage.labels {
values = {
file = "",
level = "",
}
}
// Set the timestamp to the timestamp extracted from the log line.
// This ensures proper chronological ordering in Loki.
stage.timestamp {
source = "timestamp"
format = "2006-01-02 15:04:05,000"
}
// Set the line number as structured metadata in Loki (non-indexed).
// Structured metadata is searchable but not indexed, reducing storage costs.
stage.structured_metadata {
values = {
line_num = "",
}
}
// We want to maintain a similar format to the original log line so we use template to create a new
// temporary variable called output. This creates a clean, consistent format across all Python logs.
stage.template {
source = "output"
template = "{{.file}} - {{.line_num}} - {{.level}} - {{.msg}}"
}
// We use the new output variable to create a new log body. This is the log line that will be sent to loki.
// The output stage replaces the original log message with our formatted version.
stage.output {
source = "output"
}
}
// ## Node.js Processing ##
// Let only node.js logs pass through this stage. This is done via the label match on the service_name label.
stage.match {
pipeline_name = "javascript"
selector = "{service_name=\"javascript\"}"
// Extract fields from JSON-formatted Pino logs.
// Pino outputs structured JSON logs with fields like level (numeric), time (timestamp), msg, etc.
stage.json {
expressions = {
level_num = "level",
time = "time",
pid = "pid",
hostname = "hostname",
msg = "msg",
obj = "obj",
counter = "counter",
component = "component",
query = "query",
duration = "duration",
version = "version",
method = "method",
path = "path",
status = "status",
nested_obj = "nested.obj",
nested_timestamp = "nested.timestamp",
err_type = "err.type",
err_message = "err.message",
err_stack = "err.stack",
}
}
// Convert Pino's numeric log levels to human-readable strings.
// Pino uses numbers: 10=trace, 20=debug, 30=info, 40=warn, 50=error, 60=fatal
stage.template {
source = "level"
template = "{{- if eq .level_num \"10\" -}}trace{{- else if eq .level_num \"20\" -}}debug{{- else if eq .level_num \"30\" -}}info{{- else if eq .level_num \"40\" -}}warn{{- else if eq .level_num \"50\" -}}error{{- else if eq .level_num \"60\" -}}fatal{{- else -}}unknown{{- end -}}"
}
// Set important fields as labels for efficient querying.
// hostname and component help identify log sources, level enables filtering by severity.
stage.labels {
values = {
file = "",
hostname = "",
component = "",
level = "",
}
}
// Set the timestamp from Pino's Unix millisecond timestamp.
// Pino logs include precise timestamps for accurate log ordering.
stage.timestamp {
source = "time"
format = "UnixMs"
}
// Store all extracted fields as structured metadata for searchability without indexing costs.
// This includes process info, request details, and error information.
stage.structured_metadata {
values = {
level_num = "",
pid = "",
query = "",
duration = "",
version = "",
method = "",
path = "",
status = "",
nested_obj = "",
nested_timestamp = "",
err_type = "",
err_message = "",
err_stack = "",
}
}
// Create a consistent output format prioritizing error messages over regular messages.
// This provides better visibility of errors while maintaining standard log structure.
stage.template {
source = "output"
template = "{{.hostname}} - {{.level}} - {{ if .err_message }}{{ .err_message }}{{ else }}{{ .msg }}{{ end }}"
}
// Apply the formatted output as the final log message sent to Loki.
stage.output {
source = "output"
}
}
// ## Go Processing ##
// Let only go logs pass through this stage. This is done via the label match on the service_name label.
stage.match {
pipeline_name = "go"
selector = "{service_name=\"go\"}"
// Extract fields from Zap's JSON-structured logs.
// Zap outputs detailed JSON logs with structured fields for better observability.
stage.json {
expressions = {
level = "level",
ts = "ts",
logger = "logger",
caller = "caller",
msg = "msg",
answer = "answer",
obj = "obj",
counter = "counter",
feature = "feature",
query = "query",
duration = "duration",
method = "method",
path = "path",
status = "status",
requestId = "requestId",
context1 = "context1",
context2 = "context2",
error = "error",
stacktrace = "stacktrace",
nested_obj = "nested.obj",
nested_timestamp = "nested.timestamp",
}
}
// Set logger name and level as indexed labels for efficient filtering.
// This enables quick filtering by specific loggers (e.g., database, api) and log levels.
stage.labels {
values = {
logger = "",
level = "",
}
}
// Parse Zap's Unix timestamp with fractional seconds.
// Zap provides high-precision timestamps for accurate log correlation.
stage.timestamp {
source = "ts"
format = "1750342991.0445938"
}
// Store all contextual information as structured metadata.
// This includes caller info, request details, errors, and application-specific data.
stage.structured_metadata {
values = {
caller = "caller",
answer = "answer",
obj = "obj",
counter = "counter",
feature = "feature",
query = "query",
duration = "duration",
method = "method",
path = "path",
status = "status",
requestId = "requestId",
context1 = "context1",
context2 = "context2",
error = "error",
stacktrace = "stacktrace",
nested_obj = "nested.obj",
nested_timestamp = "nested.timestamp",
}
}
// Create a clean, consistent output format showing logger, level, and message.
// This maintains readability while preserving structured data in metadata.
stage.template {
source = "output"
template = "{{.logger}} - {{.level}} - {{.msg}}"
}
// Apply the formatted output as the final log message.
stage.output {
source = "output"
}
}
// ## Java Processing ##
// Let only java logs pass through this stage. This is done via the label match on the service_name label.
stage.match {
pipeline_name = "java"
selector = "{service_name=\"java\"}"
// Handle multi-line Java stack traces by identifying the start of new log entries.
// Java exceptions often span multiple lines, so we need to group them properly.
stage.multiline {
firstline = "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}\\+\\d{4}\\[[^\\]]+\\]\\s+[A-Z]+\\s+\\w+\\s+-\\s+"
}
// Parse Logback's structured log format including timestamps, threads, levels, and stack traces.
// Format: "2024-01-15T14:41:02.423+0000[main] INFO App - Starting application"
stage.regex {
expression = "^(?P<timestamp>\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}\\+\\d{4})\\[(?P<thread>[^\\]]+)\\] (?P<level>[A-Z]+)\\s+(?P<logger>[^ ]+) - (?P<msg>[^\n]*)(?:\\n(?P<stacktrace>.*))?"
}
// Set logger and level as indexed labels for efficient log filtering.
// This enables filtering by specific Java classes/packages and log severity.
stage.labels {
values = {
logger = "",
level = "",
}
}
// Parse ISO 8601 timestamp with timezone for accurate time correlation.
// Java's Logback uses precise timestamps with timezone information.
stage.timestamp {
source = "timestamp"
format = "2006-01-02T15:04:05.000-0700"
}
// Store thread information and stack traces as structured metadata.
// Thread info helps with concurrent debugging, stack traces provide error context.
stage.structured_metadata {
values = {
thread = "",
stacktrace = "",
}
}
// Format output to show essential information: logger, level, and message.
// Stack traces are preserved in metadata for when they're needed.
stage.template {
source = "output"
template = "{{.logger}} - {{.level}} - {{.msg}}"
}
// Apply the clean formatted output while preserving detailed metadata.
stage.output {
source = "output"
}
}
// ## C# Processing ##
// Let only c# logs pass through this stage. This is done via the label match on the service_name label.
stage.match {
pipeline_name = "csharp"
selector = "{service_name=\"csharp\"}"
// Handle multi-line .NET logs and exception stack traces.
// .NET logging can span multiple lines, especially with structured logging and exceptions.
stage.multiline {
firstline = "^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}\\.\\d{3} [a-z]+: [^\\[]+\\[\\d+\\]"
}
// Parse .NET's structured logging format with event IDs.
// Format: "2024-01-15 14:41:02.423 info: Microsoft.Extensions.Hosting[1] Starting application"
stage.regex {
expression = "^(?P<timestamp>\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}\\.\\d{3}) (?P<level>[a-z]+): (?P<logger>[^\\[]+)\\[(?P<event_id>\\d+)\\]\\n\\s+(?P<msg>.*)"
}
// Set logger namespace and level as indexed labels for filtering.
// .NET uses hierarchical logger names (e.g., Microsoft.Extensions.Hosting) for categorization.
stage.labels {
values = {
logger = "",
level = "",
}
}
// Parse .NET's standard timestamp format (no timezone).
// .NET logging typically uses local time format.
stage.timestamp {
source = "timestamp"
format = "2006-01-02 15:04:05.000"
}
// Store .NET-specific event IDs as structured metadata.
// Event IDs help categorize and filter specific types of .NET framework events.
stage.structured_metadata {
values = {
event_id = "",
}
}
// Create consistent output format showing logger namespace, level, and message.
stage.template {
source = "output"
template = "{{.logger}} - {{.level}} - {{.msg}}"
}
// Apply the formatted output to maintain consistency with other language logs.
stage.output {
source = "output"
}
}
// ## PHP Processing ##
// Let only php logs pass through this stage. This is done via the label match on the service_name label.
stage.match {
pipeline_name = "php"
selector = "{service_name=\"php\"}"
// Parse Monolog's default line format with timestamp, logger, level, message, context, and extra data.
// Format: "[2024-01-15T14:41:02.123456+00:00] app.INFO: hello world {"counter":42} {"environment":"production"}"
stage.regex {
expression = "^\\[(?P<timestamp>[^\\]]+)\\] (?P<logger>[^.]+)\\.(?P<level>[A-Z]+): (?P<msg>.*?) (?P<context_json>\\[\\]|\\{.*?\\}) (?P<extra_json>\\{.*?\\})$"
}
// Set logger name and level as indexed labels for efficient querying.
// PHP applications often use multiple named loggers (app, database, api, etc.).
stage.labels {
values = {
logger = "",
level = "",
}
}
// Parse Monolog's ISO 8601 timestamp with microseconds and timezone.
// Monolog provides high-precision timestamps for accurate log correlation.
stage.timestamp {
source = "timestamp"
format = "2006-01-02T15:04:05.000000-07:00"
}
// Extract application-specific data from the context JSON.
// Context contains request-specific data like counters, query info, API details, etc.
stage.json {
source = "context_json"
expressions = {
counter = "counter",
obj = "obj",
query = "query",
duration = "duration",
method = "method",
path = "path",
status = "status",
exception = "exception",
error_code = "error_code",
affected_service = "affected_service",
}
}
// Extract environment and system-level data from the extra JSON.
// Extra data typically contains environment info, process details, etc.
stage.json {
source = "extra_json"
expressions = {
environment = "environment",
}
}
// Store all extracted PHP context and environment data as structured metadata.
// This provides rich searchability for PHP application debugging and monitoring.
stage.structured_metadata {
values = {
counter = "",
obj = "",
query = "",
duration = "",
method = "",
path = "",
status = "",
exception = "",
error_code = "",
affected_service = "",
environment = "",
}
}
// Create clean output format showing logger, level, and message.
// Detailed context remains accessible in structured metadata.
stage.template {
source = "output"
template = "{{.logger}} - {{.level}} - {{.msg}}"
}
// Apply the standardized output format while preserving rich PHP context data.
stage.output {
source = "output"
}
}
// ## C++ Processing ##
// Let only cpp logs pass through this stage. This is done via the label match on the service_name label.
stage.match {
pipeline_name = "cpp"
selector = "{service_name=\"cpp\"}"
// Parse C++ structured logging format with detailed source location information.
// Format: "2024-01-15 14:41:02.423 [info] [logger] [thread 1] [main.cpp:25 main] - Starting application"
stage.regex {
expression = "^(?P<timestamp>\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}\\.\\d{3}) \\[(?P<level>[^\\]]+)\\] \\[(?P<logger>[^\\]]+)\\] \\[(?P<thread>[^\\]]+)\\] \\[(?P<file>[^:]+):(?P<line_num>\\d+) (?P<function>[^\\]]+)\\] - (?P<msg>.*)"
}
// Set logger, level, and source file as indexed labels for debugging.
// C++ logs benefit from file-based filtering for debugging specific modules.
stage.labels {
values = {
logger = "",
level = "",
file = "",
}
}
// Parse standard timestamp format used by C++ logging libraries.
stage.timestamp {
source = "timestamp"
format = "2006-01-02 15:04:05.000"
}
// Store detailed C++ debugging information as structured metadata.
// Thread info, line numbers, and function names are crucial for C++ debugging.
stage.structured_metadata {
values = {
thread = "",
line_num = "",
function = "",
}
}
// Create detailed output showing file location, function, level, and message.
// C++ debugging often requires precise source location information.
stage.template {
source = "output"
template = "{{.file}}:{{.line_num}} {{.function}} - {{.level}} - {{.msg}}"
}
// Apply the detailed C++ format optimized for debugging and troubleshooting.
stage.output {
source = "output"
}
}
// Send processed logs to our argument.
forward_to = argument.write_to.value
}
// export.parser_input exports a value to the module consumer.
export "parser_input" {
// Expose the receiver of loki.process so the module importer can send
// logs to our loki.process component.
value = loki.process.app_logs_parser.receiver
}
}
================================================
FILE: app-instrumentation/logging/popular-logging-frameworks/cpp/CMakeLists.txt
================================================
cmake_minimum_required(VERSION 3.16)
project(LoggingExample)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Find required packages
find_package(PkgConfig REQUIRED)
find_package(Threads REQUIRED)
# Add spdlog
include(FetchContent)
FetchContent_Declare(
spdlog
GIT_REPOSITORY https://github.com/gabime/spdlog.git
GIT_TAG v1.12.0
)
FetchContent_MakeAvailable(spdlog)
# Create executable
add_executable(logging_example main.cpp)
# Link libraries
target_link_libraries(logging_example
PRIVATE
spdlog::spdlog
Threads::Threads
)
# Compiler-specific options
if(MSVC)
target_compile_options(logging_example PRIVATE /W4)
else()
target_compile_options(logging_example PRIVATE -Wall -Wextra -Wpedantic)
endif()
================================================
FILE: app-instrumentation/logging/popular-logging-frameworks/cpp/Dockerfile
================================================
FROM ubuntu:26.04@sha256:f3d28607ddd78734bb7f71f117f3c6706c666b8b76cbff7c9ff6e5718d46ff64
# Install build dependencies
RUN apt-get update && apt-get install -y \
cmake \
g++ \
make \
git \
pkg-config \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY . .
# Build the application
RUN cmake -B build -S . && \
cmake --build build --config Release
# Run the application
CMD ["./build/logging_example"]
================================================
FILE: app-instrumentation/logging/popular-logging-frameworks/cpp/main.cpp
================================================
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <chrono>
#include <thread>
int main() {
auto console = spdlog::stdout_color_mt("logger");
spdlog::set_default_logger(console);
spdlog::set_level(spdlog::level::debug);
spdlog::set_pattern(
"%Y-%m-%d %H:%M:%S.%e [%^%l%$] [%n] [thread %t] [%s:%# %!] - %v"
);
int counter = 0;
SPDLOG_LOGGER_INFO(console, "Starting C++ basic logging example");
SPDLOG_LOGGER_INFO(console, "Demonstrating spdlog formatting");
while (true) {
counter++;
int logType = counter % 5;
switch (logType) {
case 0:
SPDLOG_LOGGER_DEBUG(console, "Basic debug message, counter: {}", counter);
break;
case 1:
SPDLOG_LOGGER_INFO(console, "Information message, counter: {}", counter);
break;
case 2:
SPDLOG_LOGGER_WARN(console, "Warning message, counter: {}", counter);
break;
case 3:
SPDLOG_LOGGER_ERROR(console, "Error message, counter: {}", counter);
break;
case 4:
SPDLOG_LOGGER_CRITICAL(console, "Critical message, counter: {}", counter);
break;
}
std::this_thread::sleep_for(std::chrono::seconds(1));
}
return 0;
}
================================================
FILE: app-instrumentation/logging/popular-logging-frameworks/csharp/Dockerfile
================================================
FROM mcr.microsoft.com/dotnet/sdk:9.0@sha256:0300d42309afd86168fa57d62db79020a34ee396d39c9634844b9c0ab285ea55 AS build
WORKDIR /app
COPY *.csproj .
RUN dotnet restore
COPY . .
RUN dotnet publish -c Release -o out
FROM mcr.microsoft.com/dotnet/runtime:9.0@sha256:7590f1b7e124fe7a4b7cffa5f6f9958f2c02a22bf5bd7a0387a84b88cddf4057
WORKDIR /app
COPY --from=build /app/out .
ENTRYPOINT ["dotnet", "LoggingExample.dll"]
================================================
FILE: app-instrumentation/logging/popular-logging-frameworks/csharp/LoggingExample.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.7" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.7" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.7" />
</ItemGroup>
</Project>
================================================
FILE: app-instrumentation/logging/popular-logging-frameworks/csharp/Program.cs
================================================
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Threading.Tasks;
namespace LoggingExample
{
class Program
{
private static ILogger<Program>? _logger;
static async Task Main(string[] args)
{
// Configure logging with proper formatting
using var host = Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.AddConsole(options =>
{
options.TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff ";
options.IncludeScopes = false;
});
logging.SetMinimumLevel(LogLevel.Debug);
})
.Build();
_logger = host.Services.GetRequiredService<ILogger<Program>>();
int counter = 0;
_logger.LogInformation("Starting C# basic logging example");
_logger.LogInformation("Demonstrating Microsoft.Extensions.Logging");
// Infinite loop with different log levels
while (true)
{
counter++;
// Cycle through different log levels
int logType = counter % 5;
switch (logType)
{
case 0:
_logger.LogDebug("Basic debug message, counter: {Counter}", counter);
break;
case 1:
_logger.LogInformation("Information message, counter: {Counter}", counter);
break;
case 2:
_logger.LogWarning("Warning message, counter: {Counter}", counter);
break;
case 3:
_logger.LogError("Error message, counter: {Counter}", counter);
break;
case 4:
_logger.LogCritical("Critical message, counter: {Counter}", counter);
break;
}
// Wait 1 second before next log
await Task.Delay(1000);
}
}
}
}
================================================
FILE: app-instrumentation/logging/popular-logging-frameworks/docker-compose.coda.yml
================================================
services:
javascript-logging:
build:
context: ./javascript
dockerfile: Dockerfile
container_name: javascript
environment:
- NODE_ENV=production
restart: unless-stopped
python-logging:
build:
context: ./python
dockerfile: Dockerfile
container_name: python
environment:
- PYTHON_ENV=production
restart: unless-stopped
java-logging:
build:
context: ./java
dockerfile: Dockerfile
container_name: java
environment:
- JAVA_ENV=production
restart: unless-stopped
csharp-logging:
build:
context: ./csharp
dockerfile: Dockerfile
container_name: csharp
environment:
- DOTNET_ENVIRONMENT=Production
restart: unless-stopped
cpp-logging:
build:
context: ./cpp
dockerfile: Dockerfile
container_name: cpp
environment:
- CPP_ENV=production
restart: unless-stopped
go-logging:
build:
context: ./go
dockerfile: Dockerfile
container_name: go
environment:
- GO_ENV=production
restart: unless-stopped
php-logging:
build:
context: ./php
dockerfile: Dockerfile
container_name: php
environment:
- PHP_ENV=production
restart: unless-stopped
================================================
FILE: app-instrumentation/logging/popular-logging-frameworks/docker-compose.yml
================================================
version: '3.8'
services:
javascript-logging:
build:
context: ./javascript
dockerfile: Dockerfile
container_name: javascript
environment:
- NODE_ENV=production
restart: unless-stopped
python-logging:
build:
context: ./python
dockerfile: Dockerfile
container_name: python
environment:
- PYTHON_ENV=production
restart: unless-stopped
java-logging:
build:
context: ./java
dockerfile: Dockerfile
container_name: java
environment:
- JAVA_ENV=production
restart: unless-stopped
csharp-logging:
build:
context: ./csharp
dockerfile: Dockerfile
container_name: csharp
environment:
- DOTNET_ENVIRONMENT=Production
restart: unless-stopped
cpp-logging:
build:
context: ./cpp
dockerfile: Dockerfile
container_name: cpp
environment:
- CPP_ENV=production
restart: unless-stopped
go-logging:
build:
context: ./go
dockerfile: Dockerfile
container_name: go
environment:
- GO_ENV=production
restart: unless-stopped
php-logging:
build:
context: ./php
dockerfile: Dockerfile
container_name: php
environment:
- PHP_ENV=production
restart: unless-stopped
loki:
image: grafana/loki:${GRAFANA_LOKI_VERSION:-3.6.10}
container_name: loki
ports:
- "3100:3100"
volumes:
- ./loki-config.yaml:/etc/loki/local-config.yaml
command: -config.file=/etc/loki/local-config.yaml
grafana:
image: grafana/grafana:${GRAFANA_VERSION:-13.0.1}
container_name: grafana
environment:
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
- GF_AUTH_ANONYMOUS_ENABLED=true
- GF_AUTH_BASIC_ENABLED=false
ports:
- 3000:3000/tcp
entrypoint:
- sh
- -euc
- |
mkdir -p /etc/grafana/provisioning/datasources
cat <<EOF > /etc/grafana/provisioning/datasources/ds.yaml
apiVersion: 1
datasources:
- name: Loki
type: loki
access: proxy
orgId: 1
url: http://loki:3100
basicAuth: false
isDefault: false
version: 1
editable: false
EOF
/run.sh
alloy:
image: grafana/alloy:${GRAFANA_ALLOY_VERSION:-v1.16.1}
container_name: alloy
ports:
- 12345:12345
- 4317:4317
- 4318:4318
volumes:
- ./alloy/:/etc/alloy/
- /var/run/docker.sock:/var/run/docker.sock
command: run --server.http.listen-addr=0.0.0.0:12345 --storage.path=/var/lib/alloy/data /etc/alloy/config.alloy
networks:
default:
name: logging-examples-network
================================================
FILE: app-instrumentation/logging/popular-logging-frameworks/go/Dockerfile
================================================
FROM golang:1.26-alpine@sha256:91eda9776261207ea25fd06b5b7fed8d397dd2c0a283e77f2ab6e91bfa71079d
WORKDIR /app
# Copy go.mod and go.sum for better caching
COPY go.mod go.sum ./
RUN go mod download
# Copy source code
COPY main.go .
RUN go build -o logging_example main.go
CMD ["./logging_example"]
================================================
FILE: app-instrumentation/logging/popular-logging-frameworks/go/go.mod
================================================
module logging-example
go 1.23
require go.uber.org/zap v1.28.0
require go.uber.org/multierr v1.10.0 // indirect
================================================
FILE: app-instrumentation/logging/popular-logging-frameworks/go/go.sum
================================================
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo=
go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: app-instrumentation/logging/popular-logging-frameworks/go/main.go
================================================
package main
import (
"errors"
"time"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func main() {
// Configure Zap logger for JSON output to stdout
config := zap.NewProductionConfig()
config.Level = zap.NewAtomicLevelAt(zap.DebugLevel)
config.OutputPaths = []string{"stdout"}
logger, err := config.Build()
if err != nil {
panic(err)
}
defer logger.Sync()
// Create child loggers for different components
appLogger := logger.Named("app")
dbLogger := logger.Named("database")
apiLogger := logger.Named("api")
counter := 0
appLogger.Info("Starting Go basic logging example with Zap")
appLogger.Info("Demonstrating Zap structured logging features")
// Infinite loop with different logging examples
for {
counter++
// Cycle through different logging examples
logType := counter % 12
switch logType {
case 0:
appLogger.Info("hello world")
case 1:
appLogger.Error("this is at error level")
case 2:
appLogger.Info("the answer is 42", zap.Int("answer", 42))
case 3:
appLogger.Info("hello world", zap.Int("obj", 42))
case 4:
appLogger.Info("hello world with counter",
zap.Int("obj", 42),
zap.Int("counter", counter))
case 5:
appLogger.Info("nested object",
zap.Object("nested", zapcore.ObjectMarshalerFunc(func(enc zapcore.ObjectEncoder) error {
enc.AddInt("obj", 42)
enc.AddTime("timestamp", time.Now())
return nil
})))
case 6:
appLogger.Error("simulated error", zap.Error(errors.New("kaboom")))
case 7:
appLogger.Info("hello from app component!")
case 8:
dbLogger.Warn("slow query detected",
zap.String("query", "SELECT * FROM users"),
zap.Duration("duration", 250*time.Millisecond))
case 9:
apiLogger.Info("API request completed",
zap.String("method", "GET"),
zap.String("path", "/api/users"),
zap.Int("status", 200))
case 10:
tempChild := appLogger.With(zap.String("requestId", "req-"+string(rune(counter))))
tempChild.Debug("this is a debug statement via child")
case 11:
appLogger.Error("error with additional context",
zap.Error(errors.New("kaboom")),
zap.String("context1", "additional"),
zap.String("context2", "information"))
}
// Occasionally demonstrate sugar logger
if counter%15 == 0 {
sugar := logger.Sugar()
sugar.Infow("using sugar logger",
"counter", counter,
"feature", "sugar")
}
// Occasionally demonstrate different log levels
if counter%20 == 0 {
appLogger.Debug("this is a debug message", zap.Int("counter", counter))
appLogger.Warn("this is a warning message", zap.Int("counter", counter))
}
// Wait 1 second before next log
time.Sleep(1 * time.Second)
}
}
================================================
FILE: app-instrumentation/logging/popular-logging-frameworks/java/App.java
================================================
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
public class App {
private static final Logger logger = LoggerFactory.getLogger(App.class);
private static final Logger appLogger = LoggerFactory.getLogger("app");
private static final Logger dbLogger = LoggerFactory.getLogger("database");
private static final Logger apiLogger = LoggerFactory.getLogger("api");
public static void main(String[] args) {
int counter = 0;
logger.info("Starting Java basic logging example with SLF4J + Logback");
logger.info("Demonstrating SLF4J structured logging features");
// Infinite loop with different logging examples
while (true) {
counter++;
// Cycle through different logging examples
int logType = counter % 12;
switch (logType) {
case 0:
logger.info("hello world");
break;
case 1:
logger.error("this is at error level");
break;
case 2:
logger.info("the answer is {}", 42);
break;
case 3:
logger.info("hello world with obj {}", 42);
break;
case 4:
logger.info("hello world with counter {} and obj {}", counter, 42);
break;
case 5:
logger.info("nested object with timestamp {} and value {}",
java.time.LocalDateTime.now(), 42);
break;
case 6:
Exception simulatedError = new RuntimeException("kaboom");
logger.error("simulated error", simulatedError);
break;
case 7:
appLogger.info("hello from app component!");
break;
case 8:
dbLogger.warn("slow query detected: {} took {}ms",
"SELECT * FROM users", 250);
break;
case 9:
apiLogger.info("API request completed: {} {} status={}",
"GET", "/api/users", 200);
break;
case 10:
// Using MDC (Mapped Diagnostic Context) for contextual logging
MDC.put("requestId", "req-" + counter);
logger.debug("this is a debug statement with MDC context");
MDC.clear();
break;
case 11:
Exception error = new RuntimeException("kaboom");
logger.error("error with additional context: {} {}",
"additional", "information", error);
break;
}
// Occasionally demonstrate different log levels
if (counter % 15 == 0) {
logger.debug("this is a debug message with counter {}", counter);
logger.warn("this is a warning message with counter {}", counter);
}
// Occasionally demonstrate MDC usage
if (counter % 20 == 0) {
MDC.put("userId", "user123");
MDC.put("sessionId", "session456");
logger.info("using MDC for contextual logging");
MDC.clear();
}
// Wait 1 second before next log
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.warn("Thread interrupted: {}", e.getMessage());
break;
}
}
}
}
================================================
FILE: app-instrumentation/logging/popular-logging-frameworks/java/Dockerfile
================================================
FROM openjdk:26-slim@sha256:63814a9d8bbea6d39d5ce9c91843bec5e9d9d1d1bc2bade4bb57ba70c0839553
WORKDIR /app
# Download SLF4J API, Logback dependencies, and Jackson for JSON encoding
RUN apt-get update && apt-get install -y wget && \
wget https://repo1.maven.org/maven2/org/slf4j/slf4j-api/2.0.9/slf4j-api-2.0.9.jar && \
wget https://repo1.maven.org/maven2/ch/qos/logback/logback-classic/1.4.14/logback-classic-1.4.14.jar && \
wget https://repo1.maven.org/maven2/ch/qos/logback/logback-core/1.4.14/logback-core-1.4.14.jar && \
wget https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.16.1/jackson-core-2.16.1.jar && \
wget https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.16.1/jackson-databind-2.16.1.jar && \
wget https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.16.1/jackson-annotations-2.16.1.jar && \
apt-get clean && rm -rf /var/lib/apt/lists/*
COPY App.java .
COPY logback.xml .
RUN javac -cp "slf4j-api-2.0.9.jar:logback-classic-1.4.14.jar:logback-core-1.4.14.jar:jackson-core-2.16.1.jar:jackson-databind-2.16.1.jar:jackson-annotations-2.16.1.jar" App.java
CMD ["java", "-cp", ".:slf4j-api-2.0.9.jar:logback-classic-1.4.14.jar:logback-core-1.4.14.jar:jackson-core-2.16.1.jar:jackson-databind-2.16.1.jar:jackson-annotations-2.16.1.jar", "App"]
================================================
FILE: app-instrumentation/logging/popular-logging-frameworks/java/logback.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- Console appender with standard format -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd'T'HH:mm:ss.SSSZ}[%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- Root logger configuration -->
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
<!-- Specific logger configurations -->
<logger name="app" level="DEBUG" />
<logger name="database" level="DEBUG" />
<logger name="api" level="DEBUG" />
</configuration>
================================================
FILE: app-instrumentation/logging/popular-logging-frameworks/javascript/Dockerfile
================================================
FROM node:24-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f
WORKDIR /app
# Create package.json and install pino with pino-pretty for better output formatting
RUN echo '{"name": "logging-example", "version": "1.0.0", "dependencies": {"pino": "^8.17.2", "pino-pretty": "^10.3.1"}}' > package.json
RUN npm install
COPY app.js .
RUN chmod +x app.js
CMD ["node", "app.js"]
================================================
FILE: app-instrumentation/logging/popular-logging-frameworks/javascript/app.js
================================================
#!/usr/bin/env node
// Pino's primary usage writes ndjson to `stdout`:
const pino = require('pino')()
// However, if "human readable" output is desired,
// `pino-pretty` can be provided as the destination
// stream by uncommenting the following line in place
// of the previous declaration:
// const pino = require('pino')(require('pino-pretty')())
let counter = 0;
pino.info('Starting JavaScript basic logging example with Pino');
pino.info('Demonstrating various Pino logging features');
// Create child loggers with different contexts
const appLogger = pino.child({ component: 'app' });
const dbLogger = pino.child({ component: 'database' });
const apiLogger = pino.child({ component: 'api', version: '1.0' });
// Function to demonstrate various logging features
function logMessage() {
counter++;
// Cycle through different logging examples
const logType = counter % 12;
switch (logType) {
case 0:
pino.info('hello world');
break;
case 1:
pino.error('this is at error level');
break;
case 2:
pino.info('the answer is %d', 42);
break;
case 3:
pino.info({ obj: 42 }, 'hello world');
break;
case 4:
pino.info({ obj: 42, counter: counter }, 'hello world with counter');
break;
case 5:
pino.info({ nested: { obj: 42, timestamp: new Date() } }, 'nested object');
break;
case 6:
pino.error(new Error('simulated error'));
break;
case 7:
appLogger.info('hello from app component!');
break;
case 8:
dbLogger.warn({ query: 'SELECT * FROM users', duration: 250 }, 'slow query detected');
break;
case 9:
apiLogger.info({ method: 'GET', path: '/api/users', status: 200 }, 'API request completed');
break;
case 10:
const tempChild = pino.child({ requestId: `req-${counter}` });
tempChild.debug('this is a debug statement via child');
break;
case 11:
pino.info(new Error('kaboom'), 'with', 'additional', 'context');
break;
}
// Occasionally demonstrate level changes
if (counter % 20 === 0) {
pino.level = 'debug';
pino.debug('switched to debug level - this should now be visible');
setTimeout(() => {
pino.level = 'info';
pino.info('switched back to info level');
}, 500);
}
// Occasionally demonstrate trace level
if (counter % 25 === 0) {
const originalLevel = pino.level;
pino.level = 'trace';
pino.trace('this is a trace statement');
pino.level = originalLevel;
}
}
// Log every 1 second infinitely
setInterval(logMessage, 1000);
================================================
FILE: app-instrumentation/logging/popular-logging-frameworks/loki-config.yaml
================================================
# This is a complete configuration to deploy Loki backed by the filesystem.
# The index will be shipped to the storage via tsdb-shipper.
auth_enabled: false
limits_config:
allow_structured_metadata: true
volume_enabled: true
server:
http_listen_port: 3100
common:
ring:
instance_addr: 0.0.0.0
kvstore:
store: inmemory
replication_factor: 1
path_prefix: /tmp/loki
schema_config:
configs:
- from: 2020-05-15
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
storage_config:
tsdb_shipper:
active_index_directory: /tmp/loki/index
cache_location: /tmp/loki/index_cache
filesystem:
directory: /tmp/loki/chunks
pattern_ingester:
enabled: true
================================================
FILE: app-instrumentation/logging/popular-logging-frameworks/php/Dockerfile
================================================
FROM php:8.5-cli-alpine@sha256:6ca76906d789edfac74e5f109c800b71e571bd313277133eaddc079733ee0b65
WORKDIR /app
# Install Composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
# Create composer.json for Monolog
RUN echo '{"require": {"monolog/monolog": "^3.5"}}' > composer.json
# Install dependencies
RUN composer install --no-dev --optimize-autoloader
COPY app.php .
CMD ["php", "app.php"]
================================================
FILE: app-instrumentation/logging/popular-logging-frameworks/php/app.php
================================================
<?php
require_once 'vendor/autoload.php';
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Exception;
// Create the main logger
$logger = new Logger('app');
// Create a console handler that writes to stdout
$consoleHandler = new StreamHandler('php://stdout', Logger::DEBUG);
// Push the handler onto the logger
$logger->pushHandler($consoleHandler);
// Add a processor to inject an 'environment' extra field into every log entry
$logger->pushProcessor(function ($record) {
$record['extra']['environment'] = 'production'; // You can set any value or use getenv() etc.
return $record;
});
// Create component-specific loggers if you want
$appLogger = $logger->withName('app');
$dbLogger = $logger->withName('database');
$apiLogger = $logger->withName('api');
$counter = 0;
$logger->info("Starting PHP basic logging example with Monolog");
$logger->info("Demonstrating Monolog structured logging features");
while (true) {
$counter++;
$logType = $counter % 6;
switch ($logType) {
case 0:
$logger->info("hello world");
break;
case 1:
$logger->error("this is at error level");
break;
case 2:
$logger->info("hello world with counter", [
'counter' => $counter,
'obj' => 42
]);
break;
case 3:
$dbLogger->warning("slow query detected", [
'query' => 'SELECT * FROM users',
'duration' => 250
]);
break;
case 4:
$apiLogger->info("API request completed", [
'method' => 'GET',
'path' => '/api/users',
'status' => 200
]);
break;
case 5:
// Fatal error with stack trace
$fatalException = new Exception("Critical system failure - database connection lost");
$logger->emergency("System encountered a fatal error", [
'exception' => $fatalException,
'error_code' => 'DB_CONNECTION_LOST',
'affected_service' => 'user_authentication'
]);
break;
}
// Wait 1 second before next log
sleep(1);
}
================================================
FILE: app-instrumentation/logging/popular-logging-frameworks/python/Dockerfile
================================================
FROM python:3.12-slim@sha256:46cb7cc2877e60fbd5e21a9ae6115c30ace7a077b9f8772da879e4590c18c2e3
WORKDIR /app
COPY app.py .
RUN chmod +x app.py
CMD ["python", "app.py"]
================================================
FILE: app-instrumentation/logging/popular-logging-frameworks/python/app.py
================================================
#!/usr/bin/env python3
import logging
import time
# Configure logging
logging.basicConfig(
level=logging.DEBUG,
format= '%(asctime)s - %(filename)s:%(lineno)d - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(),
]
)
logger = logging.getLogger(__name__)
def main():
counter = 0
logger.info("Starting Python basic logging example")
logger.info("Demonstrating Python logging module")
# Infinite loop with different log levels
while True:
counter += 1
# Cycle through different log levels
log_type = counter % 5
if log_type == 0:
logger.debug(f"Basic debug message, counter: {counter}")
elif log_type == 1:
logger.info(f"Information message, counter: {counter}")
elif log_type == 2:
logger.warning(f"Warning message, counter: {counter}")
elif log_type == 3:
logger.error(f"Error message, counter: {counter}")
elif log_type == 4:
logger.critical(f"Critical message, counter: {counter}")
# Wait 1 second before next log
time.sleep(1)
if __name__ == "__main__":
main()
================================================
FILE: aws-firehose-logs/README.md
================================================
# AWS Kinesis Data Firehose to Loki — no AWS account required
Demonstrates `loki.source.awsfirehose`, the HTTP receiver that accepts AWS Kinesis Data Firehose's documented delivery format. **You don't need an AWS account or any AWS SDKs** — Firehose is just an HTTPS POST in a known JSON shape, and this scenario emulates the producer with a small Python container.
This is the same producer-emulator pattern used by [`syslog/`](../syslog/) and [`gelf-log-ingestion/`](../gelf-log-ingestion/).
## Architecture
- **`alloy`** runs `loki.source.awsfirehose` on port `:9999`, listening at `/awsfirehose/api/v1/push`
- **`firehose-sender`** (Python) generates synthetic CloudWatch-style log batches every 5 seconds and POSTs them to Alloy in the documented Firehose delivery format (records array with gzip-compressed, base64-encoded data fields)
- **`loki`** + **`grafana`** for storage and visualization, with the Loki datasource auto-provisioned
The sender alternates between three log streams:
1. VPC flow logs on `eni-0abc1234-all` (channel `/aws/vpc/flowlogs`)
2. VPC flow logs on `eni-0def5678-all` (same channel, different stream)
3. Lambda invocation logs on `[$LATEST]abc` (channel `/aws/lambda/checkout-service`)
## Running
```bash
# From this directory
docker compose up -d
# Or from the repo root
./run-example.sh aws-firehose-logs
```
## Accessing
- **Grafana**: http://localhost:3000 (no login)
- **Alloy UI**: http://localhost:12345 — confirm components healthy, use livedebugging to watch records flow through
- **Firehose endpoint**: http://localhost:9999/awsfirehose/api/v1/push (POSTable from your laptop)
- **Loki API**: http://localhost:3100
## Trying it out
Within ~10 seconds of bring-up, the sender starts producing batches. In Grafana Explore on Loki:
```logql
# All Firehose-delivered logs
{log_group=~".+"}
# Just VPC flow logs
{log_group="/aws/vpc/flowlogs"}
# A specific ENI
{log_group="/aws/vpc/flowlogs", log_stream="eni-0abc1234-all"}
# Lambda invocations
{log_group="/aws/lambda/checkout-service"}
# Just the data records (vs control messages)
{msg_type="DATA_MESSAGE"}
```
The promoted labels `log_group`, `log_stream`, and `msg_type` come from the CloudWatch envelope — `loki.source.awsfirehose` automatically attaches `__aws_cw_log_group`, `__aws_cw_log_stream`, and `__aws_cw_msg_type` discovery labels when the records contain a CloudWatch subscription filter envelope; this scenario's `loki.relabel` block promotes them.
## Send your own records
The receiver is just an HTTP endpoint. From your laptop:
```bash
curl -X POST http://localhost:9999/awsfirehose/api/v1/push \
-H 'Content-Type: application/json' \
-d '{
"requestId": "test-1",
"timestamp": 1234567890,
"records": [
{"data": "'$(printf '{"messageType":"DATA_MESSAGE","logGroup":"/manual","logStream":"laptop","logEvents":[{"id":"x","timestamp":1234567890000,"message":"hi from curl"}]}' | gzip | base64)'"}
]
}'
```
This adds a one-off entry visible at `{log_group="/manual"}`.
## Differences from real Firehose
This scenario emulates the wire format. A real Firehose delivery stream has a few additional concerns the demo doesn't cover:
- **Authentication**: real Firehose includes an `X-Amz-Firehose-Access-Key` header that the receiver validates. `loki.source.awsfirehose` supports this via the `access_key` argument; we leave it disabled in the demo for ease of trying it from curl. In production, **always** set an access key.
- **TLS**: real Firehose requires HTTPS. Add `tls { cert_file = ..., key_file = ... }` to the Alloy `http` block in production.
- **Retry semantics**: real Firehose retries on 5xx and partial successes. The Python sender here just logs failures and moves on.
- **Custom labels via header**: real Firehose can set `X-Amz-Firehose-Common-Attributes` (label names prefixed `lbl_`). Try adding this to your own producer to see additional discovery labels appear.
## Stopping
```bash
docker compose down -v
```
================================================
FILE: aws-firehose-logs/config.alloy
================================================
// AWS Kinesis Data Firehose → Loki, no AWS account required.
//
// `loki.source.awsfirehose` is just an HTTP endpoint that accepts
// Firehose's documented delivery format (a `records` array of base64
// blobs). A small Python sender container in this scenario fakes the
// producer side, posting CloudWatch-style log batches every few
// seconds. The component auto-detects the CloudWatch envelope and
// attaches the `__aws_cw_*` discovery labels we relabel below.
livedebugging { enabled = true }
// CloudWatch envelope discovery labels are exposed by
// `loki.source.awsfirehose` only via its `relabel_rules` argument
// (same pattern as `loki.source.journal`). They are NOT attached to
// outgoing entries by default — running them through a standalone
// `loki.relabel` after the source would see no `__aws_cw_*` labels.
loki.relabel "firehose" {
forward_to = []
rule {
source_labels = ["__aws_cw_log_group"]
target_label = "log_group"
}
rule {
source_labels = ["__aws_cw_log_stream"]
target_label = "log_stream"
}
rule {
source_labels = ["__aws_cw_msg_type"]
target_label = "msg_type"
}
}
loki.source.awsfirehose "fake" {
http {
listen_address = "0.0.0.0"
listen_port = 9999
}
relabel_rules = loki.relabel.firehose.rules
forward_to = [loki.write.local.receiver]
}
loki.write "local" {
endpoint {
url = "http://loki:3100/loki/api/v1/push"
}
}
================================================
FILE: aws-firehose-logs/docker-compose.yml
================================================
services:
loki:
image: grafana/loki:${GRAFANA_LOKI_VERSION:-3.6.10}
ports:
- "3100:3100/tcp"
volumes:
- ./loki-config.yaml:/etc/loki/local-config.yaml
command: -config.file=/etc/loki/local-config.yaml
grafana:
image: grafana/grafana:${GRAFANA_VERSION:-13.0.1}
environment:
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
- GF_AUTH_ANONYMOUS_ENABLED=true
- GF_AUTH_BASIC_ENABLED=false
ports:
- "3000:3000/tcp"
entrypoint:
- sh
- -euc
- |
mkdir -p /etc/grafana/provisioning/datasources
cat <<EOF > /etc/grafana/provisioning/datasources/ds.yaml
apiVersion: 1
datasources:
- name: Loki
type: loki
access: proxy
orgId: 1
url: http://loki:3100
basicAuth: false
isDefault: true
version: 1
editable: false
EOF
/run.sh
alloy:
image: grafana/alloy:${GRAFANA_ALLOY_VERSION:-v1.16.1}
ports:
- "12345:12345"
- "9999:9999"
volumes:
- ./config.alloy:/etc/alloy/config.alloy
command: run --server.http.listen-addr=0.0.0.0:12345 --storage.path=/var/lib/alloy/data /etc/alloy/config.alloy
depends_on:
- loki
firehose-sender:
image: python:${PYTHON_VERSION:-3.11-slim}
volumes:
- ./firehose_sender.py:/firehose_sender.py:ro
environment:
- ALLOY_FIREHOSE_URL=http://alloy:9999/awsfirehose/api/v1/push
- INTERVAL_SECONDS=5
- EVENTS_PER_BATCH=8
depends_on:
- alloy
command: ["python3", "-u", "/firehose_sender.py"]
restart: unless-stopped
================================================
FILE: aws-firehose-logs/firehose_sender.py
================================================
"""Fake AWS Kinesis Firehose producer for the aws-firehose-logs scenario.
Generates synthetic VPC-flow-style log batches, wraps them in the
CloudWatch logs subscription envelope (so Alloy attaches the
`__aws_cw_*` discovery labels), then posts them to Alloy's
`loki.source.awsfirehose` HTTP endpoint in the documented Firehose
delivery format.
No AWS account or SDK required — this is just an HTTP client.
"""
import base64
import gzip
import json
import os
import random
import sys
import time
import uuid
from datetime import datetime
from urllib import request as urlrequest
ENDPOINT = os.environ.get(
"ALLOY_FIREHOSE_URL",
"http://alloy:9999/awsfirehose/api/v1/push",
)
INTERVAL = float(os.environ.get("INTERVAL_SECONDS", "5"))
EVENTS_PER_BATCH = int(os.environ.get("EVENTS_PER_BATCH", "8"))
LOG_GROUPS = [
("/aws/vpc/flowlogs", "eni-0abc1234-all"),
("/aws/vpc/flowlogs", "eni-0def5678-all"),
("/aws/lambda/checkout-service", "2026/04/28/[$LATEST]abc"),
]
ACTIONS = ["ACCEPT", "REJECT"]
def vpc_flow_line() -> str:
src = f"10.0.{random.randint(0,255)}.{random.randint(1,254)}"
dst = f"10.0.{random.randint(0,255)}.{random.randint(1,254)}"
bytes_ = random.randint(40, 65000)
pkts = random.randint(1, 50)
action = random.choices(ACTIONS, weights=[9, 1])[0]
now = int(time.time())
return f"2 123456789012 eni-0abc1234 {src} {dst} 12345 443 6 {pkts} {bytes_} {now-30} {now} {action} OK"
def lambda_log_line() -> str:
levels = ["INFO", "INFO", "INFO", "WARN", "ERROR"]
level = random.choice(levels)
request_id = str(uuid.uuid4())
return f"{datetime.utcnow().isoformat()}Z {level} RequestId: {request_id} processing checkout"
def cloudwatch_envelope(log_group: str, log_stream: str, line_fn) -> dict:
"""Build a CloudWatch logs subscription delivery envelope.
See: https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/SubscriptionFilters.html
"""
return {
"messageType": "DATA_MESSAGE",
"owner": "123456789012",
"logGroup": log_group,
"logStream": log_stream,
"subscriptionFilters": ["AlloyDemo"],
"logEvents": [
{
"id": str(uuid.uuid4()),
"timestamp": int(time.time() * 1000),
"message": line_fn(),
}
for _ in range(EVENTS_PER_BATCH)
],
}
def encode_record(envelope: dict) -> dict:
"""CloudWatch subscription delivery is gzip-compressed JSON, then
base64-encoded inside the Firehose record `data` field. See:
https://docs.aws.amazon.com/firehose/latest/dev/httpdeliveryrequestresponse.html
"""
raw = json.dumps(envelope).encode()
compressed = gzip.compress(raw)
return {"data": base64.b64encode(compressed).decode()}
def send_batch() -> None:
log_group, log_stream = random.choice(LOG_GROUPS)
line_fn = lambda_log_line if "lambda" in log_group else vpc_flow_line
envelope = cloudwatch_envelope(log_group, log_stream, line_fn)
body = {
"requestId": str(uuid.uuid4()),
"timestamp": int(time.time() * 1000),
"records": [encode_record(envelope)],
}
req = urlrequest.Request(
ENDPOINT,
data=json.dumps(body).encode(),
headers={
"Content-Type": "application/json",
"X-Amz-Firehose-Request-Id": body["requestId"],
},
)
try:
with urlrequest.urlopen(req, timeout=5) as resp:
print(f"POST {log_group}/{log_stream}: {resp.status}", flush=True)
except Exception as e:
print(f"POST {log_group}/{log_stream}: FAILED {e}", flush=True)
def main() -> int:
# Wait briefly so Alloy's HTTP listener is up before the first POST.
time.sleep(3)
while True:
send_batch()
time.sleep(INTERVAL)
if __name__ == "__main__":
sys.exit(main() or 0)
================================================
FILE: aws-firehose-logs/loki-config.yaml
================================================
auth_enabled: false
limits_config:
allow_structured_metadata: true
volume_enabled: true
server:
http_listen_port: 3100
common:
ring:
instance_addr: 0.0.0.0
kvstore:
store: inmemory
replication_factor: 1
path_prefix: /tmp/loki
schema_config:
configs:
- from: 2020-05-15
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
storage_config:
tsdb_shipper:
active_index_directory: /tmp/loki/index
cache_location: /tmp/loki/index_cache
filesystem:
directory: /tmp/loki/chunks
pattern_ingester:
enabled: true
ingester:
max_chunk_age: 5m
================================================
FILE: blackbox-probing/README.md
================================================
# Blackbox Probing
This scenario demonstrates **synthetic monitoring** and **HTTP endpoint probing** using Grafana Alloy's `prometheus.exporter.blackbox` component.
## Overview
Blackbox probing (also known as synthetic monitoring) tests the availability and responsiveness of services from an external perspective. Instead of instrumenting applications to export metrics, the blackbox exporter actively probes endpoints and reports whether they are reachable, how long they take to respond, and other HTTP-level details.
This scenario probes two targets:
- **nginx** — a simple web server running on port 80
- **prometheus** — the Prometheus server running on port 9090
## Architecture
```
Alloy (blackbox exporter) --probes--> nginx:80
--probes--> prometheus:9090
--writes--> Prometheus (remote write)
Grafana --queries--> Prometheus
```
## Running
```bash
# From this directory
docker compose up -d
# Or from the repo root
./run-example.sh blackbox-probing
```
## Accessing the Stack
| Service | URL |
|------------|----------------------------|
| Grafana | http://localhost:3000 |
| Alloy UI | http://localhost:12345 |
| Prometheus | http://localhost:9090 |
| nginx | http://localhost:8080 |
## Key Metrics
Once running, you can query these metrics in Grafana or Prometheus:
- `probe_success` — 1 if the probe succeeded, 0 if it failed
- `probe_duration_seconds` — total time the probe took
- `probe_http_status_code` — HTTP status code returned by the target
- `probe_http_duration_seconds` — duration of each phase of the HTTP request (resolve, connect, tls, processing, transfer)
## Stopping
```bash
docker compose down
```
================================================
FILE: blackbox-probing/config.alloy
================================================
// --- Remote Write to Prometheus ---
prometheus.remote_write "remote" {
endpoint {
url = "http://prometheus:9090/api/v1/write"
}
}
// --- Blackbox Exporter Configuration ---
prometheus.exporter.blackbox "default" {
config = "{ modules: { http_2xx: { prober: http, timeout: 5s } } }"
target {
name = "nginx"
address = "http://nginx:80"
module = "http_2xx"
}
target {
name = "prometheus"
address = "http://prometheus:9090"
module = "http_2xx"
}
}
// --- Blackbox Scrape Configuration ---
prometheus.scrape "blackbox_targets" {
scrape_interval = "15s"
targets = prometheus.exporter.blackbox.default.targets
forward_to = [prometheus.remote_write.remote.receiver]
}
// --- Enable Live Debugging ---
livedebugging {
enabled = true
}
================================================
FILE: blackbox-probing/docker-compose.coda.yml
================================================
services:
nginx:
image: nginx:latest@sha256:1881968aff6f7cdcc4b888c00a11f4ce241ad7ec957e0cb4a9e19e93a3ff87ea
ports:
- 8080:80/tcp
================================================
FILE: blackbox-probing/docker-compose.yml
================================================
services:
nginx:
image: nginx:latest@sha256:1881968aff6f7cdcc4b888c00a11f4ce241ad7ec957e0cb4a9e19e93a3ff87ea
ports:
- 8080:80/tcp
prometheus:
image: prom/prometheus:${PROMETHEUS_VERSION:-v3.11.3}
command:
- --web.enable-remote-write-receiver
- --config.file=/etc/prometheus/prometheus.yml
ports:
- 9090:9090/tcp
volumes:
- ./prom-config.yaml:/etc/prometheus/prometheus.yml
grafana:
image: grafana/grafana:${GRAFANA_VERSION:-13.0.1}
environment:
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
- GF_AUTH_ANONYMOUS_ENABLED=true
- GF_AUTH_BASIC_ENABLED=false
ports:
- 3000:3000/tcp
entrypoint:
- sh
- -euc
- |
mkdir -p /etc/grafana/provisioning/datasources
cat <<EOF > /etc/grafana/provisioning/datasources/ds.yaml
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
orgId: 1
url: http://prometheus:9090
basicAuth: false
isDefault: true
version: 1
editable: false
EOF
/run.sh
alloy:
image: grafana/alloy:${GRAFANA_ALLOY_VERSION:-v1.16.1}
ports:
- 12345:12345
volumes:
- ./config.alloy:/etc/alloy/config.alloy
command: run --server.http.listen-addr=0.0.0.0:12345 --storage.path=/var/lib/alloy/data /etc/alloy/config.alloy
================================================
FILE: blackbox-probing/prom-config.yaml
================================================
# Minimal Prometheus configuration
global:
scrape_interval: 15s
evaluation_interval: 15s
================================================
FILE: cloudwatch-metrics/README.md
================================================
# AWS CloudWatch metrics — no AWS account required
Demonstrates `prometheus.exporter.cloudwatch`, Alloy's built-in wrapper around [YACE](https://github.com/nerdswords/yet-another-cloudwatch-exporter). **No real AWS account or live infrastructure needed** — [LocalStack](https://localstack.cloud/) emulates the CloudWatch and STS APIs locally, and a small Python seeder container plants synthetic `EC2/CPUUtilization` data points every 30 s.
This is the same offline-reproducibility pattern used by [`aws-firehose-logs/`](../aws-firehose-logs/).
## Architecture
```
metric-seeder (Python)
└── put_metric_data → LocalStack CloudWatch (:4566)
↑
Alloy prometheus.exporter.cloudwatch
↓
prometheus.scrape → prometheus.remote_write
↓
Prometheus (:9090)
↑
Grafana (:3000)
```
- **`localstack`** — emulates `cloudwatch` + `sts` APIs; no AWS credentials required
- **`metric-seeder`** — pushes `CPUUtilization` (random 5–85 %) for `i-1234567890abcdef0` every 30 s
- **`alloy`** — runs `prometheus.exporter.cloudwatch` pointed at LocalStack via `AWS_ENDPOINT_URL`; scrapes every 60 s and remote-writes to Prometheus
- **`prometheus`** — stores and serves metrics
- **`grafana`** — visualises with Prometheus datasource auto-provisioned
## Running
```bash
# From this directory
docker compose up -d
# Or from the repo root
./run-example.sh cloudwatch-metrics
```
LocalStack and the metric-seeder start first; Alloy waits for LocalStack to be healthy before scraping.
## Accessing
| Service | URL |
|---|---|
| **Grafana** | http://localhost:3000 (no login) |
| **Prometheus** | http://localhost:9090 |
| **Alloy UI** | http://localhost:12345 |
| **LocalStack** | http://localhost:4566/_localstack/health |
## Trying it out
Within ~90 s of bring-up (LocalStack ready → seeder plants first points → Alloy scrapes → Prometheus ingests), metrics appear in Prometheus.
Open **Grafana → Explore → Prometheus** and run:
```promql
# CPU utilisation for the seeded EC2 instance
aws_ec2_cpuutilization_average
# Maximum CPU in the last 5 m
aws_ec2_cpuutilization_maximum
# All CloudWatch-sourced metrics
{job="cloudwatch/localstack/ec2_cpu"}
```
Or query Prometheus directly:
```bash
curl -sG 'http://localhost:9090/api/v1/query' \
--data-urlencode 'query=aws_ec2_cpuutilization_average' | jq .
```
In the **Alloy UI** (http://localhost:12345), navigate to **Graph** to see the pipeline:
`prometheus.exporter.cloudwatch.localstack` → `prometheus.scrape.cloudwatch` → `prometheus.remote_write.local`
Use **livedebugging** on `prometheus.scrape.cloudwatch` to watch metrics flow through in real time.
## Adapting for real AWS
To point this scenario at real CloudWatch instead of LocalStack:
1. Remove the `localstack` and `metric-seeder` services from `docker-compose.yml`
2. Remove the `AWS_ENDPOINT_URL` environment variable from the `alloy` service
3. Set real credentials:
```yaml
environment:
- AWS_ACCESS_KEY_ID=<your-key>
- AWS_SECRET_ACCESS_KEY=<your-secret>
- AWS_DEFAULT_REGION=us-east-1
```
4. Update the `dimensions` in `config.alloy` to match a real `InstanceId` in your account
The `config.alloy` static job configuration and Alloy pipeline are identical for both LocalStack and real AWS.
================================================
FILE: cloudwatch-metrics/config.alloy
================================================
// AWS CloudWatch metrics → Prometheus — no AWS account required.
//
// Uses LocalStack to emulate CloudWatch locally. A companion `metric-seeder`
// container pushes synthetic EC2/CPUUtilization data points every 30 s so
// Alloy has real data to scrape immediately on start-up.
//
// `prometheus.exporter.cloudwatch` wraps YACE and honours AWS SDK v2 endpoint
// overrides; we point it at LocalStack via AWS_ENDPOINT_URL in docker-compose.
livedebugging { enabled = true }
// Static job: no live EC2 discovery needed — we target the exact InstanceId
// that the metric-seeder plants in LocalStack CloudWatch.
prometheus.exporter.cloudwatch "localstack" {
sts_region = "us-east-1"
static "ec2_cpu" {
regions = ["us-east-1"]
namespace = "AWS/EC2"
dimensions = {
"InstanceId" = "i-1234567890abcdef0",
}
metric {
name = "CPUUtilization"
statistics = ["Average", "Maximum"]
period = "1m"
}
}
}
// Scrape the exporter every 60 s — CloudWatch data points are coarse-grained
// so there is no benefit in scraping more frequently.
prometheus.scrape "cloudwatch" {
targets = prometheus.exporter.cloudwatch.localstack.targets
forward_to = [prometheus.remote_write.local.receiver]
scrape_interval = "60s"
}
// Remote-write to the local Prometheus instance.
prometheus.remote_write "local" {
endpoint {
url = "http://prometheus:9090/api/v1/write"
}
}
================================================
FILE: cloudwatch-metrics/docker-compose.yml
================================================
services:
# LocalStack emulates the CloudWatch + STS APIs locally.
# No real AWS account or credentials needed.
localstack:
image: localstack/localstack:${LOCALSTACK_VERSION:-4.4.0}
ports:
- "4566:4566"
environment:
- SERVICES=cloudwatch,sts
- DEFAULT_REGION=us-east-1
healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:4566/_localstack/health"]
interval: 5s
timeout: 5s
retries: 15
# Pushes synthetic EC2/CPUUtilization data into LocalStack every 30 s.
metric-seeder:
image: python:${PYTHON_VERSION:-3.11-slim}
volumes:
- ./seed-metrics.py:/seed-metrics.py:ro
environment:
- AWS_ACCESS_KEY_ID=test
- AWS_SECRET_ACCESS_KEY=test
- AWS_DEFAULT_REGION=us-east-1
- AWS_ENDPOINT_URL=http://localstack:4566
- INTERVAL_SECONDS=30
command: >
sh -c "pip install boto3 --quiet && python -u /seed-metrics.py"
depends_on:
localstack:
condition: service_healthy
restart: unless-stopped
prometheus:
image: prom/prometheus:${PROMETHEUS_VERSION:-v3.11.3}
command:
- --web.enable-remote-write-receiver
- --config.file=/etc/prometheus/prometheus.yml
volumes:
- ./prom-config.yaml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
grafana:
image: grafana/grafana:${GRAFANA_VERSION:-13.0.1}
environment:
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
- GF_AUTH_ANONYMOUS_ENABLED=true
- GF_AUTH_BASIC_ENABLED=false
ports:
- "3000:3000/tcp"
entrypoint:
- sh
- -euc
- |
mkdir -p /etc/grafana/provisioning/datasources
cat <<EOF > /etc/grafana/provisioning/datasources/ds.yaml
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
orgId: 1
url: http://prometheus:9090
basicAuth: false
isDefault: true
version: 1
editable: false
EOF
/run.sh
depends_on:
- prometheus
alloy:
image: grafana/alloy:${GRAFANA_ALLOY_VERSION:-v1.16.1}
ports:
- "12345:12345"
volumes:
- ./config.alloy:/etc/alloy/config.alloy
environment:
# Point AWS SDK v2 at LocalStack instead of real AWS endpoints.
- AWS_ACCESS_KEY_ID=test
- AWS_SECRET_ACCESS_KEY=test
- AWS_DEFAULT_REGION=us-east-1
- AWS_ENDPOINT_URL=http://localstack:4566
command: run --server.http.listen-addr=0.0.0.0:12345 --storage.path=/var/lib/alloy/data /etc/alloy/config.alloy
depends_on:
localstack:
condition: service_healthy
prometheus:
condition: service_started
================================================
FILE: cloudwatch-metrics/prom-config.yaml
================================================
global:
scrape_interval: 15s
evaluation_interval: 15s
================================================
FILE: cloudwatch-metrics/seed-metrics.py
================================================
"""
CloudWatch metric seeder for LocalStack.
Pushes synthetic EC2 CPUUtilization data points into LocalStack every
INTERVAL_SECONDS so that prometheus.exporter.cloudwatch has something
to scrape immediately without a real AWS account.
"""
import os
import random
import time
import boto3
from botocore.config import Config
ENDPOINT = os.getenv("AWS_ENDPOINT_URL", "http://localstack:4566")
REGION = os.getenv("AWS_DEFAULT_REGION", "us-east-1")
INTERVAL = int(os.getenv("INTERVAL_SECONDS", "30"))
INSTANCE_ID = "i-1234567890abcdef0"
cw = boto3.client(
"cloudwatch",
endpoint_url=ENDPOINT,
region_name=REGION,
aws_access_key_id=os.getenv("AWS_ACCESS_KEY_ID", "test"),
aws_secret_access_key=os.getenv("AWS_SECRET_ACCESS_KEY", "test"),
config=Config(retries={"max_attempts": 5}),
)
print(f"Seeder started — pushing to {ENDPOINT} every {INTERVAL}s", flush=True)
while True:
cpu = round(random.uniform(5.0, 85.0), 2)
cw.put_metric_data(
Namespace="AWS/EC2",
MetricData=[
{
"MetricName": "CPUUtilization",
"Dimensions": [{"Name": "InstanceId", "Value": INSTANCE_ID}],
"Value": cpu,
"Unit": "Percent",
}
],
)
print(f" → CPUUtilization={cpu}% instance={INSTANCE_ID}", flush=True)
time.sleep(INTERVAL)
================================================
FILE: coda
================================================
#!/usr/bin/env bash
set -euo pipefail
# On Coda VMs the repo lives at /opt/alloy-scenarios and this script is
# symlinked from /usr/local/bin/coda. For local dev use the script's own
# directory (works when invoked directly, not via symlink).
if [[ -d /opt/alloy-scenarios ]]; then
REPO_DIR="/opt/alloy-scenarios"
else
REPO_DIR="$(cd "$(dirname "$0")" && pwd)"
fi
ENV_FILE="${REPO_DIR}/image-versions.env"
SCENARIO_FILE="/etc/coda/scenario"
usage() {
cat <<EOF
Usage: coda <command> [scenario]
Commands:
start [scenario] Start app containers for a scenario
stop [scenario] Stop app containers for a scenario
status [scenario] Show container status for a scenario
list List all available scenarios
If no scenario is given, reads from ${SCENARIO_FILE}.
EOF
exit 1
}
resolve_scenario() {
local scenario="${1:-}"
if [[ -z "$scenario" ]]; then
if [[ -f "$SCENARIO_FILE" ]]; then
scenario="$(cat "$SCENARIO_FILE")"
else
echo "Error: no scenario specified and ${SCENARIO_FILE} not found" >&2
exit 1
fi
fi
echo "$scenario"
}
compose_args() {
local scenario="$1"
local dir="${REPO_DIR}/${scenario}"
local compose_file="${dir}/docker-compose.coda.yml"
if [[ ! -f "$compose_file" ]]; then
echo "Error: ${compose_file} not found" >&2
exit 1
fi
# Sanitize project name: replace / with -
local project_name="coda-${scenario//\//-}"
echo "-f ${compose_file} --env-file ${ENV_FILE} -p ${project_name}"
}
cmd_start() {
local scenario
scenario="$(resolve_scenario "${1:-}")"
local args
args="$(compose_args "$scenario")"
echo "Starting scenario: ${scenario}"
eval docker compose $args up -d --build
}
cmd_stop() {
local scenario
scenario="$(resolve_scenario "${1:-}")"
local args
args="$(compose_args "$scenario")"
echo "Stopping scenario: ${scenario}"
eval docker compose $args down
}
cmd_status() {
local scenario
scenario="$(resolve_scenario "${1:-}")"
local args
args="$(compose_args "$scenario")"
eval docker compose $args ps
}
cmd_list() {
echo "Available scenarios:"
find "$REPO_DIR" -name docker-compose.coda.yml 2>/dev/null \
| sed "s|^${REPO_DIR}/||; s|/docker-compose.coda.yml||" \
| sort \
| while read -r s; do echo " $s"; done
}
[[ $# -lt 1 ]] && usage
command="$1"
shift
case "$command" in
start) cmd_start "$@" ;;
stop) cmd_stop "$@" ;;
status) cmd_status "$@" ;;
list) cmd_list ;;
*) usage ;;
esac
================================================
FILE: continuous-profiling/README.md
================================================
# Continuous Profiling
This scenario demonstrates continuous profiling of a Go application using Grafana Alloy's `pyroscope.scrape` and `pyroscope.write` components, with Grafana Pyroscope as the profiling backend.
## Overview
The example includes:
- **demo-app** -- A Go application that performs CPU-intensive and memory-intensive work, exposing standard pprof endpoints on port 6060
- **alloy** -- Grafana Alloy configured to scrape pprof profiles from the demo app and forward them to Pyroscope
- **pyroscope** -- Grafana Pyroscope for storing and querying profiling data
- **grafana** -- Grafana with the Pyroscope datasource pre-configured for visualizing profiles
## Running the Demo
1. Clone the repository:
```
git clone https://github.com/grafana/alloy-scenarios.git
cd alloy-scenarios
```
2. Navigate to this example directory:
```
cd continuous-profiling
```
3. Run using Docker Compose:
```
docker compose up -d
```
Or use the centralized image management:
```
cd ..
./run-example.sh continuous-profiling
```
4. Access Grafana at http://localhost:3000
## What to Expect
After starting the scenario, Alloy will scrape the following profile types from the demo app every 15 seconds:
- **CPU** -- Identifies functions consuming the most CPU time (the `cpuIntensive` goroutine)
- **Memory (heap)** -- Shows memory allocation patterns (the `memoryIntensive` goroutine allocating 1MB chunks)
- **Goroutine** -- Displays active goroutines and their stack traces
- **Mutex** -- Captures mutex contention profiles
- **Block** -- Captures blocking operation profiles
To view profiles:
1. Open Grafana at http://localhost:3000
2. Navigate to **Explore**
3. Select the **Pyroscope** datasource
4. Choose a profile type (e.g., `process_cpu`) and the `demo-app` service
5. You should see flame graphs showing where the application spends its time and allocates memory
## Architecture
```
┌───────────┐ scrape pprof ┌───────────┐ push profiles ┌────────────┐
│ demo-app │◀─────────────────────│ Alloy │─────────────────────▶│ Pyroscope │
│ :6060 │ /debug/pprof/* │ :12345 │ │ :4040 │
└───────────┘ └───────────┘ └─────┬──────┘
│
▼
┌──────────┐
│ Grafana │
│ :3000 │
└──────────┘
```
## Useful Links
- Alloy UI: http://localhost:12345 -- Inspect the Alloy pipeline and component status
- Grafana: http://localhost:3000 -- Explore profiles via the Pyroscope datasource
- Pyroscope: http://localhost:4040 -- Direct access to the Pyroscope UI
- Demo app pprof index: http://localhost:6060/debug/pprof/ -- Raw pprof endpoints
================================================
FILE: continuous-profiling/app/go.mod
================================================
module demo
go 1.23
================================================
FILE: continuous-profiling/app/main.go
================================================
package main
import (
"fmt"
"math/rand"
"net/http"
_ "net/http/pprof"
"time"
)
func cpuIntensive() {
for {
sum := 0
for i := 0; i < 1000000; i++ {
sum += rand.Intn(100)
}
time.Sleep(100 * time.Millisecond)
}
}
func memoryIntensive() {
var data [][]byte
for {
chunk := make([]byte, 1024*1024) // 1MB
for i := range chunk {
chunk[i] = byte(rand.Intn(256))
}
data = append(data, chunk)
if len(data) > 50 {
data = data[1:]
}
time.Sleep(500 * time.Millisecond)
}
}
func main() {
go cpuIntensive()
go memoryIntensive()
fmt.Println("Demo app running on :6060 with pprof endpoints")
http.ListenAndServe(":6060", nil)
}
================================================
FILE: continuous-profiling/config.alloy
================================================
livedebugging {
enabled = true
}
// Scrape pprof profiles from the demo Go application
pyroscope.scrape "default" {
targets = [
{"__address__" = "demo-app:6060", "service_name" = "demo-app"},
]
scrape_interval = "15s"
profiling_config {
profile.process_cpu {
enabled = true
}
profile.memory {
enabled = true
}
profile.goroutine {
enabled = true
}
profile.mutex {
enabled = true
}
profile.block {
enabled = true
}
}
forward_to = [pyroscope.write.default.receiver]
}
pyroscope.write "default" {
endpoint {
url = "http://pyroscope:4040"
}
}
================================================
FILE: continuous-profiling/docker-compose.coda.yml
================================================
services:
demo-app:
image: golang:1.26@sha256:2981696eed011d747340d7252620932677929cce7d2d539602f56a8d7e9b660b
ports:
- 6060:6060
volumes:
- ./app:/app
working_dir: /app
command: go run main.go
================================================
FILE: continuous-profiling/docker-compose.yml
================================================
services:
# Demo Go application with pprof endpoints
demo-app:
image: golang:1.26@sha256:2981696eed011d747340d7252620932677929cce7d2d539602f56a8d7e9b660b
ports:
- 6060:6060
volumes:
- ./app:/app
working_dir: /app
command: go run main.go
# Pyroscope for continuous profiling storage and visualization
pyroscope:
image: grafana/pyroscope:2.0.1@sha256:704889ae04768d982a0a71935bb054948993ddc3fe80234611d20877ba8be4c9
ports:
- 4040:4040
# Grafana for visualization
grafana:
image: grafana/grafana:${GRAFANA_VERSION:-13.0.1}
environment:
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
- GF_AUTH_ANONYMOUS_ENABLED=true
- GF_AUTH_BASIC_ENABLED=false
ports:
- 3000:3000/tcp
entrypoint:
- sh
- -euc
- |
mkdir -p /etc/grafana/provisioning/datasources
cat <<EOF > /etc/grafana/provisioning/datasources/ds.yaml
apiVersion: 1
datasources:
- name: Pyroscope
type: grafana-pyroscope-datasource
access: proxy
orgId: 1
url: http://pyroscope:4040
basicAuth: false
isDefault: true
version: 1
editable: false
EOF
/run.sh
depends_on:
- pyroscope
# Alloy for telemetry pipeline
alloy:
image: grafana/alloy:${GRAFANA_ALLOY_VERSION:-v1.16.1}
ports:
- 12345:12345 # Alloy HTTP server
volumes:
- ./config.alloy:/etc/alloy/config.alloy
command: run --server.http.listen-addr=0.0.0.0:12345 --storage.path=/var/lib/alloy/data /etc/alloy/config.alloy
depends_on:
- demo-app
- pyroscope
================================================
FILE: docker-monitoring/README.md
================================================
# Docker Monitoring with Grafana Alloy
This example demonstrates how to monitor Docker containers using Grafana Alloy.
## Prerequisites
- Docker
- Docker Compose
- Git
## Running the Demo
### Step 1: Clone the repository
```bash
git clone https://github.com/grafana/alloy-scenarios.git
```
### Step 2: Deploy the monitoring stack
```bash
cd alloy-scenarios/docker-monitoring
docker-compose up -d
```
> **Note (macOS Docker Desktop):** If Alloy cannot connect to the Docker socket, you may need to change the volume mount in `docker-compose.yml` from `/var/run/docker.sock` to `/var/run/docker.sock.raw`. This is a workaround specific to some versions of Docker Desktop on macOS.
### Step 3: Access Grafana Alloy UI
Open your browser and go to `http://localhost:12345`.
### Step 4: Access Grafana UI
Open your browser and go to `http://localhost:3000`.
================================================
FILE: docker-monitoring/config.alloy
================================================
// ###############################
// #### Metrics Configuration ####
// ###############################
// Host Cadvisor on the Docker socket to expose container metrics.
prometheus.exporter.cadvisor "example" {
docker_only = true
}
discovery.relabel "example" {
targets = prometheus.exporter.cadvisor.example.targets
rule {
target_label = "job"
replacement = "integrations/docker"
}
rule {
target_label = "instance"
replacement = constants.hostname
}
}
// Configure a prometheus.scrape component to collect cadvisor metrics.
prometheus.scrape "scraper" {
targets = discovery.relabel.example.output
forward_to = [ prometheus.remote_write.demo.receiver ]
scrape_interval = "10s"
}
// Configure a prometheus.remote_write component to send metrics to a Prometheus server.
prometheus.remote_write "demo" {
endpoint {
url = "http://prometheus:9090/api/v1/write"
}
}
// ###############################
// #### Logging Configuration ####
// ###############################
// Discover Docker containers and extract metadata.
discovery.docker "linux" {
host = "unix:///var/run/docker.sock"
}
// Define a relabeling rule to create a service name from the container name.
discovery.relabel "logs_integrations_docker" {
targets = []
rule {
source_labels = ["__meta_docker_container_name"]
regex = "/(.*)"
target_label = "container_name"
}
rule {
target_label = "instance"
replacement = constants.hostname
}
}
// Configure a loki.source.docker component to collect logs from Docker containers.
loki.source.docker "default" {
host = "unix:///var/run/docker.sock"
targets = discovery.docker.linux.targets
relabel_rules = discovery.relabel.logs_integrations_docker.rules
forward_to = [loki.process.docker_logs.receiver]
}
// Process and filter Docker logs before sending to Loki.
// Example: Drop logs from infrastructure containers.
// Modify the regex pattern to match container names you want to exclude.
loki.process "docker_logs" {
forward_to = [loki.write.local.receiver]
stage.drop {
source = "container_name"
expression = "(alloy|grafana|loki)"
}
}
loki.write "local" {
endpoint {
url = "http://loki:3100/loki/api/v1/push"
}
}
================================================
FILE: docker-monitoring/docker-compose.yml
================================================
version: '3'
services:
loki:
image: grafana/loki:${GRAFANA_LOKI_VERSION:-3.6.10}
ports:
- "3100:3100"
volumes:
- ./loki-config.yaml:/etc/loki/local-config.yaml
command: -config.file=/etc/loki/local-config.yaml
prometheus:
image: prom/prometheus:${PROMETHEUS_VERSION:-v3.11.3}
command:
- --web.enable-remote-write-receiver
- --config.file=/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
grafana:
image: grafana/grafana:${GRAFANA_VERSION:-13.0.1}
environment:
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
- GF_AUTH_ANONYMOUS_ENABLED=true
- GF_AUTH_BASIC_ENABLED=false
ports:
- 3000:3000/tcp
entrypoint:
- sh
- -euc
- |
mkdir -p /etc/grafana/provisioning/datasources
cat <<EOF > /etc/grafana/provisioning/datasources/ds.yaml
apiVersion: 1
datasources:
- name: Loki
type: loki
access: proxy
orgId: 1
url: http://loki:3100
basicAuth: false
isDefault: false
version: 1
editable: false
- name: Prometheus
type: prometheus
orgId: 1
url: http://prometheus:9090
basicAuth: false
isDefault: true
version: 1
editable: false
EOF
/run.sh
alloy:
image: grafana/alloy:${GRAFANA_ALLOY_VERSION:-v1.16.1}
privileged: true
ports:
- 12345:12345
- 4317:4317
- 4318:4318
environment:
ALLOY_DEPLOY_MODE: docker
volumes:
- ./config.alloy:/etc/alloy/config.alloy
- /proc:/rootproc:ro
- /var/run/docker.sock:/var/run/docker.sock
- /sys:/sys:ro
- /:/rootfs:ro
- /dev/disk/:/dev/disk:ro
- /var/lib/docker/:/var/lib/docker:ro
command: run --server.http.listen-addr=0.0.0.0:12345 --storage.path=/var/lib/alloy/data /etc/alloy/config.alloy
extra_hosts:
- "host.docker.internal:host-gateway"
devices:
- /dev/kmsg
================================================
FILE: docker-monitoring/grafana/datasources/default.yml
================================================
apiVersion: 1
datasources:
- name: Loki
type: loki
access: proxy
url: http://loki:3100
================================================
FILE: docker-monitoring/loki-config.yaml
================================================
# This is a complete configuration to deploy Loki backed by the filesystem.
# The index will be shipped to the storage via tsdb-shipper.
auth_enabled: false
limits_config:
allow_structured_metadata: true
volume_enabled: true
distributor:
otlp_config:
# List of default otlp resource attributes to be picked as index labels
# CLI flag: -distributor.otlp.default_resource_attributes_as_index_labels
default_resource_attributes_as_index_labels: [service.name service.namespace service.instance.id deployment.environment deployment.environment.name cloud.region cloud.availability_zone k8s.cluster.name k8s.namespace.name k8s.container.name container.name k8s.replicaset.name k8s.deployment.name k8s.statefulset.name k8s.daemonset.name k8s.cronjob.name k8s.job.name]
server:
http_listen_port: 3100
common:
ring:
instance_addr: 0.0.0.0
kvstore:
store: inmemory
replication_factor: 1
path_prefix: /tmp/loki
schema_config:
configs:
- from: 2020-05-15
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
storage_config:
tsdb_shipper:
active_index_directory: /tmp/loki/index
cache_location: /tmp/loki/index_cache
filesystem:
directory: /tmp/loki/chunks
pattern_ingester:
enabled: true
================================================
FILE: elasticsearch-monitoring/README.md
================================================
# Elasticsearch Monitoring with Grafana Alloy
This scenario demonstrates how to monitor an Elasticsearch instance using Grafana Alloy's built-in `prometheus.exporter.elasticsearch` component.
## Architecture
- **Elasticsearch** - The monitored Elasticsearch instance (single-node, security disabled)
- **Grafana Alloy** - Collects Elasticsearch metrics via `prometheus.exporter.elasticsearch` and remote writes them to Prometheus
- **Prometheus** - Stores the scraped metrics
- **Grafana** - Visualizes Elasticsearch metrics (auto-provisioned with Prometheus datasource)
## Running
```bash
# From this directory
docker compose up -d
# Or from the repo root using centralized image versions
./run-example.sh elasticsearch-monitoring
```
## Accessing
- **Grafana**: http://localhost:3000 (no login required)
- **Alloy UI**: http://localhost:12345
- **Prometheus**: http://localhost:9090
- **Elasticsearch**: http://localhost:9200
## Key Metrics
Once running, you can query Elasticsearch metrics in Grafana or Prometheus. Some useful metrics include:
- `elasticsearch_cluster_health_status` - Cluster health (green/yellow/red)
- `elasticsearch_cluster_health_number_of_nodes` - Number of nodes in the cluster
- `elasticsearch_indices_docs_total` - Total number of documents
- `elasticsearch_indices_store_size_bytes` - Total store size
- `elasticsearch_jvm_memory_used_bytes` - JVM memory usage
- `elasticsearch_process_cpu_percent` - CPU usage
- `elasticsearch_breakers_tripped` - Circuit breaker trip count
Metrics are scraped every 30s by default — adjust `scrape_interval` in `config.alloy` if you need finer or coarser resolution.
## Stopping
```bash
docker compose down
```
================================================
FILE: elasticsearch-monitoring/config.alloy
================================================
// Elasticsearch Monitoring with Grafana Alloy
// This configuration scrapes Elasticsearch metrics using the built-in prometheus.exporter.elasticsearch component
// and remote writes them to Prometheus.
livedebugging {
enabled = true
}
prometheus.exporter.elasticsearch "default" {
address = "http://elasticsearch:9200"
}
prometheus.scrape "elasticsearch" {
targets = prometheus.exporter.elasticsearch.default.targets
forward_to = [prometheus.remote_write.default.receiver]
scrape_interval = "30s"
}
prometheus.remote_write "default" {
endpoint {
url = "http://prometheus:9090/api/v1/write"
}
}
================================================
FILE: elasticsearch-monitoring/docker-compose.coda.yml
================================================
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.17.0@sha256:2f602552550869fb29b6fd5848c5118d3ef3a2e1d5d45802e3ab9088cb2de8e2
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- ES_JAVA_OPTS=-Xms512m -Xmx512m
ports:
- "9200:9200"
================================================
FILE: elasticsearch-monitoring/docker-compose.yml
================================================
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.17.0@sha256:2f602552550869fb29b6fd5848c5118d3ef3a2e1d5d45802e3ab9088cb2de8e2
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- ES_JAVA_OPTS=-Xms512m -Xmx512m
ports:
- "9200:9200"
prometheus:
image: prom/prometheus:${PROMETHEUS_VERSION:-v3.11.3}
command:
- --web.enable-remote-write-receiver
- --config.file=/etc/prometheus/prometheus.yml
volumes:
- ./prom-config.yaml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
grafana:
image: grafana/grafana:${GRAFANA_VERSION:-13.0.1}
environment:
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
- GF_AUTH_ANONYMOUS_ENABLED=true
- GF_AUTH_BASIC_ENABLED=false
ports:
- 3000:3000/tcp
entrypoint:
- sh
- -euc
- |
mkdir -p /etc/grafana/provisioning/datasources
cat <<EOF > /etc/grafana/provisioning/datasources/ds.yaml
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
orgId: 1
url: http://prometheus:9090
basicAuth: false
isDefault: true
version: 1
editable: false
EOF
/run.sh
alloy:
image: grafana/alloy:${GRAFANA_ALLOY_VERSION:-v1.16.1}
ports:
- 12345:12345
volumes:
- ./config.alloy:/etc/alloy/config.alloy
command: run --server.http.listen-addr=0.0.0.0:12345 --storage.path=/var/lib/alloy/data /etc/alloy/config.alloy
depends_on:
- elasticsearch
- prometheus
================================================
FILE: elasticsearch-monitoring/prom-config.yaml
================================================
global:
scrape_interval: 15s
evaluation_interval: 15s
================================================
FILE: faro-frontend-observability/README.md
================================================
# Faro Frontend Observability
This scenario demonstrates collecting frontend web telemetry using Grafana Alloy's `faro.receiver` component and the [Grafana Faro Web SDK](https://github.com/grafana/faro-web-sdk).
The Faro Web SDK runs
gitextract_o8zi_7a5/
├── .coda/
│ ├── coda-start.service
│ ├── coda-start.sh
│ └── packer-install.sh
├── .cursor/
│ ├── docker-example.mdc
│ └── k8s-example.mdc
├── .github/
│ ├── k8s-scenarios.json
│ ├── scenario-list.txt
│ └── workflows/
│ ├── check-image-versions.yml
│ ├── validate-k8s-scenarios.yml
│ └── validate-scenarios.yml
├── .gitignore
├── CLAUDE.md
├── LICENSE
├── README.md
├── app-instrumentation/
│ └── logging/
│ └── popular-logging-frameworks/
│ ├── README.md
│ ├── alloy/
│ │ ├── config.alloy
│ │ └── helper.alloy
│ ├── cpp/
│ │ ├── CMakeLists.txt
│ │ ├── Dockerfile
│ │ └── main.cpp
│ ├── csharp/
│ │ ├── Dockerfile
│ │ ├── LoggingExample.csproj
│ │ └── Program.cs
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ ├── go/
│ │ ├── Dockerfile
│ │ ├── go.mod
│ │ ├── go.sum
│ │ └── main.go
│ ├── java/
│ │ ├── App.java
│ │ ├── Dockerfile
│ │ └── logback.xml
│ ├── javascript/
│ │ ├── Dockerfile
│ │ └── app.js
│ ├── loki-config.yaml
│ ├── php/
│ │ ├── Dockerfile
│ │ └── app.php
│ └── python/
│ ├── Dockerfile
│ └── app.py
├── aws-firehose-logs/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.yml
│ ├── firehose_sender.py
│ └── loki-config.yaml
├── blackbox-probing/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ └── prom-config.yaml
├── cloudwatch-metrics/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.yml
│ ├── prom-config.yaml
│ └── seed-metrics.py
├── coda
├── continuous-profiling/
│ ├── README.md
│ ├── app/
│ │ ├── go.mod
│ │ └── main.go
│ ├── config.alloy
│ ├── docker-compose.coda.yml
│ └── docker-compose.yml
├── docker-monitoring/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.yml
│ ├── grafana/
│ │ └── datasources/
│ │ └── default.yml
│ └── loki-config.yaml
├── elasticsearch-monitoring/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ └── prom-config.yaml
├── faro-frontend-observability/
│ ├── README.md
│ ├── app/
│ │ └── index.html
│ ├── config.alloy
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ └── loki-config.yaml
├── game-of-tracing/
│ ├── AGENTS.md
│ ├── CLAUDE.md
│ ├── README.md
│ ├── SPAN_LINKS.md
│ ├── ai_opponent/
│ │ ├── CLAUDE.md
│ │ ├── Dockerfile
│ │ ├── README.md
│ │ ├── ai_server.py
│ │ ├── requirements.txt
│ │ └── telemetry.py
│ ├── app/
│ │ ├── CLAUDE.md
│ │ ├── Dockerfile
│ │ ├── game_config.py
│ │ ├── location_server.py
│ │ ├── requirements.txt
│ │ ├── run_game.py
│ │ └── telemetry.py
│ ├── config-otel.yaml
│ ├── config.alloy
│ ├── docker-compose-otel.yml
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ ├── grafana/
│ │ ├── dashboards/
│ │ │ ├── War of Kingdoms-1747821967780.json
│ │ │ └── dashboards.yaml
│ │ └── datasources/
│ │ └── defaults.yml
│ ├── loki-config.yaml
│ ├── prom-config.yaml
│ ├── pyroscope-config.yaml
│ ├── tempo-config.yaml
│ └── war_map/
│ ├── CLAUDE.md
│ ├── Dockerfile
│ ├── app.py
│ ├── requirements.txt
│ ├── static/
│ │ └── css/
│ │ └── style.css
│ ├── telemetry.py
│ └── templates/
│ ├── index.html
│ ├── layout.html
│ ├── map.html
│ ├── map_picker.html
│ ├── replay.html
│ └── replay_session.html
├── gelf-log-ingestion/
│ ├── README.md
│ ├── app/
│ │ └── main.py
│ ├── config.alloy
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ └── loki-config.yaml
├── image-versions.env
├── k8s/
│ ├── README.md
│ ├── events/
│ │ ├── README.md
│ │ ├── alloy-config.yaml
│ │ ├── alloy-deployment.yaml
│ │ ├── alloy-rbac.yaml
│ │ ├── grafana-values.yml
│ │ ├── kind.yml
│ │ └── loki-values.yml
│ ├── logs/
│ │ ├── README.md
│ │ ├── grafana-values.yml
│ │ ├── k8s-monitoring-values.yml
│ │ ├── killercoda/
│ │ │ └── loki-values.yml
│ │ ├── kind.yml
│ │ └── loki-values.yml
│ ├── metrics/
│ │ ├── README.md
│ │ ├── grafana-values.yml
│ │ ├── k8s-monitoring-values.yml
│ │ ├── kind.yml
│ │ └── prometheus-values.yml
│ ├── profiling/
│ │ ├── README.md
│ │ ├── grafana-values.yml
│ │ ├── k8s-monitoring-values.yml
│ │ ├── kind.yml
│ │ └── pyroscope-values.yml
│ └── tracing/
│ ├── README.md
│ ├── grafana-values.yml
│ ├── k8s-monitoring-values.yml
│ ├── kind.yml
│ └── tempo-values.yml
├── kafka/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ ├── gen_log.sh
│ └── loki-config.yaml
├── linux/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.yml
│ ├── loki-config.yaml
│ └── prom-config.yaml
├── log-api-gateway/
│ ├── README.md
│ ├── app/
│ │ └── producer.py
│ ├── config.alloy
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ └── loki-config.yaml
├── log-secret-filtering/
│ ├── README.md
│ ├── app/
│ │ └── main.py
│ ├── config.alloy
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ └── loki-config.yaml
├── logs-file/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ ├── loki-config.yaml
│ └── main.py
├── logs-tcp/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ ├── loki-config.yaml
│ └── simulator.py
├── mail-house/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ ├── loki-config.yaml
│ └── main.py
├── memcached-monitoring/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ └── prom-config.yaml
├── mysql-monitoring/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ └── prom-config.yaml
├── nginx-monitoring/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.yml
│ ├── loki-config.yaml
│ ├── nginx.conf
│ └── prom-config.yaml
├── otel-basic-tracing/
│ ├── README.md
│ ├── app/
│ │ ├── Dockerfile
│ │ ├── app.py
│ │ └── requirements.txt
│ ├── config-otel.yaml
│ ├── config.alloy
│ ├── docker-compose-otel.yml
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ ├── prom-config.yaml
│ └── tempo-config.yaml
├── otel-examples/
│ ├── README.md
│ ├── cost-control/
│ │ ├── README.md
│ │ ├── app/
│ │ │ ├── Dockerfile
│ │ │ ├── app.py
│ │ │ └── requirements.txt
│ │ ├── config-otel.yaml
│ │ ├── config.alloy
│ │ ├── docker-compose.coda.yml
│ │ ├── docker-compose.yml
│ │ ├── loki-config.yaml
│ │ └── tempo-config.yaml
│ ├── count-connector/
│ │ ├── README.md
│ │ ├── app/
│ │ │ ├── Dockerfile
│ │ │ ├── app.py
│ │ │ └── requirements.txt
│ │ ├── config-otel.yaml
│ │ ├── config.alloy
│ │ ├── docker-compose.coda.yml
│ │ ├── docker-compose.yml
│ │ ├── loki-config.yaml
│ │ ├── prom-config.yaml
│ │ └── tempo-config.yaml
│ ├── filelog-processing/
│ │ ├── README.md
│ │ ├── app/
│ │ │ └── generate_logs.py
│ │ ├── config-otel.yaml
│ │ ├── config.alloy
│ │ ├── docker-compose.coda.yml
│ │ ├── docker-compose.yml
│ │ └── loki-config.yaml
│ ├── host-metrics/
│ │ ├── README.md
│ │ ├── config-otel.yaml
│ │ ├── config.alloy
│ │ ├── docker-compose.coda.yml
│ │ ├── docker-compose.yml
│ │ └── prom-config.yaml
│ ├── kafka-buffer/
│ │ ├── README.md
│ │ ├── app/
│ │ │ ├── Dockerfile
│ │ │ ├── app.py
│ │ │ └── requirements.txt
│ │ ├── config-otel.yaml
│ │ ├── config.alloy
│ │ ├── docker-compose.coda.yml
│ │ ├── docker-compose.yml
│ │ └── tempo-config.yaml
│ ├── multi-pipeline-fanout/
│ │ ├── README.md
│ │ ├── app/
│ │ │ ├── Dockerfile
│ │ │ ├── app.py
│ │ │ └── requirements.txt
│ │ ├── config-otel.yaml
│ │ ├── config.alloy
│ │ ├── docker-compose.coda.yml
│ │ ├── docker-compose.yml
│ │ ├── prom-config.yaml
│ │ └── tempo-config.yaml
│ ├── ottl-transform/
│ │ ├── README.md
│ │ ├── app/
│ │ │ ├── Dockerfile
│ │ │ ├── app.py
│ │ │ └── requirements.txt
│ │ ├── config-otel.yaml
│ │ ├── config.alloy
│ │ ├── docker-compose.coda.yml
│ │ ├── docker-compose.yml
│ │ ├── loki-config.yaml
│ │ └── tempo-config.yaml
│ ├── pii-redaction/
│ │ ├── README.md
│ │ ├── app/
│ │ │ ├── Dockerfile
│ │ │ ├── app.py
│ │ │ └── requirements.txt
│ │ ├── config-otel.yaml
│ │ ├── config.alloy
│ │ ├── docker-compose.coda.yml
│ │ ├── docker-compose.yml
│ │ ├── loki-config.yaml
│ │ └── tempo-config.yaml
│ ├── resource-enrichment/
│ │ ├── README.md
│ │ ├── app/
│ │ │ ├── Dockerfile
│ │ │ ├── app.py
│ │ │ └── requirements.txt
│ │ ├── config-otel.yaml
│ │ ├── config.alloy
│ │ ├── docker-compose.coda.yml
│ │ ├── docker-compose.yml
│ │ ├── prom-config.yaml
│ │ └── tempo-config.yaml
│ └── routing-multi-tenant/
│ ├── README.md
│ ├── app/
│ │ ├── generate_logs.py
│ │ └── requirements.txt
│ ├── config-otel.yaml
│ ├── config.alloy
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ └── loki-config.yaml
├── otel-metrics-pipeline/
│ ├── README.md
│ ├── app/
│ │ └── main.py
│ ├── config-otel.yaml
│ ├── config.alloy
│ ├── docker-compose-otel.yml
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ └── prom-config.yaml
├── otel-span-metrics/
│ ├── README.md
│ ├── app/
│ │ ├── load.py
│ │ ├── main.py
│ │ └── requirements.txt
│ ├── config-otel.yaml
│ ├── config.alloy
│ ├── docker-compose-otel.yml
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ ├── prom-config.yaml
│ └── tempo-config.yaml
├── otel-tail-sampling/
│ ├── README.md
│ ├── app/
│ │ ├── Dockerfile
│ │ ├── app.py
│ │ └── requirements.txt
│ ├── config-otel.yaml
│ ├── config.alloy
│ ├── docker-compose-otel.yml
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ ├── prom-config.yaml
│ └── tempo-config.yaml
├── otel-tracing-service-graphs/
│ ├── README.md
│ ├── app/
│ │ ├── Dockerfile
│ │ ├── app.py
│ │ └── requirements.txt
│ ├── config-otel.yaml
│ ├── config.alloy
│ ├── docker-compose-otel.yml
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ ├── prom-config.yaml
│ └── tempo-config.yaml
├── postgres-monitoring/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ └── prom-config.yaml
├── rabbitmq-monitoring/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ ├── enabled_plugins
│ ├── loki-config.yaml
│ ├── prom-config.yaml
│ └── rabbitmq.conf
├── redis-monitoring/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ └── prom-config.yaml
├── renovate.json
├── routing/
│ ├── README.MD
│ ├── config.alloy
│ ├── docker-compose.yaml
│ └── support/
│ ├── grafana/
│ │ └── datasources.yml
│ ├── loki/
│ │ └── server.yaml
│ └── promtail/
│ ├── myCustomLog.txt
│ └── promtail-config.yml
├── run-example.sh
├── self-monitoring/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.yaml
│ └── loki-config.yaml
├── snmp/
│ ├── Readme.md
│ ├── config.alloy
│ ├── docker-compose.yml
│ ├── loki-config.yaml
│ ├── prom-config.yaml
│ └── snmp.yml
├── syslog/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ ├── loki-config.yaml
│ ├── rsyslog.conf
│ └── syslog_simulator.py
├── systemd-journal/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.yml
│ └── loki-config.yaml
├── trace-delivery/
│ ├── README.md
│ ├── app/
│ │ ├── Dockerfile
│ │ ├── app.py
│ │ └── requirements.txt
│ ├── config-otel.yaml
│ ├── config.alloy
│ ├── docker-compose-otel.yml
│ ├── docker-compose.coda.yml
│ ├── docker-compose.yml
│ ├── prom-config.yaml
│ └── tempo-config.yaml
├── vault-secrets/
│ ├── README.md
│ ├── auth/
│ │ └── htpasswd
│ ├── config.alloy
│ ├── docker-compose.yml
│ ├── nginx.conf
│ ├── prom-config.yaml
│ └── rotate.sh
├── windows/
│ ├── README.md
│ ├── config.alloy
│ ├── docker-compose.yml
│ ├── loki-config.yaml
│ └── prom-config.yaml
└── windows-events/
├── README.md
├── config.alloy
├── docker-compose.yml
└── loki-config.yaml
SYMBOL INDEX (346 symbols across 32 files)
FILE: app-instrumentation/logging/popular-logging-frameworks/cpp/main.cpp
function main (line 6) | int main() {
FILE: app-instrumentation/logging/popular-logging-frameworks/csharp/Program.cs
class Program (line 9) | class Program
method Main (line 13) | static async Task Main(string[] args)
FILE: app-instrumentation/logging/popular-logging-frameworks/go/main.go
function main (line 11) | func main() {
FILE: app-instrumentation/logging/popular-logging-frameworks/java/App.java
class App (line 5) | public class App {
method main (line 11) | public static void main(String[] args) {
FILE: app-instrumentation/logging/popular-logging-frameworks/javascript/app.js
function logMessage (line 23) | function logMessage() {
FILE: app-instrumentation/logging/popular-logging-frameworks/python/app.py
function main (line 17) | def main():
FILE: aws-firehose-logs/firehose_sender.py
function vpc_flow_line (line 39) | def vpc_flow_line() -> str:
function lambda_log_line (line 49) | def lambda_log_line() -> str:
function cloudwatch_envelope (line 56) | def cloudwatch_envelope(log_group: str, log_stream: str, line_fn) -> dict:
function encode_record (line 78) | def encode_record(envelope: dict) -> dict:
function send_batch (line 88) | def send_batch() -> None:
function main (line 113) | def main() -> int:
FILE: continuous-profiling/app/main.go
function cpuIntensive (line 11) | func cpuIntensive() {
function memoryIntensive (line 21) | func memoryIntensive() {
function main (line 36) | func main() {
FILE: game-of-tracing/ai_opponent/ai_server.py
function get_map_graph (line 172) | def get_map_graph(map_id):
function get_capitals (line 176) | def get_capitals(map_id):
function get_location_types (line 180) | def get_location_types(map_id):
function get_initial_factions (line 184) | def get_initial_factions(map_id):
function get_army_cost_for (line 188) | def get_army_cost_for(map_id, faction):
class GamePhase (line 194) | class GamePhase(Enum):
class MapAnalyzer (line 203) | class MapAnalyzer:
method __init__ (line 206) | def __init__(self, graph=None, capitals=None):
method _bfs_distances (line 215) | def _bfs_distances(self, start):
method _compute_all_distances (line 227) | def _compute_all_distances(self):
method _compute_strategic_values (line 231) | def _compute_strategic_values(self):
method distance (line 251) | def distance(self, a, b):
method neighbors (line 254) | def neighbors(self, loc):
method path_army_estimate (line 257) | def path_army_estimate(self, game_state, from_loc, to_loc, my_faction):
class GameMemory (line 290) | class GameMemory:
method __init__ (line 293) | def __init__(self):
method update (line 299) | def update(self, game_state, my_faction):
method record_failed_attack (line 322) | def record_failed_attack(self, target):
method recently_failed (line 325) | def recently_failed(self, target, cooldown=60):
method territory_lost_recently (line 331) | def territory_lost_recently(self, seconds=30):
class PhaseDetector (line 346) | class PhaseDetector:
method detect (line 350) | def detect(my_territories, enemy_territories, total_army):
class Planner (line 367) | class Planner:
method __init__ (line 370) | def __init__(self):
method active (line 375) | def active(self):
method set_plan (line 378) | def set_plan(self, goal, steps):
method next_step (line 382) | def next_step(self):
method advance (line 387) | def advance(self):
method abandon (line 391) | def abandon(self, reason=""):
method validate (line 395) | def validate(self, game_state, my_faction, my_capital):
class StrategicAI (line 419) | class StrategicAI:
method __init__ (line 422) | def __init__(self, faction, map_id="war_of_kingdoms"):
method decide (line 442) | def decide(self, game_state):
method _check_capital_defense (line 530) | def _check_capital_defense(self, game_state):
method _reinforce_capital (line 574) | def _reinforce_capital(self, game_state):
method _step_toward (line 599) | def _step_toward(self, from_loc, toward_loc):
method _find_zero_risk_captures (line 616) | def _find_zero_risk_captures(self, game_state):
method _do_resource_transfers (line 665) | def _do_resource_transfers(self, game_state):
method _execute_plan_step (line 688) | def _execute_plan_step(self, game_state):
method _create_new_plan (line 750) | def _create_new_plan(self, game_state):
method _plan_capture (line 795) | def _plan_capture(self, game_state):
method _find_capturable_targets (line 829) | def _find_capturable_targets(self, game_state):
method _plan_all_out_attack (line 875) | def _plan_all_out_attack(self, game_state):
method _concentrate_forces (line 892) | def _concentrate_forces(self, game_state):
method _fallback (line 924) | def _fallback(self, game_state):
method get_pause_time (line 934) | def get_pause_time(self):
class WhiteWalkerAI (line 946) | class WhiteWalkerAI(StrategicAI):
method decide (line 968) | def decide(self, game_state):
method _defend_fortress (line 1029) | def _defend_fortress(self, game_state):
method _capture_unowned_wall (line 1064) | def _capture_unowned_wall(self, game_state):
method _reinforce_weakest_wall (line 1091) | def _reinforce_weakest_wall(self, game_state):
method _raid_barbarian (line 1140) | def _raid_barbarian(self, game_state):
method _raise_army_from_corpses (line 1173) | def _raise_army_from_corpses(self, game_state, corpses):
method _passive_fallback (line 1191) | def _passive_fallback(self):
method _walls (line 1200) | def _walls(self):
method _nearest_source_with_army (line 1204) | def _nearest_source_with_army(self, game_state, target, needed):
class AIState (line 1224) | class AIState:
method __init__ (line 1225) | def __init__(self):
function get_location_url (line 1239) | def get_location_url(location_id):
function fetch_faction_corpses (line 1257) | def fetch_faction_corpses(faction):
function make_api_request (line 1272) | def make_api_request(location_id, endpoint, method='GET', data=None):
function get_game_state (line 1308) | def get_game_state(parent_ctx):
function execute_strategic_action (line 1350) | def execute_strategic_action(action, game_state, parent_ctx, decision_li...
function ai_decision_loop (line 1440) | def ai_decision_loop():
function activate_ai (line 1539) | def activate_ai():
function deactivate_ai (line 1611) | def deactivate_ai():
function ai_status (line 1627) | def ai_status():
function health_check (line 1637) | def health_check():
FILE: game-of-tracing/ai_opponent/telemetry.py
class AITelemetry (line 29) | class AITelemetry:
method __init__ (line 30) | def __init__(self, service_name="ai-opponent", logging_endpoint="http:...
method _setup_logging (line 47) | def _setup_logging(self):
method _setup_tracing (line 74) | def _setup_tracing(self):
method _setup_profiling (line 91) | def _setup_profiling(self):
method _setup_metrics (line 102) | def _setup_metrics(self):
method _observe_territory_count (line 175) | def _observe_territory_count(self, options: CallbackOptions) -> Iterab...
method _observe_total_army (line 188) | def _observe_total_army(self, options: CallbackOptions) -> Iterable[Ob...
method set_state_callback (line 201) | def set_state_callback(self, fn):
method set_corpse_callback (line 205) | def set_corpse_callback(self, fn):
method _observe_corpse_pool (line 212) | def _observe_corpse_pool(self, options: CallbackOptions) -> Iterable[O...
method record_wall_captured (line 224) | def record_wall_captured(self, wall_id, source):
method record_decision (line 230) | def record_decision(self, action_type, phase):
method record_plan_created (line 234) | def record_plan_created(self, goal):
method record_plan_abandoned (line 238) | def record_plan_abandoned(self, reason):
method record_cycle_duration (line 242) | def record_cycle_duration(self, seconds):
method collect_metrics (line 246) | def collect_metrics(self):
method get_tracer (line 254) | def get_tracer(self):
method get_logger (line 258) | def get_logger(self):
method shutdown (line 262) | def shutdown(self):
FILE: game-of-tracing/app/game_config.py
function get_map (line 301) | def get_map(map_id):
function resolve_slot (line 308) | def resolve_slot(map_id, slot_id):
function get_location_config (line 313) | def get_location_config(map_id, location_id):
function get_rules (line 318) | def get_rules(map_id):
function get_army_cost (line 323) | def get_army_cost(map_id, faction):
function get_army_currency (line 329) | def get_army_currency(map_id, faction):
function locations_by_type (line 335) | def locations_by_type(map_id, type_name):
FILE: game-of-tracing/app/location_server.py
class PathType (line 40) | class PathType(Enum):
class LocationServer (line 44) | class LocationServer:
method __init__ (line 45) | def __init__(self, slot_or_location=None):
method _current_locations (line 102) | def _current_locations(self) -> Dict:
method _current_rules (line 106) | def _current_rules(self) -> Dict:
method _read_active_map_id (line 109) | def _read_active_map_id(self) -> str:
method _load_identity (line 119) | def _load_identity(self):
method _start_passive_threads_if_needed (line 156) | def _start_passive_threads_if_needed(self):
method _get_corpses (line 209) | def _get_corpses(self, faction: str = "white_walkers") -> int:
method _add_corpses (line 219) | def _add_corpses(self, delta: int, faction: str = "white_walkers"):
method _spend_corpses (line 233) | def _spend_corpses(self, amount: int, faction: str = "white_walkers") ...
method _find_capital (line 247) | def _find_capital(self, faction: str) -> Optional[str]:
method _find_enemy_capital (line 254) | def _find_enemy_capital(self, faction: str) -> Optional[str]:
method _get_db_connection (line 261) | def _get_db_connection(self):
method _initialize_database (line 271) | def _initialize_database(self):
method _get_location_state (line 310) | def _get_location_state(self, location_id):
method _update_location_state (line 327) | def _update_location_state(self, location_id, resources=None, army=Non...
method _find_path (line 361) | def _find_path(self, target: str, path_type: PathType) -> Optional[Lis...
method _handle_battle (line 421) | def _handle_battle(self, attacking_army: int, attacking_faction: str,
method _continue_army_movement (line 469) | def _continue_army_movement(self, army_size: int, faction: str, curren...
method _transfer_resources_along_path (line 533) | def _transfer_resources_along_path(self, resources: int, path: List[st...
method _make_request_with_trace (line 587) | def _make_request_with_trace(self, method: str, url: str, json_data: O...
method _can_collect_resources (line 614) | def _can_collect_resources(self) -> tuple[bool, Optional[str], Optiona...
method _start_resource_cooldown (line 642) | def _start_resource_cooldown(self):
method get_location_url (line 646) | def get_location_url(self, location_id):
method _container_for (line 663) | def _container_for(self, location_id: str) -> str:
method _start_passive_generation (line 680) | def _start_passive_generation(self):
method _start_barbarian_growth (line 714) | def _start_barbarian_growth(self, interval_s: int):
method _start_nights_watch_capital_resource_tick (line 746) | def _start_nights_watch_capital_resource_tick(self, interval_s: int, a...
method _start_white_walker_corpse_tick (line 781) | def _start_white_walker_corpse_tick(self, interval_s: int):
method reset_database (line 806) | def reset_database(self):
method setup_routes (line 830) | def setup_routes(self):
method run (line 1554) | def run(self):
FILE: game-of-tracing/app/run_game.py
function reset_game (line 10) | def reset_game():
function run_location (line 40) | def run_location(location_id):
function run_single_location (line 46) | def run_single_location():
function show_game_state (line 61) | def show_game_state():
function run_game (line 91) | def run_game(reset=False):
FILE: game-of-tracing/app/telemetry.py
class GameTelemetry (line 29) | class GameTelemetry:
method __init__ (line 30) | def __init__(self, service_name, logging_endpoint="http://alloy:4318",...
method _setup_logging (line 44) | def _setup_logging(self):
method _setup_tracing (line 71) | def _setup_tracing(self):
method _setup_profiling (line 88) | def _setup_profiling(self):
method _setup_metrics (line 105) | def _setup_metrics(self):
method _setup_game_gauges (line 132) | def _setup_game_gauges(self):
method _active_location_id (line 188) | def _active_location_id(self):
method _active_location_type (line 197) | def _active_location_type(self):
method _observe_resources (line 200) | def _observe_resources(self, options: CallbackOptions) -> Iterable[Obs...
method _observe_army_size (line 218) | def _observe_army_size(self, options: CallbackOptions) -> Iterable[Obs...
method _observe_resource_cooldown (line 237) | def _observe_resource_cooldown(self, options: CallbackOptions) -> Iter...
method _observe_location_control (line 257) | def _observe_location_control(self, options: CallbackOptions) -> Itera...
method get_tracer (line 279) | def get_tracer(self):
method get_logger (line 283) | def get_logger(self):
method get_meter (line 287) | def get_meter(self):
method record_battle (line 291) | def record_battle(self, attacker_faction: str, defender_faction: str, ...
method collect_metrics (line 309) | def collect_metrics(self):
method shutdown (line 320) | def shutdown(self):
FILE: game-of-tracing/war_map/app.py
function _current_positions (line 147) | def _current_positions():
function _current_connections (line 154) | def _current_connections():
function _current_walls (line 161) | def _current_walls():
function init_game_session_tracking (line 164) | def init_game_session_tracking():
function store_game_action (line 218) | def store_game_action(game_session_id, action_type, player_name, faction,
function get_session_map_id (line 259) | def get_session_map_id(session_id):
function get_previous_action_context (line 291) | def get_previous_action_context(game_session_id, target_sequence):
function create_span_link_from_context (line 333) | def create_span_link_from_context(span_context, link_type="game_sequence"):
function remove_frame_options (line 353) | def remove_frame_options(response):
function _container_for_slot (line 433) | def _container_for_slot(slot_id):
function _slot_port_pairs (line 438) | def _slot_port_pairs():
function get_db_connection (line 451) | def get_db_connection():
function _ensure_game_config_tables (line 463) | def _ensure_game_config_tables():
function get_active_map_id (line 508) | def get_active_map_id():
function set_active_map_id (line 521) | def set_active_map_id(map_id):
function reset_wall_hold (line 539) | def reset_wall_hold(map_id):
function bump_wall_hold (line 551) | def bump_wall_hold(map_id, faction, reset_others=True):
function get_wall_hold (line 584) | def get_wall_hold(map_id):
function get_faction_corpses (line 597) | def get_faction_corpses(faction):
function check_faction_availability (line 610) | def check_faction_availability(faction):
function register_faction (line 641) | def register_faction(faction, player_name, session_id):
function get_player_faction (line 660) | def get_player_faction(session_id):
function release_faction (line 676) | def release_faction(session_id):
function release_all_factions (line 691) | def release_all_factions():
function get_location_url (line 706) | def get_location_url(location_id):
function make_api_request (line 720) | def make_api_request(location_id, endpoint, method='GET', data=None):
function check_game_over (line 770) | def check_game_over(locations_data, map_id=None):
function check_capital_capture_win (line 781) | def check_capital_capture_win(locations_data):
function check_wall_hold_win (line 801) | def check_wall_hold_win(locations_data, map_id):
function reset_game_state (line 832) | def reset_game_state():
function reset_game_data (line 839) | def reset_game_data():
function health (line 876) | def health():
function index (line 880) | def index():
function map_picker (line 930) | def map_picker():
function select_map (line 937) | def select_map():
function select_faction (line 1009) | def select_faction():
function logout (line 1095) | def logout():
function restart_game (line 1105) | def restart_game():
function game_map (line 1132) | def game_map():
function collect_resources (line 1185) | def collect_resources():
function create_army (line 1242) | def create_army():
function move_army (line 1299) | def move_army():
function location_info (line 1402) | def location_info(location_id):
function map_data (line 1412) | def map_data():
function game_status (line 1450) | def game_status():
function reset_game (line 1471) | def reset_game():
function send_resources_to_capital (line 1477) | def send_resources_to_capital():
function all_out_attack (line 1499) | def all_out_attack():
function toggle_ai (line 1582) | def toggle_ai():
function get_ai_status (line 1650) | def get_ai_status():
function get_replay_sessions (line 1662) | def get_replay_sessions():
function replay_page (line 1743) | def replay_page():
function replay_session_page (line 1748) | def replay_session_page(session_id):
function get_replay_session (line 1845) | def get_replay_session(session_id):
function parse_span_to_action_from_detail (line 1986) | def parse_span_to_action_from_detail(span, trace_id, root_trace_name):
function parse_span_to_action_from_search (line 2091) | def parse_span_to_action_from_search(span, trace_id, root_trace_name, se...
function verify_action_links (line 2161) | def verify_action_links(actions):
function _wall_tick_thread (line 2215) | def _wall_tick_thread():
FILE: game-of-tracing/war_map/telemetry.py
class GameTelemetry (line 20) | class GameTelemetry:
method __init__ (line 21) | def __init__(self, service_name, logging_endpoint="http://alloy:4318",...
method _setup_logging (line 33) | def _setup_logging(self):
method _setup_tracing (line 60) | def _setup_tracing(self):
method _setup_profiling (line 77) | def _setup_profiling(self):
method get_tracer (line 88) | def get_tracer(self):
method get_logger (line 92) | def get_logger(self):
method shutdown (line 96) | def shutdown(self):
FILE: logs-file/main.py
function simulate_process (line 25) | def simulate_process():
FILE: mail-house/main.py
function generate_log_entry (line 42) | def generate_log_entry():
function main (line 68) | def main():
FILE: otel-basic-tracing/app/app.py
function home (line 39) | def home():
function simple_trace (line 53) | def simple_trace():
function nested_trace (line 61) | def nested_trace():
function error_trace (line 83) | def error_trace():
function chain_trace (line 95) | def chain_trace():
function service_b (line 112) | def service_b():
function service_c (line 125) | def service_c():
function delayed_chain_trace (line 141) | def delayed_chain_trace():
function delayed_service_a (line 161) | def delayed_service_a():
function delayed_service_b (line 175) | def delayed_service_b():
function delayed_service_c (line 189) | def delayed_service_c():
function delayed_service_d (line 203) | def delayed_service_d():
function delayed_service_e (line 222) | def delayed_service_e():
FILE: otel-examples/cost-control/app/app.py
function health (line 56) | def health():
function ready (line 63) | def ready():
function order (line 70) | def order():
function error (line 86) | def error():
function load_generator (line 98) | def load_generator():
FILE: otel-examples/count-connector/app/app.py
function process (line 57) | def process():
function notify (line 78) | def notify():
function health (line 96) | def health():
function load_generator (line 100) | def load_generator():
FILE: otel-examples/filelog-processing/app/generate_logs.py
function write_json_line (line 39) | def write_json_line(f, level):
function write_plain_line (line 50) | def write_plain_line(f, level):
function main (line 56) | def main():
FILE: otel-examples/kafka-buffer/app/app.py
function list_items (line 40) | def list_items():
function get_item (line 49) | def get_item(item_id):
function checkout (line 59) | def checkout():
function health (line 75) | def health():
function generate_load (line 79) | def generate_load():
FILE: otel-examples/multi-pipeline-fanout/app/app.py
function list_orders (line 52) | def list_orders():
function create_order (line 61) | def create_order():
function health (line 71) | def health():
function generate_load (line 75) | def generate_load():
FILE: otel-examples/ottl-transform/app/app.py
function send_json_log_records (line 50) | def send_json_log_records():
function send_traces (line 64) | def send_traces():
function main (line 92) | def main():
FILE: otel-examples/pii-redaction/app/app.py
function place_order (line 77) | def place_order():
function health (line 99) | def health():
function traffic_generator (line 103) | def traffic_generator():
FILE: otel-examples/resource-enrichment/app/app.py
function list_users (line 57) | def list_users():
function list_items (line 72) | def list_items():
function health (line 87) | def health():
function load_generator (line 91) | def load_generator():
FILE: otel-examples/routing-multi-tenant/app/generate_logs.py
function create_logger (line 38) | def create_logger(tenant: str, service_name: str) -> logging.Logger:
function main (line 55) | def main():
FILE: otel-span-metrics/app/main.py
function index (line 20) | def index():
function get_data (line 26) | def get_data():
function slow (line 34) | def slow():
FILE: otel-tail-sampling/app/app.py
function generate_simple_trace (line 50) | def generate_simple_trace():
function generate_nested_trace (line 59) | def generate_nested_trace():
function generate_error_trace (line 82) | def generate_error_trace():
function generate_high_latency_trace (line 93) | def generate_high_latency_trace():
function generate_delayed_chain_trace (line 102) | def generate_delayed_chain_trace():
function generate_multi_service_trace_bg (line 158) | def generate_multi_service_trace_bg():
function generate_trace_batch (line 250) | def generate_trace_batch():
function trace_generator_thread (line 269) | def trace_generator_thread():
function home (line 284) | def home():
function simple_trace (line 303) | def simple_trace():
function nested_trace (line 308) | def nested_trace():
function error_trace (line 313) | def error_trace():
function high_latency_trace (line 318) | def high_latency_trace():
function batch_trace (line 323) | def batch_trace():
function multi_service_trace (line 328) | def multi_service_trace():
function chain_trace (line 338) | def chain_trace():
function service_b (line 355) | def service_b():
function service_c (line 369) | def service_c():
function delayed_chain_trace_endpoint (line 386) | def delayed_chain_trace_endpoint():
function delayed_service_a (line 406) | def delayed_service_a():
function delayed_service_b (line 421) | def delayed_service_b():
function delayed_service_c (line 436) | def delayed_service_c():
function delayed_service_d (line 451) | def delayed_service_d():
function delayed_service_e (line 471) | def delayed_service_e():
FILE: otel-tracing-service-graphs/app/app.py
function home (line 40) | def home():
function simple_trace (line 55) | def simple_trace():
function nested_trace (line 63) | def nested_trace():
function error_trace (line 85) | def error_trace():
function chain_trace (line 97) | def chain_trace():
function service_b (line 114) | def service_b():
function service_c (line 127) | def service_c():
function delayed_chain_trace (line 143) | def delayed_chain_trace():
function delayed_service_a (line 163) | def delayed_service_a():
function delayed_service_b (line 179) | def delayed_service_b():
function delayed_service_c (line 195) | def delayed_service_c():
function delayed_service_d (line 211) | def delayed_service_d():
function delayed_service_e (line 232) | def delayed_service_e():
function multi_service_trace (line 245) | def multi_service_trace():
function generate_multi_service_trace (line 254) | def generate_multi_service_trace():
FILE: trace-delivery/app/app.py
function generate_order_id (line 79) | def generate_order_id():
function random_item (line 83) | def random_item(items):
function should_fail (line 87) | def should_fail(service_name, order):
function maybe_add_latency (line 100) | def maybe_add_latency(service_name, span):
function home (line 114) | def home():
function catalog (line 137) | def catalog():
function place_order (line 147) | def place_order():
function check_order_status (line 207) | def check_order_status():
function delivery_notification (line 232) | def delivery_notification():
function manufacture (line 275) | def manufacture():
function global_pickup (line 459) | def global_pickup():
function local_deliver (line 604) | def local_deliver():
function customer_receive (line 805) | def customer_receive():
function demo_success (line 845) | def demo_success():
function demo_failure_endpoint (line 903) | def demo_failure_endpoint():
function demo_failure (line 906) | def demo_failure(failure_service=None, is_background=False):
function demo_latency_endpoint (line 976) | def demo_latency_endpoint():
function demo_latency (line 979) | def demo_latency(latency_service=None, is_background=False):
function generate_random_trace (line 1049) | def generate_random_trace():
function trace_generator_thread (line 1122) | def trace_generator_thread():
Condensed preview — 430 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,415K chars).
[
{
"path": ".coda/coda-start.service",
"chars": 344,
"preview": "[Unit]\nDescription=Coda Alloy Scenario Start\nAfter=network-online.target docker.service\nWants=network-online.target\nRequ"
},
{
"path": ".coda/coda-start.sh",
"chars": 997,
"preview": "#!/usr/bin/env bash\nset -euo pipefail\n\nSCENARIO_FILE=\"/etc/coda/scenario\"\nREPO_DIR=\"/opt/alloy-scenarios\"\n\n# Wait for th"
},
{
"path": ".coda/packer-install.sh",
"chars": 1189,
"preview": "#!/usr/bin/env bash\n# Packer provisioner: set up coda CLI and systemd services on an AMI.\n#\n# Expects the alloy-scenario"
},
{
"path": ".cursor/docker-example.mdc",
"chars": 12316,
"preview": "---\ndescription: creating a new alloy docker example\nglobs: \nalwaysApply: false\n---\n# Grafana Alloy Docker Example Templ"
},
{
"path": ".cursor/k8s-example.mdc",
"chars": 8282,
"preview": "---\ndescription: \nglobs: \nalwaysApply: false\n---\n# Grafana Alloy Kubernetes Example Template\n\nThis template provides a c"
},
{
"path": ".github/k8s-scenarios.json",
"chars": 1731,
"preview": "{\n \"metrics\": [\n { \"release\": \"prometheus\", \"chart\": \"prometheus-community/prometheus\", \"values\": \"prometheus-values"
},
{
"path": ".github/scenario-list.txt",
"chars": 543,
"preview": "aws-firehose-logs\nblackbox-probing\ncontinuous-profiling\ndocker-monitoring\nelasticsearch-monitoring\nfaro-frontend-observa"
},
{
"path": ".github/workflows/check-image-versions.yml",
"chars": 3174,
"preview": "name: check-image-versions\n\n# Drift guard: every ${VAR:-default} fallback in a docker-compose file\n# must match the valu"
},
{
"path": ".github/workflows/validate-k8s-scenarios.yml",
"chars": 12123,
"preview": "name: validate-k8s-scenarios\n\n# Lightweight validation for k8s scenarios under k8s/. Mirrors the\n# defense-in-depth post"
},
{
"path": ".github/workflows/validate-scenarios.yml",
"chars": 16971,
"preview": "name: validate-scenarios\n\n# Boots every scenario whose files were touched by the PR, after a CVE\n# scan of every image t"
},
{
"path": ".gitignore",
"chars": 3139,
"preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packagi"
},
{
"path": "CLAUDE.md",
"chars": 3210,
"preview": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## "
},
{
"path": "LICENSE",
"chars": 11357,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "README.md",
"chars": 12138,
"preview": "<p align=\"center\">\n <img src=\"./img/banner.png\" alt=\"Grafana Alloy Scenarios Banner\" width=\"300\"/>\n</p>\n\n# Grafana Allo"
},
{
"path": "app-instrumentation/logging/popular-logging-frameworks/README.md",
"chars": 7276,
"preview": "# App Instrumentation - Structured Logging with Alloy Parsing\n\nThis directory contains a comprehensive **Alloy tutorial*"
},
{
"path": "app-instrumentation/logging/popular-logging-frameworks/alloy/config.alloy",
"chars": 2890,
"preview": "// ###############################\n// #### Main Logging Configuration ####\n// ###############################\n\n// Import"
},
{
"path": "app-instrumentation/logging/popular-logging-frameworks/alloy/helper.alloy",
"chars": 18660,
"preview": "declare \"app_logs_parser\" {\n // argument.write_to is a required argument that specifies where parsed\n // log lines are"
},
{
"path": "app-instrumentation/logging/popular-logging-frameworks/cpp/CMakeLists.txt",
"chars": 757,
"preview": "cmake_minimum_required(VERSION 3.16)\nproject(LoggingExample)\n\nset(CMAKE_CXX_STANDARD 17)\nset(CMAKE_CXX_STANDARD_REQUIRED"
},
{
"path": "app-instrumentation/logging/popular-logging-frameworks/cpp/Dockerfile",
"chars": 432,
"preview": "FROM ubuntu:26.04@sha256:f3d28607ddd78734bb7f71f117f3c6706c666b8b76cbff7c9ff6e5718d46ff64\n\n# Install build dependencies\n"
},
{
"path": "app-instrumentation/logging/popular-logging-frameworks/cpp/main.cpp",
"chars": 1385,
"preview": "#include <spdlog/spdlog.h>\n#include <spdlog/sinks/stdout_color_sinks.h>\n#include <chrono>\n#include <thread>\n\nint main() "
},
{
"path": "app-instrumentation/logging/popular-logging-frameworks/csharp/Dockerfile",
"chars": 417,
"preview": "FROM mcr.microsoft.com/dotnet/sdk:9.0@sha256:0300d42309afd86168fa57d62db79020a34ee396d39c9634844b9c0ab285ea55 AS build\nW"
},
{
"path": "app-instrumentation/logging/popular-logging-frameworks/csharp/LoggingExample.csproj",
"chars": 476,
"preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n <PropertyGroup>\n <OutputType>Exe</OutputType>\n <TargetFramework>net9.0</Targe"
},
{
"path": "app-instrumentation/logging/popular-logging-frameworks/csharp/Program.cs",
"chars": 2322,
"preview": "using Microsoft.Extensions.Logging;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Hosting;\n"
},
{
"path": "app-instrumentation/logging/popular-logging-frameworks/docker-compose.coda.yml",
"chars": 1274,
"preview": "services:\n javascript-logging:\n build:\n context: ./javascript\n dockerfile: Dockerfile\n container_name: "
},
{
"path": "app-instrumentation/logging/popular-logging-frameworks/docker-compose.yml",
"chars": 2661,
"preview": "version: '3.8'\n\nservices:\n javascript-logging:\n build:\n context: ./javascript\n dockerfile: Dockerfile\n "
},
{
"path": "app-instrumentation/logging/popular-logging-frameworks/go/Dockerfile",
"chars": 300,
"preview": "FROM golang:1.26-alpine@sha256:91eda9776261207ea25fd06b5b7fed8d397dd2c0a283e77f2ab6e91bfa71079d\n\nWORKDIR /app\n\n# Copy go"
},
{
"path": "app-instrumentation/logging/popular-logging-frameworks/go/go.mod",
"chars": 115,
"preview": "module logging-example\n\ngo 1.23\n\nrequire go.uber.org/zap v1.28.0\n\nrequire go.uber.org/multierr v1.10.0 // indirect\n"
},
{
"path": "app-instrumentation/logging/popular-logging-frameworks/go/go.sum",
"chars": 1294,
"preview": "github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.m"
},
{
"path": "app-instrumentation/logging/popular-logging-frameworks/go/main.go",
"chars": 2681,
"preview": "package main\n\nimport (\n\t\"errors\"\n\t\"time\"\n\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zapcore\"\n)\n\nfunc main() {\n\t// Configure Z"
},
{
"path": "app-instrumentation/logging/popular-logging-frameworks/java/App.java",
"chars": 3891,
"preview": "import org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.slf4j.MDC;\n\npublic class App {\n private static fi"
},
{
"path": "app-instrumentation/logging/popular-logging-frameworks/java/Dockerfile",
"chars": 1360,
"preview": "FROM openjdk:26-slim@sha256:63814a9d8bbea6d39d5ce9c91843bec5e9d9d1d1bc2bade4bb57ba70c0839553\n\nWORKDIR /app\n\n# Download S"
},
{
"path": "app-instrumentation/logging/popular-logging-frameworks/java/logback.xml",
"chars": 639,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<configuration>\n <!-- Console appender with standard format -->\n <appender "
},
{
"path": "app-instrumentation/logging/popular-logging-frameworks/javascript/Dockerfile",
"chars": 405,
"preview": "FROM node:24-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f\n\nWORKDIR /app\n\n# Create pack"
},
{
"path": "app-instrumentation/logging/popular-logging-frameworks/javascript/app.js",
"chars": 2878,
"preview": "#!/usr/bin/env node\n\n// Pino's primary usage writes ndjson to `stdout`:\nconst pino = require('pino')()\n\n// However, if \""
},
{
"path": "app-instrumentation/logging/popular-logging-frameworks/loki-config.yaml",
"chars": 754,
"preview": "\n# This is a complete configuration to deploy Loki backed by the filesystem.\n# The index will be shipped to the storage "
},
{
"path": "app-instrumentation/logging/popular-logging-frameworks/php/Dockerfile",
"chars": 455,
"preview": "FROM php:8.5-cli-alpine@sha256:6ca76906d789edfac74e5f109c800b71e571bd313277133eaddc079733ee0b65\n\nWORKDIR /app\n\n# Install"
},
{
"path": "app-instrumentation/logging/popular-logging-frameworks/php/app.php",
"chars": 2248,
"preview": "<?php\n\nrequire_once 'vendor/autoload.php';\n\nuse Monolog\\Logger;\nuse Monolog\\Handler\\StreamHandler;\nuse Exception;\n\n// Cr"
},
{
"path": "app-instrumentation/logging/popular-logging-frameworks/python/Dockerfile",
"chars": 170,
"preview": "FROM python:3.12-slim@sha256:46cb7cc2877e60fbd5e21a9ae6115c30ace7a077b9f8772da879e4590c18c2e3\n\nWORKDIR /app\n\nCOPY app.py"
},
{
"path": "app-instrumentation/logging/popular-logging-frameworks/python/app.py",
"chars": 1207,
"preview": "#!/usr/bin/env python3\n\nimport logging\nimport time\n\n# Configure logging\nlogging.basicConfig(\n level=logging.DEBUG,\n "
},
{
"path": "aws-firehose-logs/README.md",
"chars": 3989,
"preview": "# AWS Kinesis Data Firehose to Loki — no AWS account required\n\nDemonstrates `loki.source.awsfirehose`, the HTTP receiver"
},
{
"path": "aws-firehose-logs/config.alloy",
"chars": 1395,
"preview": "// AWS Kinesis Data Firehose → Loki, no AWS account required.\n//\n// `loki.source.awsfirehose` is just an HTTP endpoint t"
},
{
"path": "aws-firehose-logs/docker-compose.yml",
"chars": 1636,
"preview": "services:\n\n loki:\n image: grafana/loki:${GRAFANA_LOKI_VERSION:-3.6.10}\n ports:\n - \"3100:3100/tcp\"\n volume"
},
{
"path": "aws-firehose-logs/firehose_sender.py",
"chars": 3870,
"preview": "\"\"\"Fake AWS Kinesis Firehose producer for the aws-firehose-logs scenario.\n\nGenerates synthetic VPC-flow-style log batche"
},
{
"path": "aws-firehose-logs/loki-config.yaml",
"chars": 659,
"preview": "auth_enabled: false\n\nlimits_config:\n allow_structured_metadata: true\n volume_enabled: true\n\nserver:\n http_listen_port"
},
{
"path": "blackbox-probing/README.md",
"chars": 1757,
"preview": "# Blackbox Probing\n\nThis scenario demonstrates **synthetic monitoring** and **HTTP endpoint probing** using Grafana Allo"
},
{
"path": "blackbox-probing/config.alloy",
"chars": 779,
"preview": "// --- Remote Write to Prometheus ---\nprometheus.remote_write \"remote\" {\n\tendpoint {\n\t\turl = \"http://prometheus:9090/api"
},
{
"path": "blackbox-probing/docker-compose.coda.yml",
"chars": 146,
"preview": "services:\n nginx:\n image: nginx:latest@sha256:1881968aff6f7cdcc4b888c00a11f4ce241ad7ec957e0cb4a9e19e93a3ff87ea\n p"
},
{
"path": "blackbox-probing/docker-compose.yml",
"chars": 1420,
"preview": "\nservices:\n\n nginx:\n image: nginx:latest@sha256:1881968aff6f7cdcc4b888c00a11f4ce241ad7ec957e0cb4a9e19e93a3ff87ea\n "
},
{
"path": "blackbox-probing/prom-config.yaml",
"chars": 93,
"preview": "# Minimal Prometheus configuration\nglobal:\n scrape_interval: 15s\n evaluation_interval: 15s\n"
},
{
"path": "cloudwatch-metrics/README.md",
"chars": 3448,
"preview": "# AWS CloudWatch metrics — no AWS account required\n\nDemonstrates `prometheus.exporter.cloudwatch`, Alloy's built-in wrap"
},
{
"path": "cloudwatch-metrics/config.alloy",
"chars": 1406,
"preview": "// AWS CloudWatch metrics → Prometheus — no AWS account required.\n//\n// Uses LocalStack to emulate CloudWatch locally. A"
},
{
"path": "cloudwatch-metrics/docker-compose.yml",
"chars": 2676,
"preview": "services:\n\n # LocalStack emulates the CloudWatch + STS APIs locally.\n # No real AWS account or credentials needed.\n l"
},
{
"path": "cloudwatch-metrics/prom-config.yaml",
"chars": 58,
"preview": "global:\n scrape_interval: 15s\n evaluation_interval: 15s\n"
},
{
"path": "cloudwatch-metrics/seed-metrics.py",
"chars": 1367,
"preview": "\"\"\"\nCloudWatch metric seeder for LocalStack.\n\nPushes synthetic EC2 CPUUtilization data points into LocalStack every\nINTE"
},
{
"path": "coda",
"chars": 2486,
"preview": "#!/usr/bin/env bash\nset -euo pipefail\n\n# On Coda VMs the repo lives at /opt/alloy-scenarios and this script is\n# symlink"
},
{
"path": "continuous-profiling/README.md",
"chars": 3121,
"preview": "# Continuous Profiling\n\nThis scenario demonstrates continuous profiling of a Go application using Grafana Alloy's `pyros"
},
{
"path": "continuous-profiling/app/go.mod",
"chars": 21,
"preview": "module demo\n\ngo 1.23\n"
},
{
"path": "continuous-profiling/app/main.go",
"chars": 661,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n\t\"net/http\"\n\t_ \"net/http/pprof\"\n\t\"time\"\n)\n\nfunc cpuIntensive() {\n\tfor {\n\t\tsum"
},
{
"path": "continuous-profiling/config.alloy",
"chars": 595,
"preview": "livedebugging {\n\tenabled = true\n}\n\n// Scrape pprof profiles from the demo Go application\npyroscope.scrape \"default\" {\n\tt"
},
{
"path": "continuous-profiling/docker-compose.coda.yml",
"chars": 228,
"preview": "services:\n demo-app:\n image: golang:1.26@sha256:2981696eed011d747340d7252620932677929cce7d2d539602f56a8d7e9b660b\n "
},
{
"path": "continuous-profiling/docker-compose.yml",
"chars": 1663,
"preview": "\nservices:\n # Demo Go application with pprof endpoints\n demo-app:\n image: golang:1.26@sha256:2981696eed011d747340d7"
},
{
"path": "docker-monitoring/README.md",
"chars": 862,
"preview": "# Docker Monitoring with Grafana Alloy\n\nThis example demonstrates how to monitor Docker containers using Grafana Alloy.\n"
},
{
"path": "docker-monitoring/config.alloy",
"chars": 2335,
"preview": "// ###############################\n// #### Metrics Configuration ####\n// ###############################\n\n// Host Cadvis"
},
{
"path": "docker-monitoring/docker-compose.yml",
"chars": 2006,
"preview": "version: '3'\nservices:\n loki:\n image: grafana/loki:${GRAFANA_LOKI_VERSION:-3.6.10}\n ports:\n - \"3100:3100\"\n "
},
{
"path": "docker-monitoring/grafana/datasources/default.yml",
"chars": 93,
"preview": "apiVersion: 1\ndatasources:\n- name: Loki\n type: loki\n access: proxy\n url: http://loki:3100\n"
},
{
"path": "docker-monitoring/loki-config.yaml",
"chars": 1308,
"preview": "\n# This is a complete configuration to deploy Loki backed by the filesystem.\n# The index will be shipped to the storage "
},
{
"path": "elasticsearch-monitoring/README.md",
"chars": 1691,
"preview": "# Elasticsearch Monitoring with Grafana Alloy\n\nThis scenario demonstrates how to monitor an Elasticsearch instance using"
},
{
"path": "elasticsearch-monitoring/config.alloy",
"chars": 621,
"preview": "// Elasticsearch Monitoring with Grafana Alloy\n// This configuration scrapes Elasticsearch metrics using the built-in pr"
},
{
"path": "elasticsearch-monitoring/docker-compose.coda.yml",
"chars": 322,
"preview": "services:\n elasticsearch:\n image: docker.elastic.co/elasticsearch/elasticsearch:8.17.0@sha256:2f602552550869fb29b6fd"
},
{
"path": "elasticsearch-monitoring/docker-compose.yml",
"chars": 1624,
"preview": "services:\n elasticsearch:\n image: docker.elastic.co/elasticsearch/elasticsearch:8.17.0@sha256:2f602552550869fb29b6fd"
},
{
"path": "elasticsearch-monitoring/prom-config.yaml",
"chars": 58,
"preview": "global:\n scrape_interval: 15s\n evaluation_interval: 15s\n"
},
{
"path": "faro-frontend-observability/README.md",
"chars": 2190,
"preview": "# Faro Frontend Observability\n\nThis scenario demonstrates collecting frontend web telemetry using Grafana Alloy's `faro."
},
{
"path": "faro-frontend-observability/app/index.html",
"chars": 2715,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width"
},
{
"path": "faro-frontend-observability/config.alloy",
"chars": 348,
"preview": "livedebugging {\n\tenabled = true\n}\n\n// Receive frontend telemetry from the Faro Web SDK\nfaro.receiver \"default\" {\n\tserver"
},
{
"path": "faro-frontend-observability/docker-compose.coda.yml",
"chars": 192,
"preview": "services:\n web:\n image: nginx:latest@sha256:1881968aff6f7cdcc4b888c00a11f4ce241ad7ec957e0cb4a9e19e93a3ff87ea\n por"
},
{
"path": "faro-frontend-observability/docker-compose.yml",
"chars": 1688,
"preview": "services:\n # Nginx web server serving the demo frontend page\n web:\n image: nginx:latest@sha256:1881968aff6f7cdcc4b8"
},
{
"path": "faro-frontend-observability/loki-config.yaml",
"chars": 645,
"preview": "auth_enabled: false\n\nlimits_config:\n allow_structured_metadata: true\n volume_enabled: true\n\nserver:\n http_listen_port"
},
{
"path": "game-of-tracing/AGENTS.md",
"chars": 16671,
"preview": "# Game of Tracing — Agent Guide\n\n> Canonical guide for any AI coding agent working inside this scenario. Tool-agnostic ("
},
{
"path": "game-of-tracing/CLAUDE.md",
"chars": 5330,
"preview": "# CLAUDE.md — Game of Tracing (Claude Code)\n\n> Claude-specific workflow for this scenario. For architecture, services, O"
},
{
"path": "game-of-tracing/README.md",
"chars": 9555,
"preview": "---\ntitle: A Game of Traces\nmenuTitle: A Game of Traces\ndescription: A grand strategy game with distributed tracing\nweig"
},
{
"path": "game-of-tracing/SPAN_LINKS.md",
"chars": 17433,
"preview": "# Span Links Implementation in Game of Tracing\n\nThis document explains how span links are implemented in the Game of Tra"
},
{
"path": "game-of-tracing/ai_opponent/CLAUDE.md",
"chars": 8632,
"preview": "# ai_opponent/ — Strategic AI Decision Engine\n\n> Algorithmic opponent (not LLM-based) that plays the faction not chosen "
},
{
"path": "game-of-tracing/ai_opponent/Dockerfile",
"chars": 324,
"preview": "FROM python:3.11-slim@sha256:6d85378d88a19cd4d76079817532d62232be95757cb45945a99fec8e8084b9c2\n\nWORKDIR /app\n\nCOPY requir"
},
{
"path": "game-of-tracing/ai_opponent/README.md",
"chars": 2706,
"preview": "# AI Opponent for War of Kingdoms\n\nThis Flask-based AI service provides an intelligent opponent for single-player games "
},
{
"path": "game-of-tracing/ai_opponent/ai_server.py",
"chars": 64716,
"preview": "import os\nimport time\nimport random\nimport requests\nimport threading\nimport atexit\nfrom collections import deque\nfrom fl"
},
{
"path": "game-of-tracing/ai_opponent/requirements.txt",
"chars": 160,
"preview": "flask==3.1.3\nrequests==2.33.1\nopentelemetry-api==1.41.1\nopentelemetry-sdk==1.41.1\nopentelemetry-exporter-otlp==1.41.1\npy"
},
{
"path": "game-of-tracing/ai_opponent/telemetry.py",
"chars": 9885,
"preview": "import os\n\nfrom opentelemetry.sdk.resources import SERVICE_NAME, Resource\nfrom opentelemetry.sdk.trace import TracerProv"
},
{
"path": "game-of-tracing/app/CLAUDE.md",
"chars": 12797,
"preview": "# app/ — Location Servers\n\n> 8 Flask microservices representing map territories in the *War of Kingdoms* game. This doc "
},
{
"path": "game-of-tracing/app/Dockerfile",
"chars": 303,
"preview": "FROM python:3.11-slim@sha256:6d85378d88a19cd4d76079817532d62232be95757cb45945a99fec8e8084b9c2\n\nWORKDIR /app\n\nCOPY requir"
},
{
"path": "game-of-tracing/app/game_config.py",
"chars": 12897,
"preview": "\"\"\"Game configuration for all maps in the game-of-tracing scenario.\n\nEach entry in ``MAPS`` describes a playable map. A "
},
{
"path": "game-of-tracing/app/location_server.py",
"chars": 73649,
"preview": "\"\"\"Location server implementation.\n\nEach of the 8 location containers has a constant ``SLOT_ID`` env var\n(``slot_1`` … `"
},
{
"path": "game-of-tracing/app/requirements.txt",
"chars": 160,
"preview": "flask==3.1.3\nrequests==2.33.1\nopentelemetry-api==1.41.1\nopentelemetry-sdk==1.41.1\nopentelemetry-exporter-otlp==1.41.1\npy"
},
{
"path": "game-of-tracing/app/run_game.py",
"chars": 4799,
"preview": "import os\nimport sys\nimport json\nimport sqlite3\nimport argparse\nimport multiprocessing\nfrom game_config import LOCATIONS"
},
{
"path": "game-of-tracing/app/telemetry.py",
"chars": 13099,
"preview": "import os\n\nfrom opentelemetry.sdk.resources import SERVICE_NAME, Resource\nfrom opentelemetry.sdk.trace import TracerProv"
},
{
"path": "game-of-tracing/config-otel.yaml",
"chars": 891,
"preview": "#\n# OTel Collector YAML Configuration for Game of Tracing\n#\n# This is the OTel-native equivalent of config.alloy for use"
},
{
"path": "game-of-tracing/config.alloy",
"chars": 1300,
"preview": "/*\n * Alloy Configuration for OpenTelemetry Trace Collection with Tail Sampling\n */\n\n// Receive OpenTelemetry traces\note"
},
{
"path": "game-of-tracing/docker-compose-otel.yml",
"chars": 520,
"preview": "# OTel Engine Override\n#\n# Uses Alloy's experimental OTel Engine to run a standard OTel Collector YAML config\n# instead "
},
{
"path": "game-of-tracing/docker-compose.coda.yml",
"chars": 7278,
"preview": "services:\n # Southern Capital\n southern-capital:\n build:\n context: ./app\n dockerfile: Dockerfile\n port"
},
{
"path": "game-of-tracing/docker-compose.yml",
"chars": 10425,
"preview": "version: '3.8'\n\nservices:\n loki:\n image: grafana/loki:${GRAFANA_LOKI_VERSION:-3.6.10}\n ports:\n - \"3100:3100\""
},
{
"path": "game-of-tracing/grafana/dashboards/War of Kingdoms-1747821967780.json",
"chars": 72254,
"preview": "{\n \"apiVersion\": \"dashboard.grafana.app/v2beta1\",\n \"kind\": \"Dashboard\",\n \"metadata\": {\n \"name\": \"game-dashboard\"\n "
},
{
"path": "game-of-tracing/grafana/dashboards/dashboards.yaml",
"chars": 279,
"preview": "apiVersion: 1\nproviders:\n - name: 'game-of-tracing'\n orgId: 1\n folder: ''\n type: file\n disableDeletion: tru"
},
{
"path": "game-of-tracing/grafana/datasources/defaults.yml",
"chars": 1425,
"preview": "apiVersion: 1\ndatasources:\n- name: prometheus\n uid: prometheus\n type: prometheus\n orgId: 1\n url: http://prometheus:9"
},
{
"path": "game-of-tracing/loki-config.yaml",
"chars": 1531,
"preview": "auth_enabled: false\n\nserver:\n http_listen_port: 3100\n grpc_listen_port: 9096\n log_level: debug\n grpc_server_max_conc"
},
{
"path": "game-of-tracing/prom-config.yaml",
"chars": 109,
"preview": "global:\n scrape_interval: 15s\n evaluation_interval: 15s\n\notlp:\n keep_identifying_resource_attributes: true"
},
{
"path": "game-of-tracing/pyroscope-config.yaml",
"chars": 231,
"preview": "---\n# Minimal Pyroscope v2 config for local single-binary demo.\n# v2 defaults (filesystem backend, v1-v2-dual storage) h"
},
{
"path": "game-of-tracing/tempo-config.yaml",
"chars": 2673,
"preview": "stream_over_http_enabled: true\nserver:\n http_listen_port: 3200\n log_level: info\n\n\ncache:\n background:\n writeback_g"
},
{
"path": "game-of-tracing/war_map/CLAUDE.md",
"chars": 10201,
"preview": "# war_map/ — UI + Span-Link Broker\n\n> Flask web UI on port 8080, game session orchestrator, and **owner of the span-link"
},
{
"path": "game-of-tracing/war_map/Dockerfile",
"chars": 318,
"preview": "FROM python:3.11-slim@sha256:6d85378d88a19cd4d76079817532d62232be95757cb45945a99fec8e8084b9c2\n\nWORKDIR /app\n\nCOPY requir"
},
{
"path": "game-of-tracing/war_map/app.py",
"chars": 89552,
"preview": "import os\nimport json\nimport sqlite3\nimport requests\nimport threading\nimport uuid\nimport time\nimport atexit\nfrom flask i"
},
{
"path": "game-of-tracing/war_map/requirements.txt",
"chars": 181,
"preview": "flask==3.1.3\nrequests==2.33.1\npython-dotenv==1.2.2\nopentelemetry-api==1.41.1\nopentelemetry-sdk==1.41.1\nopentelemetry-exp"
},
{
"path": "game-of-tracing/war_map/static/css/style.css",
"chars": 34029,
"preview": "/* ========================================\n Game of Traces - Dark Fantasy Theme\n =================================="
},
{
"path": "game-of-tracing/war_map/telemetry.py",
"chars": 3606,
"preview": "import os\n\nfrom opentelemetry.sdk.resources import SERVICE_NAME, Resource\nfrom opentelemetry.sdk.trace import TracerProv"
},
{
"path": "game-of-tracing/war_map/templates/index.html",
"chars": 9942,
"preview": "{% extends \"layout.html\" %}\n\n{% block title %}\n {% if single_player %}Take the Black — {{ map_meta.display_name }}{% "
},
{
"path": "game-of-tracing/war_map/templates/layout.html",
"chars": 3704,
"preview": "<!DOCTYPE html>\n<html lang=\"en\" data-bs-theme=\"dark\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" conten"
},
{
"path": "game-of-tracing/war_map/templates/map.html",
"chars": 54082,
"preview": "{% extends \"layout.html\" %}\n\n{% block title %}Game Map{% endblock %}\n\n{% block content %}\n<!-- Template variables for Ja"
},
{
"path": "game-of-tracing/war_map/templates/map_picker.html",
"chars": 2906,
"preview": "{% extends \"layout.html\" %}\n\n{% block title %}Pick a Map{% endblock %}\n\n{% block content %}\n<div class=\"faction-hero\">\n "
},
{
"path": "game-of-tracing/war_map/templates/replay.html",
"chars": 6483,
"preview": "{% extends \"layout.html\" %}\n\n{% block title %}Game Replay{% endblock %}\n\n{% block content %}\n<div class=\"row g-3\">\n <"
},
{
"path": "game-of-tracing/war_map/templates/replay_session.html",
"chars": 27941,
"preview": "{% extends \"layout.html\" %}\n\n{% block title %}Session Replay{% endblock %}\n\n{% block content %}\n<div class=\"row g-3\">\n "
},
{
"path": "gelf-log-ingestion/README.md",
"chars": 1455,
"preview": "# GELF Log Ingestion Scenario\n\nThis scenario demonstrates how to ingest GELF (Graylog Extended Log Format) logs using Gr"
},
{
"path": "gelf-log-ingestion/app/main.py",
"chars": 1211,
"preview": "import logging\nimport time\nimport random\nfrom pygelf import GelfUdpHandler\n\nlogger = logging.getLogger(\"gelf-demo\")\nlogg"
},
{
"path": "gelf-log-ingestion/config.alloy",
"chars": 580,
"preview": "livedebugging {\n\tenabled = true\n}\n\n// Receive GELF logs over UDP\nloki.source.gelf \"default\" {\n\tforward_to = [loki.relabe"
},
{
"path": "gelf-log-ingestion/docker-compose.coda.yml",
"chars": 224,
"preview": "services:\n gelf-logger:\n image: python:${PYTHON_VERSION:-3.11-slim}\n container_name: gelf-logger\n volumes:\n "
},
{
"path": "gelf-log-ingestion/docker-compose.yml",
"chars": 1580,
"preview": "\nservices:\n\n # GELF log generator using pygelf\n gelf-logger:\n image: python:${PYTHON_VERSION:-3.11-slim}\n contai"
},
{
"path": "gelf-log-ingestion/loki-config.yaml",
"chars": 753,
"preview": "\n# This is a complete configuration to deploy Loki backed by the filesystem.\n# The index will be shipped to the storage "
},
{
"path": "image-versions.env",
"chars": 2106,
"preview": "# Centralized Docker image versions for all examples.\n#\n# Renovate tracks each variable below — the `# renovate:` annota"
},
{
"path": "k8s/README.md",
"chars": 865,
"preview": "\n# Monitor Kubernetes Grafana Alloy\n\n> Note this scenario works using the K8s Monitoring Helm chart. This abstracts the "
},
{
"path": "k8s/events/README.md",
"chars": 4071,
"preview": "# Kubernetes events to Loki — without the k8s-monitoring Helm chart\n\nA focused scenario showing how `loki.source.kuberne"
},
{
"path": "k8s/events/alloy-config.yaml",
"chars": 1695,
"preview": "# Alloy pipeline as a ConfigMap. Mounted into the alloy Deployment at\n# /etc/alloy/config.alloy.\n#\n# Pipeline:\n# loki."
},
{
"path": "k8s/events/alloy-deployment.yaml",
"chars": 1535,
"preview": "# A single-replica Deployment is the right shape for this scenario:\n# `loki.source.kubernetes_events` watches a cluster-"
},
{
"path": "k8s/events/alloy-rbac.yaml",
"chars": 754,
"preview": "# Minimal RBAC for `loki.source.kubernetes_events`.\n# It needs cluster-wide read/list/watch on events. Nothing else.\napi"
},
{
"path": "k8s/events/grafana-values.yml",
"chars": 712,
"preview": "---\npersistence:\n type: pvc\n enabled: true\n\n# DO NOT DO THIS IN PRODUCTION USECASES\nadminUser: admin\nadminPassword: ad"
},
{
"path": "k8s/events/kind.yml",
"chars": 180,
"preview": "# 1 control-plane + 2 workers — matches the other k8s/ scenarios.\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes"
},
{
"path": "k8s/events/loki-values.yml",
"chars": 1355,
"preview": "---\nloki:\n auth_enabled: false\n commonConfig:\n replication_factor: 1\n schemaConfig:\n configs:\n - from: 202"
},
{
"path": "k8s/logs/README.md",
"chars": 5417,
"preview": "\n# Monitor Kubernetes Logs with Grafana Alloy and Loki\n\n> Note this scenario works using the K8s Monitoring Helm chart. "
},
{
"path": "k8s/logs/grafana-values.yml",
"chars": 712,
"preview": "---\npersistence:\n type: pvc\n enabled: true\n\n# DO NOT DO THIS IN PRODUCTION USECASES\nadminUser: admin\nadminPassword: ad"
},
{
"path": "k8s/logs/k8s-monitoring-values.yml",
"chars": 483,
"preview": "---\ncluster:\n name: meta-monitoring-tutorial\n\ndestinations:\n loki:\n type: loki\n url: http://loki-gateway.meta.sv"
},
{
"path": "k8s/logs/killercoda/loki-values.yml",
"chars": 1612,
"preview": "---\nloki:\n auth_enabled: false\n commonConfig:\n replication_factor: 1\n schemaConfig:\n configs:\n - from: 202"
},
{
"path": "k8s/logs/kind.yml",
"chars": 161,
"preview": "# a cluster with 3 control-plane nodes and 3 workers\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n- role: con"
},
{
"path": "k8s/logs/loki-values.yml",
"chars": 1355,
"preview": "---\nloki:\n auth_enabled: false\n commonConfig:\n replication_factor: 1\n schemaConfig:\n configs:\n - from: 202"
},
{
"path": "k8s/metrics/README.md",
"chars": 2764,
"preview": "\n# Monitor Kubernetes Metrics with Grafana Alloy and Prometheus\n\n> Note this scenario works using the K8s Monitoring Hel"
},
{
"path": "k8s/metrics/grafana-values.yml",
"chars": 434,
"preview": "---\npersistence:\n type: pvc\n enabled: true\n\nadminUser: admin\nadminPassword: adminadminadmin\n\nservice:\n enabled: true\n"
},
{
"path": "k8s/metrics/k8s-monitoring-values.yml",
"chars": 398,
"preview": "---\ncluster:\n name: meta-monitoring-tutorial\n\ndestinations:\n prometheus:\n type: prometheus\n url: http://promethe"
},
{
"path": "k8s/metrics/kind.yml",
"chars": 160,
"preview": "# a cluster with 1 control-plane node and 2 workers\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n- role: cont"
},
{
"path": "k8s/metrics/prometheus-values.yml",
"chars": 333,
"preview": "server:\n persistentVolume:\n enabled: false\n extraFlags:\n - web.enable-remote-write-receiver\n - enable-feature"
},
{
"path": "k8s/profiling/README.md",
"chars": 4089,
"preview": "# Monitor Kubernetes Profiles with Grafana Alloy and Pyroscope\n\n> Note this scenario works using the K8s Monitoring Helm"
},
{
"path": "k8s/profiling/grafana-values.yml",
"chars": 481,
"preview": "---\npersistence:\n type: pvc\n enabled: true\n\nadminUser: admin\nadminPassword: adminadminadmin\n\nservice:\n enabled: true\n"
},
{
"path": "k8s/profiling/k8s-monitoring-values.yml",
"chars": 299,
"preview": "---\ncluster:\n name: meta-monitoring-tutorial\n\ndestinations:\n pyroscope:\n type: pyroscope\n url: http://pyroscope."
},
{
"path": "k8s/profiling/kind.yml",
"chars": 160,
"preview": "# a cluster with 1 control-plane node and 2 workers\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n- role: cont"
},
{
"path": "k8s/profiling/pyroscope-values.yml",
"chars": 163,
"preview": "pyroscope:\n extraArgs:\n store.max-block-duration: 5m\n resources:\n requests:\n cpu: 500m\n memory: 512Mi\n"
},
{
"path": "k8s/tracing/README.md",
"chars": 3187,
"preview": "# Monitor Kubernetes Traces with Grafana Alloy and Tempo\n\n> Note this scenario works using the K8s Monitoring Helm chart"
},
{
"path": "k8s/tracing/grafana-values.yml",
"chars": 414,
"preview": "---\npersistence:\n type: pvc\n enabled: true\n\nadminUser: admin\nadminPassword: adminadminadmin\n\nservice:\n enabled: true\n"
},
{
"path": "k8s/tracing/k8s-monitoring-values.yml",
"chars": 508,
"preview": "---\ncluster:\n name: meta-monitoring-tutorial\n\ndestinations:\n tempo:\n type: otlp\n url: http://tempo.meta.svc.clus"
},
{
"path": "k8s/tracing/kind.yml",
"chars": 160,
"preview": "# a cluster with 1 control-plane node and 2 workers\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n- role: cont"
},
{
"path": "k8s/tracing/tempo-values.yml",
"chars": 310,
"preview": "tempo:\n storage:\n trace:\n backend: local\n local:\n path: /var/tempo/traces\n wal:\n path: "
},
{
"path": "kafka/README.md",
"chars": 1411,
"preview": "# Kafka Scenarios\n\nLearn how to use Grafana Alloy to monitor logs from Kafka.\n\n## Overview\n\nThis demo showcases how to:\n"
},
{
"path": "kafka/config.alloy",
"chars": 929,
"preview": "\n\nlivedebugging {\n enabled = true\n}\n\nloki.source.kafka \"kafka\" {\n brokers = [\"kafka:9092\"]\n topics = [\"alloy-logs\"]\n"
},
{
"path": "kafka/docker-compose.coda.yml",
"chars": 941,
"preview": "services:\n kafka:\n image: 'bitnami/kafka:3.8'\n ports:\n - \"9092:9092\"\n volumes:\n - kafka_data:/bitnam"
},
{
"path": "kafka/docker-compose.yml",
"chars": 2316,
"preview": "version: '3.8'\n\nservices:\n # kafka server instance\n kafka:\n image: 'bitnami/kafka:3.8'\n ports:\n - \"9092:909"
},
{
"path": "kafka/gen_log.sh",
"chars": 1359,
"preview": "#!/usr/bin/env bash\nset -euo pipefail\n\nLEVELS=(info warn error debug)\nAPPS=(test auth payment order catalog)\nMSGS=(\n \"H"
},
{
"path": "kafka/loki-config.yaml",
"chars": 1091,
"preview": "\n# This is a complete configuration to deploy Loki backed by the filesystem.\n# The index will be shipped to the storage "
},
{
"path": "linux/README.md",
"chars": 3380,
"preview": "# Monitoring Linux with Alloy\n\nGrafana Alloy can be used to monitor Linux servers and containers. In this guide, we will"
},
{
"path": "linux/config.alloy",
"chars": 4755,
"preview": "// This block relabels metrics coming from node_exporter to add standard labels\ndiscovery.relabel \"integrations_node_exp"
},
{
"path": "linux/docker-compose.yml",
"chars": 1747,
"preview": "version: '3.8'\n\nservices:\n\n loki:\n image: grafana/loki:${GRAFANA_LOKI_VERSION:-3.6.10}\n ports:\n - 3100:3100/"
},
{
"path": "linux/loki-config.yaml",
"chars": 1091,
"preview": "\n# This is a complete configuration to deploy Loki backed by the filesystem.\n# The index will be shipped to the storage "
},
{
"path": "linux/prom-config.yaml",
"chars": 274,
"preview": "# my global config\nglobal:\n scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minu"
},
{
"path": "log-api-gateway/README.md",
"chars": 2429,
"preview": "# Log API Gateway\n\nThis scenario demonstrates using **Grafana Alloy** as a centralized log gateway via the `loki.source."
},
{
"path": "log-api-gateway/app/producer.py",
"chars": 1628,
"preview": "import requests\nimport time\nimport random\nimport json\n\nALLOY_URL = \"http://alloy:3500/loki/api/v1/push\"\n\nservices = [\n "
},
{
"path": "log-api-gateway/config.alloy",
"chars": 501,
"preview": "livedebugging {\n\tenabled = true\n}\n\n// Accept logs via Loki push API - acts as a centralized log gateway\nloki.source.api "
},
{
"path": "log-api-gateway/docker-compose.coda.yml",
"chars": 232,
"preview": "services:\n log-producer:\n image: python:${PYTHON_VERSION:-3.11-slim}\n container_name: log-producer\n volumes:\n "
},
{
"path": "log-api-gateway/docker-compose.yml",
"chars": 1614,
"preview": "\nservices:\n\n # Python script that pushes logs to Alloy's Loki push API endpoint\n log-producer:\n image: python:${PYT"
},
{
"path": "log-api-gateway/loki-config.yaml",
"chars": 1529,
"preview": "auth_enabled: false\n\nserver:\n http_listen_port: 3100\n grpc_listen_port: 9096\n log_level: debug\n grpc_server_max_conc"
},
{
"path": "log-secret-filtering/README.md",
"chars": 2944,
"preview": "# Log Secret Filtering\n\nDemonstrates how Grafana Alloy's `loki.secretfilter` component automatically redacts secrets fro"
},
{
"path": "log-secret-filtering/app/main.py",
"chars": 975,
"preview": "import time\nimport random\nimport datetime\n\nsecrets = [\n 'Found config: AKIAIOSFODNN7EXAMPLE with secret',\n 'Databa"
},
{
"path": "log-secret-filtering/config.alloy",
"chars": 659,
"preview": "livedebugging {\n\tenabled = true\n}\n\nlocal.file_match \"app_logs\" {\n\tpath_targets = [{\"__path__\" = \"/tmp/logs/*.log\", \"job\""
},
{
"path": "log-secret-filtering/docker-compose.coda.yml",
"chars": 177,
"preview": "services:\n secret-logger:\n image: python:${PYTHON_VERSION:-3.11-slim}\n volumes:\n - ./app/main.py:/app/main.p"
},
{
"path": "log-secret-filtering/docker-compose.yml",
"chars": 1713,
"preview": "services:\n # Python app that periodically logs fake secrets (API keys, passwords, tokens)\n secret-logger:\n image: p"
},
{
"path": "log-secret-filtering/loki-config.yaml",
"chars": 645,
"preview": "auth_enabled: false\n\nlimits_config:\n allow_structured_metadata: true\n volume_enabled: true\n\nserver:\n http_listen_port"
},
{
"path": "logs-file/README.md",
"chars": 483,
"preview": "# File Scenarios\n\nLearn how to use Grafana Alloy to monitor logs from a file.\n\n## Running the Demo\n\n### Step 1: Clone th"
},
{
"path": "logs-file/config.alloy",
"chars": 451,
"preview": "\n\nlivedebugging {\n enabled = true\n}\n\nlocal.file_match \"local_files\" {\n path_targets = [{\"__path__\" = \"/temp/logs/*.l"
},
{
"path": "logs-file/docker-compose.coda.yml",
"chars": 198,
"preview": "services:\n logs-file:\n image: python:${PYTHON_VERSION:-3.11-slim}\n container_name: logs-file\n volumes:\n -"
},
{
"path": "logs-file/docker-compose.yml",
"chars": 1657,
"preview": "version: '3.8'\n\nservices:\n # Syslog simulator using a Python script\n logs-file:\n image: python:${PYTHON_VERSION:-3."
},
{
"path": "logs-file/loki-config.yaml",
"chars": 1091,
"preview": "\n# This is a complete configuration to deploy Loki backed by the filesystem.\n# The index will be shipped to the storage "
},
{
"path": "logs-file/main.py",
"chars": 1411,
"preview": "import logging\nimport time\nimport random\nimport os\n\n# Ensure the /logs directory exists\nlog_directory = \"/logs\"\nlog_file"
},
{
"path": "logs-tcp/README.md",
"chars": 675,
"preview": "# Logs Over TCP Scenario\n\nThis scenario demonstrates how to send TCP logs to Alloy within a JSON format. We then use `lo"
},
{
"path": "logs-tcp/config.alloy",
"chars": 816,
"preview": "\n\nlivedebugging {\n enabled = true\n}\n\nloki.source.api \"loki_push_api\" {\n http {\n listen_address = \"0.0.0.0\"\n "
},
{
"path": "logs-tcp/docker-compose.coda.yml",
"chars": 260,
"preview": "services:\n simulator:\n image: python:${PYTHON_VERSION:-3.11-slim}\n container_name: simulator\n volumes:\n -"
},
{
"path": "logs-tcp/docker-compose.yml",
"chars": 1719,
"preview": "version: '3.8'\n\nservices:\n\n # Syslog simulator using a Python script\n simulator:\n image: python:${PYTHON_VERSION:-3"
},
{
"path": "logs-tcp/loki-config.yaml",
"chars": 1531,
"preview": "auth_enabled: false\n\nserver:\n http_listen_port: 3100\n grpc_listen_port: 9096\n log_level: debug\n grpc_server_max_conc"
},
{
"path": "logs-tcp/simulator.py",
"chars": 2893,
"preview": "import socket\nimport time\nimport os\nimport random\nimport json\nfrom datetime import datetime\n\n# Get the target host and p"
},
{
"path": "mail-house/README.md",
"chars": 501,
"preview": "# Mail House Scenario\n\nLearn how to parse structured logs into Labels and Structured Metadata.\n\n## Running the Demo\n\n###"
},
{
"path": "mail-house/config.alloy",
"chars": 1129,
"preview": "\n\nlivedebugging {\n enabled = true\n}\n\nloki.source.api \"loki_push_api\" {\n http {\n listen_address = \"0.0.0.0\"\n "
},
{
"path": "mail-house/docker-compose.coda.yml",
"chars": 663,
"preview": "services:\n mail-house-01:\n image: python:${PYTHON_VERSION:-3.11-slim}\n volumes:\n - ./main.py:/main.py\n co"
},
{
"path": "mail-house/docker-compose.yml",
"chars": 2059,
"preview": "version: '3.8'\n\nservices:\n mail-house-01:\n image: python:${PYTHON_VERSION:-3.11-slim}\n volumes:\n - ./main.py"
},
{
"path": "mail-house/loki-config.yaml",
"chars": 1091,
"preview": "\n# This is a complete configuration to deploy Loki backed by the filesystem.\n# The index will be shipped to the storage "
},
{
"path": "mail-house/main.py",
"chars": 3592,
"preview": "import random\nimport json\nimport time\nimport socket\nfrom datetime import datetime\nimport os\n\n\n# Get the target host and "
},
{
"path": "memcached-monitoring/README.md",
"chars": 1457,
"preview": "# Memcached Monitoring with Grafana Alloy\n\nThis scenario demonstrates how to monitor a Memcached instance using Grafana "
},
{
"path": "memcached-monitoring/config.alloy",
"chars": 552,
"preview": "// Memcached Monitoring with Grafana Alloy\n// This configuration scrapes Memcached metrics using the built-in prometheus"
},
{
"path": "memcached-monitoring/docker-compose.coda.yml",
"chars": 153,
"preview": "services:\n memcached:\n image: memcached:1.6@sha256:277e0c4f249b118e95ab10e535bae2fa1af772271d9152f3468e58d59348db56\n"
},
{
"path": "memcached-monitoring/docker-compose.yml",
"chars": 1451,
"preview": "services:\n memcached:\n image: memcached:1.6@sha256:277e0c4f249b118e95ab10e535bae2fa1af772271d9152f3468e58d59348db56\n"
},
{
"path": "memcached-monitoring/prom-config.yaml",
"chars": 58,
"preview": "global:\n scrape_interval: 15s\n evaluation_interval: 15s\n"
}
]
// ... and 230 more files (download for full content)
About this extraction
This page contains the full source code of the grafana/alloy-scenarios GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 430 files (1.3 MB), approximately 325.9k tokens, and a symbol index with 346 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.