Release tracking
Record deploys, see which release an issue first appeared in, and link an issue's source frame straight to your code host. Off by default; enable per application in Settings → Plugins → Release tracking.
This page explains what each of the three pieces actually does mechanically — including where the code-host linking has real, current limitations, not just how to click the buttons.
The data model
Three tables, and nothing auto-populates any of them without you telling it to:
releases— deploys you record:version,environment,ref(a git ref/commit),notes,deployed_at. Unique per(version, environment).occurrences.release— every captured event optionally carries a release string. Nothing sets this for you — it's whatever your ingestion supplies.source_maps— uploaded Source Map v3 files, keyed by(release, file), used only for de-minifying browser stack traces.
1. Record deploys
POST ?api=deploys (or Settings → Plugins → Release tracking → Deploys) stores one row: version, environment, ref, notes, timestamp. Recorded deploys show up as markers overlaid on the dashboard's charts, so you can visually line up "the error rate jumped right after this deploy."
There is no CI/CD integration. No GitHub Actions step, no webhook listener, nothing watches your deploy pipeline. If you want this populated automatically, you add a call to your deploy script:
# at the end of your deploy script
curl -X POST "$LOG_LENS_URL/?api=deploys" \
-H "X-Log-Lens-Token: $LOG_LENS_TOKEN" -H "Content-Type: application/json" \
-d "{\"version\":\"$VERSION\",\"environment\":\"production\",\"ref\":\"$GIT_SHA\"}"
Leave it manual and, realistically, it stops getting filled in within a few weeks — that's true of any manual logging step, not specific to Log Lens.
2. Which release did this issue first appear in
This is the part that needs no extra plumbing beyond step 3 below, and is genuinely useful once that's in place: GET ?api=issue-releases&id={id} groups an issue's occurrences by their release value and returns count / first-seen / last-seen per release, plus an overall first_release and last_release. It's a plain aggregation over data you're already storing — no separate "which release introduced this" tracking system, just grouping by the value in occurrences.release.
3. Where occurrences.release actually comes from
Nothing in Log Lens detects your application version. release is set only when whatever's sending the event includes one:
- A pushed event via
?api=ingest/ one of the capture SDKs with"release": "v2.4.1"in the payload (the Laravel reporter can set this fromLOG_LENS_RELEASEin.envautomatically). - A connector that happens to supply one.
File-based log parsing (Laravel, nginx, console) does not tag a release on its own — if your only ingestion path is parsing log files, release stays null on every occurrence and this whole feature has nothing to show, regardless of whether the plugin is enabled.
4. Code-host links — read this before relying on it
Settings → Release tracking → Code host stores a repo_url_template (e.g. https://github.com/org/repo/blob/{ref}/{path}#L{line}) and a default_ref (e.g. main). An issue's source frame — something like app/Services/Checkout.php:42, extracted from a stack trace during parsing — gets substituted into that template to produce a "View source" link in the inspector.
This is pure string substitution with no verification — there is no GitHub/GitLab API call, no OAuth, nothing that checks the resulting URL actually resolves. Two concrete gaps follow from that, worth knowing about before you configure it:
The path is usually an absolute server path, not a repo-relative one
The source frame is captured from your stack trace exactly as PHP wrote it — an absolute filesystem path, e.g.:
/var/www/releases/20260818120000/app/Services/PaymentService.php:142
The link builder only strips a single leading /. It does not know how to convert that into the repo-relative path GitHub expects (app/Services/PaymentService.php). So unless your deploy layout happens to put the repo root exactly at your web/document root with no wrapper directories, the generated link includes your server's deploy-path segments and 404s on your code host:
https://github.com/org/repo/blob/main/var/www/releases/20260818120000/app/Services/PaymentService.php#L142
Typical Forge/Envoyer/Docker-style deploy layouts (timestamped release directories, current/ symlinks, container paths) all hit this. It only works cleanly today if your absolute path already starts exactly where your repo does.
The {ref} is always your one global default, never the release the bug actually shipped in
sourceLink() always uses the single default_ref you configured — it does not look up the ref you recorded for the specific release the issue first appeared in (step 1), even though that's sitting right there in the releases table. So an old issue from v2.3.0 links to whatever main looks like today, not the code as it was at v2.3.0 — if the file has since been refactored, moved, or had lines added/removed above it, the link's line number can point at the wrong code entirely.
So: the "which release" attribution (§2) is solid and needs no caveat. The code-host link (§4) is a real idea with a genuine implementation gap for most real-world deploy layouts — treat a broken/wrong link as expected today, not a bug report, unless your repo root and deploy root are literally the same directory.
5. Source maps (browser stack de-minification)
Separate from the above: if a browser event arrives with a release and a minified stack (app.4f2a.js:1:88213), and you've uploaded that release's Source Map v3 file, Log Lens de-minifies each frame at ingest time, before the issue is even stored — so it's saved with Checkout.tsx:88:12 (submitOrder) instead of the bundled location. This one is verified in the sense that it's exact math (base64-VLQ decoding of the map's mappings field, nearest-segment lookup), not a guess — a frame with no matching map is simply left alone, never a wrong guess.
Upload maps from your deploy pipeline right after building:
for map in dist/assets/*.js.map; do
curl -X POST "$LOG_LENS_URL/?api=source-maps&app=web" \
-H "X-Log-Lens-Token: $LOG_LENS_TOKEN" -H "Content-Type: application/json" \
-d "$(jq -c --arg r "$VERSION" --arg f "$(basename "${map%.map}")" --rawfile m "$map" \
-n '{release:$r, file:$f, map:$m}')"
done
file may be a full URL — only its basename is kept, since that's how stack frames are matched.
Related
- HTTP ingest and capture SDKs — the paths that can actually populate
release. - Alerting — spike/new-error rules run over the same occurrences, independent of whether they carry a release.