Docs / Search / LingCode Cloud / Analytics
๐Ÿ“˜ Reference โ— Telemetry Updated 2026-06-11

Analytics & telemetry

TL;DR: The SDK ships with first-party analytics and error reporting โ€” logEvent, logScreen, trace, and recordError, optionally tied to a real setUserId. Events buffer on the client and flush to your backend, so there's no third-party analytics library to add and the data never leaves your control.

Most apps reach for a hosted analytics SDK on day one, and within a week they're shipping user behaviour to someone else's servers and wiring a second account into the build. LingCode Cloud folds the basics โ€” events, screen views, timing, and caught errors โ€” into the same client you already use for data and auth. You call a method, the SDK batches it, and it lands in your own backend. This page covers why that split matters and exactly how to use each call.

Why it's built in

Analytics and crash reporting are the two things almost every app needs and almost every app outsources. Outsourcing them means a third dependency in your bundle, another vendor with a copy of your users' behaviour, and one more dashboard to log into. The SDK's telemetry surface removes all three: the events you record are sent to the same backend that holds your data, batched and flushed by the client, with no extra library to install.

The model is deliberately small. You record four kinds of signal โ€” events, screen views, performance traces, and errors โ€” and you can optionally attach a real user id so the data segments by who did what. Everything else (buffering, batching, retry-on-flush) is handled for you.

Where the data goes

Telemetry is first-party. Events accumulate in a small client-side buffer and are posted to your backend's telemetry endpoint โ€” they do not transit any third-party analytics service. Because the destination is your own LingCode Cloud backend, the analytics data stays exactly where the rest of your app's data lives.

The telemetry surface

Everything hangs off client.telemetry. Throughout the samples below the client is named lingcode โ€” inside /try and the Mac preview that's already window.lingcode; in your own app it's whatever LingCode.createClient(...) returned.

Track an action

The workhorse is logEvent. Give it a name, and optionally a flat bag of scalar params for context:

lingcode.telemetry.logEvent('checkout_started', { plan: 'pro', amount: 29 });

Params are for slicing later โ€” plan tier, item count, the variant a user saw. Keep them scalar (string, number, or boolean) and keep the count at or under 25 keys; nested objects and overflowing keys don't belong in an event.

Track a screen

Screen views get their own shorthand so you don't have to remember an event-name convention:

lingcode.telemetry.logScreen('Pricing');

That's exactly equivalent to a screen_view event for the Pricing screen โ€” call it from each route or view as the user navigates.

Tie analytics to a user

By default events aren't attached to a person. Once someone signs in, opt in by handing the telemetry layer their app user id; clear it again when they sign out so the next session starts clean:

// after a successful sign-in
lingcode.telemetry.setUserId(user.id);

// on sign-out
lingcode.telemetry.setUserId(null);

For segmentation โ€” plan tier, locale, cohort โ€” set user properties. They're merged with whatever you set before, persisted on the client, and attached to subsequent events:

lingcode.telemetry.setUserProperties({ plan: 'pro', locale: 'en-US', cohort: '2026-Q2' });

Capture errors

Reach for recordError in your global error handler and in any catch block worth knowing about. It accepts an Error, a plain { message?, stack? } object, or a bare string:

// a global handler
window.addEventListener('error', (e) => {
  lingcode.telemetry.recordError(e.error ?? e.message);
});

// or in a catch block
try {
  await doRiskyThing();
} catch (err) {
  lingcode.telemetry.recordError(err);
}

Unlike the other calls, recordError flushes immediately โ€” a crash that takes the page down with it shouldn't take its own report along too.

Measure timing

Use trace to record a custom performance measurement in milliseconds โ€” how long an image took to load, how long a query ran, how long a screen took to become interactive:

const start = performance.now();
await loadHeroImage();
lingcode.telemetry.trace('image_load', performance.now() - start);

// or pass a measurement you already have
lingcode.telemetry.trace('image_load', 320);

How buffering and flushing work

Events batch and flush automatically โ€” you don't normally call flush() yourself. The two cases where it earns its keep: before a hard navigation (a full-page redirect or close) where you need delivery guaranteed, and any moment you want buffered events sent right now. recordError already flushes on its own, so crash reports aren't lost. Keep event params scalar and within 25 keys, and lean on setUserProperties for the segmentation you'll filter by โ€” plan tier, locale, cohort.

Flush on the way out

flush() returns a promise so you can await delivery before the page goes away:

// guarantee buffered events are sent before a hard navigation
await lingcode.telemetry.flush();
window.location.href = '/checkout';

Under the hood (REST)

The SDK posts batched events to your backend's telemetry endpoint. You rarely call this directly, but it's here for non-JS clients and debugging:

POST https://lingcode.dev/api/cloud/be/<backend-id>/telemetry
Content-Type: application/json

{
  "events": [
    { "name": "checkout_started", "params": { "plan": "pro", "amount": 29 }, "timestamp": 1718064000000 }
  ]
}

A failed send surfaces as telemetry_failed.

Telemetry is opt-in for identity. Events are anonymous until you call setUserId. Only attach a real user id once you have a basis to โ€” a signed-in session and whatever consent your app requires โ€” and clear it with setUserId(null) on sign-out so a shared device doesn't blend two people's analytics.