SDK GUIDE

Connect your SDK.
See your first event.

Pick a runtime, send an event, and verify its receipt in the console.

01 / PREPARE

Start here

Create one app per runtime in the console. Its ID must match appId in your SDK config. Then issue a key for that app and environment.

!
SDK packages are in this repo

The TypeScript package @maxlogs/sdk and the Flutter package are not published yet. Use workspace or repository paths; these examples match the codebase.

Browser / AppPublic App Keymlpk_…

Can send events, but cannot read them.

Node / SSRServer credentialmlsk_…

Store in server environment variables. Never ship it to clients.

02 / WEB

Browser SDK

Add the SDK to your pnpm workspace and run pnpm install; build it first with pnpm --filter @maxlogs/sdk build. Initialize it once on the client. It captures unhandled errors and fetch and XHR calls, then retries offline events from IndexedDB.

JSONpackage.json
"dependencies": {
  "@maxlogs/sdk": "workspace:*"
}
TypeScriptbrowser.ts
import { createBrowserMaxLogs } from '@maxlogs/sdk/browser'

const logs = createBrowserMaxLogs({
  endpoint: 'https://ingest.example.com/v2/events/batch',
  credential: 'mlpk_<keyId>.<secret>', // public App Key
  appId: 'your-web-app', env: 'production',
  platform: 'web', runtime: 'browser', source: 'browser',
  appVersion: '1.0.0', buildId: 'web-2026-10-15',
})

logs.event('checkout_opened', 'info')
logs.captureError(new Error('Checkout failed'))
// Offline events are stored in IndexedDB and retried after reconnecting.

In Nuxt, use a *.client.ts plugin and a public App Key. Map real URLs with routeTemplateForFetch / routeTemplateForXhr to stable route templates.

03 / RELEASE

Resolve production stacks

Use the same unique buildId in the SDK and release artifacts. Upload every generated JS map before publishing the build; each map is matched to its exact asset path. A missing asset stays unmapped.

Shellrelease
# Keep client .map files private and upload them before publishing JS.
pnpm symbols:upload \
  --url https://logs.example.com \
  --tenant tenant-1 --app your-web-app --platform web \
  --build-id "$BUILD_ID" --maps-dir ./private-maps \
  --asset-prefix _nuxt --probe ./symbol-probe.json

Use an app-scoped release account and keep its password in CI secrets. The probe file contains file, line, column and expectedSource from a known stack. Do not publish .map files. Mini-programs use --platform wechat-miniapp.

03 / SERVER

Node / SSR

Pass the server key through environment variables. The basic SSR SDK has no timer by default; call flush() before a request, job, or process ends.

TypeScriptserver.ts
import { createServerMaxLogs } from '@maxlogs/sdk/node'

const logs = createServerMaxLogs({
  endpoint: process.env.MAXLOGS_INGEST_URL!,
  credential: process.env.MAXLOGS_SECRET!, // mlsk_…; server only
  appId: 'your-server-app', env: 'production',
  platform: 'server', runtime: 'node', source: 'backend',
})

try {
  await handleRequest()
} catch (error) {
  logs.captureError(error)
  throw error
} finally {
  await logs.flush() // wait for receipts before the request ends
}

Node also includes createNodeHttpMiddleware for inbound attempts. A long-running server can opt into the disk-backed adapter below.

TypeScriptdurable-server.ts
import { createDurableServerMaxLogs } from '@maxlogs/sdk/node-durable'

const logs = await createDurableServerMaxLogs({
  endpoint: process.env.MAXLOGS_INGEST_URL!,
  credential: process.env.MAXLOGS_SECRET!,
  appId: 'orders-server', env: 'production',
  platform: 'server', runtime: 'node', source: 'backend',
  spoolDir: '/var/lib/maxlogs/sdk-spool',
})
const observedFetch = logs.instrumentFetch(fetch, () => '/orders/:id')

Create one durable instance per process, use a private persistent directory, and monitor spoolStatus(), onSpoolError, and onSpoolDrop. instrumentFetch wraps only the business fetch passed to it.

04 / CRAWLERS

Search crawler analytics

Search bots usually do not run browser JavaScript, so the browser SDK cannot see their page visits. Add server middleware after your router has matched a request and report only its route template, response status, and elapsed time.

TypeScriptcrawler-middleware.ts
import { classifyCrawler } from '@maxlogs/sdk'

