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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .bumpy/telemetry-exit-latency.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
varlock: patch
---

Telemetry no longer adds latency to CLI commands. Events still in flight when a command finishes are handed to a detached background process instead of being awaited, so `varlock load` and `varlock/auto-load` take the same time with telemetry on or off.
15 changes: 15 additions & 0 deletions packages/varlock-website/src/content/docs/guides/telemetry.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,21 @@ We track general usage information, and the environment in which `varlock` is be

**We will never collect any of your config files or environment variables.**

## How Events Are Sent

Sending telemetry never delays a command. The CLI does not wait for the network before printing its output or exiting, so the time `varlock load` or `varlock/auto-load` takes is the same with telemetry on or off.

Events are sent in the background while a command runs. Long-running commands such as `varlock run` or `varlock proxy run` confirm delivery while they run and nothing else happens.

If a command finishes before delivery is confirmed (as `varlock load` usually does), the CLI hands the events off and exits immediately:

1. A detached copy of the CLI is started as `varlock --flush-telemetry`, with the pending events passed in its environment (`__VARLOCK_TELEMETRY_EVENTS`) and its stdin/stdout/stderr closed, so it never holds open the pipes or terminal of whatever started varlock. You may briefly see it in `ps` output. Nothing is written to disk.
2. That process sends the events in one request (giving up after 10 seconds) and exits. It records no telemetry of its own (`VARLOCK_TELEMETRY_DISABLED` is set on it) and never starts another process.

Each event carries a random id, so if one reaches us twice (once from the original process and once from the handoff) the duplicate can be recognized and dropped.

This is the same approach used by the Vercel, Netlify, Expo, and Prisma CLIs and by `next dev`. If you would rather varlock never start that background process, opt out using one of the methods below: when telemetry is off, no events are recorded and nothing is spawned.

## How to Opt Out

You can opt out of analytics in three ways:
Expand Down
13 changes: 12 additions & 1 deletion packages/varlock/src/cli/cli-executable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import { commandTelemetry } from './command-telemetry-plugin';
import { VARLOCK_BANNER_COLOR } from '../lib/ascii-art';
import { CliExitError } from './helpers/exit-error';
import { fmt } from './helpers/pretty-format';
import { trackCommand, trackInstall } from './helpers/telemetry';
import {
trackCommand, trackInstall, runTelemetrySender, TELEMETRY_SENDER_FLAG,
} from './helpers/telemetry';
import { InvalidEnvError } from './helpers/invalid-env-error';
import { isArgError, toCliExitError } from './helpers/arg-errors';
import { checkBunVersion } from '../lib/check-bun-version';
Expand Down Expand Up @@ -113,6 +115,15 @@ process.prependOnceListener('beforeExit', () => {

let args = process.argv.slice(2);

// detached telemetry sender spawned by a previous varlock process at exit (see
// handOffUnconfirmedEvents) - deliver its events and exit, running no command
if (args[0] === TELEMETRY_SENDER_FLAG) {
await runTelemetrySender();
commandFinished = true;
gracefulExit();
return;
}

// TODO: remove this once we have a better way to re-trigger help
if (args[0] === 'help') args = ['--help'];

Expand Down
6 changes: 3 additions & 3 deletions packages/varlock/src/cli/commands/run.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,9 +256,9 @@ export const commandFn: TypedGunshiCommandFn<typeof commandSpec> = async (ctx) =

// The schema is resolved and validated by now, and nothing in this process changes it
// afterwards, so send the schema usage event here rather than at exit. A long-running
// child (a server) may only ever end by SIGKILL, in which case an exit-time flush is
// lost. Fire-and-forget: the request completes while the child runs, gracefulExit still
// awaits anything pending, and taking the payload makes the exit-time flush a no-op.
// child (a server) may only ever end by SIGKILL, in which case an exit-time handoff is
// lost. Fire-and-forget: the request completes while the child runs, and taking the
// payload makes the exit-time flush a no-op.
flushSchemaLoadedEvent().catch(() => undefined);

// Forward terminating signals to the child and wait for it, rather than exiting and
Expand Down
Loading
Loading