OpenFlight can ship session logs to Grafana Cloud for long-term storage, querying, and dashboarding. This uses Grafana Alloy to tail JSONL session files and push them to Loki.
Session Logs (JSONL) → Grafana Alloy → Grafana Cloud Loki → Grafana Dashboards
~/openflight_sessions/ (local agent) (cloud storage) (query & visualize)
- What gets shipped: Every JSONL entry — session start/end, shot data (ball speed, club speed, spin, launch angle, carry), trigger events, I/Q readings
- Labels:
app=openflight,host=<hostname>,log_type(shot_detected, session_start, etc.),mode,club - Extracted fields:
ball_speed,club_speed,carry,spin_rpm,launch_v,launch_h,angle_source,club_aoa,club_path,shot_number - Buffering: Local WAL (write-ahead log) buffers during network outages
- Raspberry Pi running OpenFlight
- A Grafana Cloud account (free tier works)
- Log in to Grafana Cloud
- Go to your stack → Connections → Loki
- Click Details to find:
- URL: Loki push endpoint (e.g.,
https://logs-prod-us-central1.grafana.net/loki/api/v1/push) - User: Numeric instance ID
- URL: Loki push endpoint (e.g.,
- Generate an API key with
logs:writescope
sudo ./scripts/setup/setup_alloy.shThis script:
- Adds the Grafana APT repository
- Installs Grafana Alloy
- Writes the Alloy config to
/etc/alloy/config.alloy(fromconfig/alloy.alloy) - Creates a credentials template at
/etc/alloy/credentials.env - Configures the systemd service to run as your user (so it can read session logs)
- Enables the Alloy service
sudo vim /etc/alloy/credentials.envFill in your Grafana Cloud values:
LOKI_URL=https://logs-prod-us-central1.grafana.net/loki/api/v1/push
LOKI_USER=123456
LOKI_API_KEY=glc_your_api_key_heresudo systemctl start alloyOr just run scripts/start-kiosk.sh — it starts Alloy automatically if credentials are configured.
Check Alloy is running:
sudo systemctl status alloyCheck for errors:
sudo journalctl -u alloy -fThen query in Grafana Cloud → Explore → Loki:
{app="openflight"}
# All shot data
{app="openflight", log_type="shot_detected"}
# Shots with a specific club
{app="openflight", log_type="shot_detected", club="driver"}
# Session starts
{app="openflight", log_type="session_start"}
# Filter by ball speed (using JSON parsing)
{app="openflight", log_type="shot_detected"} | json | ball_speed_mph > 150
# Average ball speed over time
avg_over_time({app="openflight", log_type="shot_detected"} | json | unwrap ball_speed_mph [1h])
# Shot count per session
count_over_time({app="openflight", log_type="shot_detected"} [24h])
# Spin detection rate (shots with spin > 0)
count_over_time({app="openflight", log_type="shot_detected"} | json | spin_rpm > 0 [24h])
# Processing failures during a session
{app="openflight", log_type="error"}
OpenFlight writes JSONL files to ~/openflight_sessions/session_<timestamp>_<mode>.jsonl. Each line is a JSON object with a type field:
| Entry Type | Description | Key Fields |
|---|---|---|
session_start |
Session began | mode, camera_enabled, sample_rate |
session_end |
Session ended | duration_seconds, total_shots |
shot_detected |
A shot was detected | ball_speed_mph, club_speed_mph, spin_rpm, launch_angle_vertical, launch_angle_horizontal, angle_source, club_angle_deg, club_path_deg, estimated_carry_yards |
connection |
Device connected | device, port, baud, firmware, radc_available, base_freq |
trigger_event |
Trigger accepted/rejected | accepted, latency_ms, trigger_type |
rolling_buffer_capture |
Raw I/Q capture | num_samples, sample_rate |
error |
Processing or hardware failure | error (message), context (component, stage, exception_type, exception_message, …) |
The config at config/alloy.alloy defines a three-stage pipeline:
- File Discovery (
local.file_match): Watches forsession_*.jsonlfiles - JSON Processing (
loki.process): Extracts fields as labels and structured metadata, uses thetsfield as the log timestamp - Loki Push (
loki.write): Ships to Grafana Cloud with basic auth and retry on rate-limiting
When scripts/start-kiosk.sh runs, it checks:
- Is Alloy installed?
- Is
/etc/alloy/credentials.envconfigured (not empty)? - Is the Alloy service already running?
If credentials are configured but Alloy isn't running, it starts the service automatically.
Run the setup script:
sudo ./scripts/setup/setup_alloy.shThen fill in /etc/alloy/credentials.env.
The credentials file exists but has empty values. Edit it:
sudo vim /etc/alloy/credentials.envYour API key is invalid or missing the logs:write scope. Generate a new key in Grafana Cloud → Loki → Details.
sudo chmod 755 /etc/alloy
sudo chmod 644 /etc/alloy/config.alloy
sudo chmod 600 /etc/alloy/credentials.envThe data directory may not exist:
sudo mkdir -p /var/lib/alloy/data
sudo chown -R $(whoami):$(whoami) /var/lib/alloy- Verify Alloy is running:
sudo systemctl status alloy - Check Alloy logs:
sudo journalctl -u alloy -f - Verify session logs exist:
ls ~/openflight_sessions/ - Make sure session logging is enabled (it's on by default, including mock mode)
| File | Purpose |
|---|---|
config/alloy.alloy |
Alloy pipeline config (source of truth) |
config/credentials.env.example |
Credentials template |
/etc/alloy/config.alloy |
Deployed config (written by setup script) |
/etc/alloy/credentials.env |
Deployed credentials (user fills in) |
scripts/setup/setup_alloy.sh |
Installation and setup script |
| Variable | Description |
|---|---|
LOKI_URL |
Grafana Cloud Loki push endpoint |
LOKI_USER |
Grafana Cloud instance ID (numeric) |
LOKI_API_KEY |
API key with logs:write scope |