app.use(async (request, response, next) => {
  const started = performance.now()
  await next()
  const source = classifyCrawler(request.headers['user-agent'])
  if (!source) return
  logs.crawlerVisit({
    source, routeTemplate: request.routeTemplate,
    status: response.statusCode, durationMs: performance.now() - started,
  })
  void logs.flush()
})

Use a server credential (mlsk_); public App Keys are rejected for crawler events. Never send raw IPs, full User-Agent strings, or URLs with query parameters. The User-Agent match is a classification hint and does not verify search-engine identity.

05 / MINI-PROGRAM

uni-app mini-program

Use a public App Key and an HTTPS ingest domain added to the WeChat request allowlist. The adapter uses uni.request and a bounded uni.storage queue; it does not install global error hooks by default.

TypeScriptruntime-logs.ts
import { createUniMaxLogs } from '@maxlogs/sdk/uni'

export const logs = createUniMaxLogs({
  endpoint: 'https://logs.example.com/v2/events/batch',
  credential: 'mlpk_<keyId>.<secret>',
  appId: 'your-miniapp', env: 'production',
  appVersion: '1.0.0', buildId: 'miniapp-2026-10',
})
void logs.ready.then(() => logs.flush())
logs.captureError(new Error('Checkout failed'))
// App.vue onShow: void logs.flush()
// Sign out: await logs.dispose(); await logs.clearOfflineData()

Call flush() from App.vue onShow. Wire existing uni.onError and request wrappers to captureError/apiAttempt rather than installing a second global logger. Physical-device delivery and storage limits still need acceptance testing.

05 / MOBILE

Flutter SDK

Add the Flutter adapter by local path in pubspec.yaml and run flutter pub get. It captures framework and Zone errors, flushes on lifecycle changes, and intercepts Dio. With a writable directory, it retries queued events on the next launch.

YAMLpubspec.yaml
dependencies:
  maxlogs_flutter:
    path: ../../packages/sdk_flutter # adjust for your app
Dartmain.dart
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter/widgets.dart';
import 'package:maxlogs_flutter/maxlogs_flutter.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:path_provider/path_provider.dart';

void main() {
  runMaxLogsAppAsync(() async {
    final packageInfo = await PackageInfo.fromPlatform();
    final docsDir = await getApplicationDocumentsDirectory();
    return MaxLogsConfig(
      endpoint: 'https://ingest.example.com/v2/events/batch',
      credential: 'mlpk_…',
      appId: 'your-mobile-app', env: 'production',
      platform: Platform.isIOS ? 'ios' : 'android',
      runtime: 'flutter', source: 'app',
      appVersion: packageInfo.version,
      buildId: packageInfo.buildNumber,
      offlineDir: docsDir.path,
    );
  }, (logs) {
    final dio = Dio()..interceptors.add(MaxLogsDioInterceptor(logs));
    runApp(MyApp(dio: dio));
  });
}

For this example, add package_info_plus and path_provider to the app. runMaxLogsAppAsync gets plugin values and starts runApp in one Zone. Validate native crashes, force-quit, and long offline periods on devices.

05 / DELIVERY

Delivery & retries

Send events to POST /v2/events/batch. Batches hold up to 100 events. Each eventId gets a status; the SDK sends 20 events per batch by default.

accepted

Stored by the server. The SDK can remove it from the queue.

retryable

Keep the original ID and timestamp. Retry with backoff and Retry-After.

rejected

Stop retrying and keep the rejection for diagnosis.

To test the protocol directly, send a batch with a server key:

ShellHTTP API
curl -X POST 'https://ingest.example.com/v2/events/batch' \
  -H 'Authorization: Bearer mlsk_<keyId>.<secret>' \
  -H 'content-type: application/json' \
  --data @batch.json

Find the app and environment in Live or Events. accepted means durable receipt; search may lag briefly.

06 / BOUNDARIES

Privacy & access

  • The SDK redacts common sensitive fields before queuing. Avoid sending passwords, tokens, request bodies, or full query strings.
  • Clients cannot choose a tenant. The ingest service derives tenant, app, and environment from the credential; public App Keys cannot read data.
  • Members see only apps they can access. App owner, editor, and viewer roles control management.
  • Hot storage keeps up to 168 hours by receipt time. Older events remain in verified archives.

Ready to test the flow?

Create an app and key, send a demo event, then find it in the console.