Skip to content
v2.4

Every deploy lands on the same timeline as the latency it moved

When p95 moves, the commit that shipped 24 seconds earlier is already next to it.

brew install signalhq/tap/sgl

free for solo devs · no card · self-host the collector

~/payments — sgl watch
$ sgl watch --env prod --since 15m

  prod · us-east-1, eu-west-1 · 3 services

  ok  api-gateway      p95 184ms  err 0.02%  ▁▂▁▁▂▁
  ok  checkout         p95 221ms  err 0.05%  ▁▁▂▁▁▁
  !!  payments-worker  p95 912ms  err 1.31%  ▁▂▄▆██

  ▲ regression  payments-worker · p95 +611ms
    baseline    301ms (24h rolling)
    onset       14:02:11Z · 11m ago
    deploy      8f21c4e "retry budget for webhooks"
                @nadia · 24s before onset

  → sgl trace payments-worker --deploy 8f21c4e
prod · 3 services · 1 regression · refreshed 4s ago
sgl watch · live service health, deploy-annotated

The regression, drawn: p95 against the deploy timeline

p95 301ms → 912ms at 14:02:11Z · deploy 8f21c4e (@nadia) landed 24s before onset

Signal reads what you already run

No proprietary agent: anything that speaks OTLP works the day you install it, and the rest pipes through sgl ingest.

  • OpenTelemetryOTLP traces + metrics
  • Prometheusscrape + remote write
  • Kubernetespod, node + owner labels
  • PostgreSQLslow query → span link
  • gRPCstreaming span capture
  • Envoysidecar access logs
  • Fluent Bitlog forwarding
  • systemdbare-metal units

What you get

Built for how you already debug

Signal is a CLI first: everything you can click, you can also review in a pull request.

Deploys are first-class events

Every release lands on the timeline with its sha, author and diff. When a regression starts inside a deploy window, Signal attributes it before you open a dashboard.

.github/workflows/deploy.ymlyaml
# runs after your existing deploy step
- name: Mark deploy in Signal
  uses: signalhq/mark-deploy@v2
  with:
    service: payments-worker
    env: prod
    sha: ${{ github.sha }}
    by: ${{ github.actor }}
    fail-on-regression: true

Three lines to instrument

One import at the top of your entrypoint. The SDK wraps HTTP, gRPC and your database driver, and adds nothing to the request path but a buffer write.

instrument.tsts
import { init } from "@signal/node";

init({
  service: "payments-worker",
  env: process.env.SIGNAL_ENV,
  // all errors, half the slow ones, 1% of the rest
  sample: { errors: 1, slow: 0.5, baseline: 0.01 },
  endpoint: process.env.SIGNAL_ENDPOINT,
});

Alerts live in your repo

Thresholds are code, reviewed in pull requests, versioned with the service they watch. No click-ops, no drift between staging and prod.

signal.config.tsts
import { alert, p95 } from "@signal/config";

export default [
  alert({
    when: p95("checkout.submit").above("400ms"),
    for: "5m",
    notify: ["#oncall-payments", "escalate:tier1"],
  }),
];

Query traces like a table

The same query language in the CLI, the dashboard and the API. Pipe it to jq, commit it as a saved view, or page it into an incident doc.

zsh · sgl querybash
$ sgl query 'p95(latency) by route' \
    --service checkout --since 24h

route                 p95      n       24h
/checkout/submit     412ms   18.2k    +38%
/checkout/quote      121ms   44.9k     -2%
/checkout/receipt     88ms   12.1k     +1%
/checkout/webhook    118ms    6.4k     -1%
/checkout/refund     204ms    2.1k     +4%
“Time to attribution went from forty minutes to under three.”
Priya Raghavan · staff SRE, Kesteven Health · 140 services on the self-hosted collector

Sampling that keeps the weird ones

Head sampling throws away exactly the requests you needed. Signal decides after the fact: every error, every p99 outlier, a slice of the boring remainder.

sgl config sampling

Self-host in one binary

The collector is a single static binary: no queue, no cluster to babysit. On the self-hosted plan raw traces never leave your VPC.

sgl collector verify

A bill you can predict

Priced per ingested gigabyte after sampling, not per host and not per seat. Estimate it against your current volume before you commit.

sgl cost --estimateestimator

Changelog

Every release is in the log

216 releases since 2024, each written by the person who shipped it.

full changelog
v2.4.0

Deploy correlation for monorepos

One repository can now emit a deploy event per service, so a forty-package monorepo stops blaming every regression on the same commit.

  • addedmark-deploy@v2 takes a service matrix and fans out one event per changed package.
  • addedsgl trace --deploy <sha> scopes a trace search to a single release window.
  • improvedAttribution weights onset distance: a deploy 24s before onset now outranks one 9m before.
  • fixedRe-running a workflow no longer duplicates the deploy annotation.
v2.3.2

Sampling budget drift on long-lived workers

Processes that never restart drifted above their configured baseline rate, up to 4x on workers older than a week. Thanks to @tvoss for the reproduction.

  • fixedThe baseline sampler refills its token bucket on the hour instead of on process start.
  • fixedsgl doctor reports the effective sample rate, not the configured one.
v2.3.0

Trace-linked alerts

An alert now arrives carrying the three slowest traces that triggered it, so the notification is the first step of the investigation rather than a reason to go find one.

  • addednotify targets accept templates, including trace_url and deploy_url.
  • addedConditions can hold for up to 24h, for slow-burn error budgets.
  • improvedEvaluation moved into the collector: median notification latency 41s to 6s.

Your next regression already has a commit

Install the CLI, point one service at the collector, and the next deploy annotates itself.

sgl init --service payments-worker
  • $0.42 per ingested GB after sampling
  • Free under 5 GB a month, 30-day retention
  • No credit card, no sales call
  • Self-host the collector whenever you want