Skip to main content

telemetry_flight_recorder 0.1.0 copy "telemetry_flight_recorder: ^0.1.0" to clipboard
telemetry_flight_recorder: ^0.1.0 copied to clipboard

High-reliability, zero-impact silent telemetry and flight recorder for Flutter. Captures console breadcrumbs, storage snapshots, and network traces with zero UI thread overhead.

telemetry_flight_recorder #

High-reliability, zero-impact silent flight recorder and runtime telemetry for Flutter applications. Built for production scale (1M+ active users) with zero frame drops, sub-microsecond log overhead, and automatic Gzip wire compression.


1. Core Architecture & Guiding Principles #

  1. Strict Fire-and-Forget: Telemetry dispatches must never block, await, or disrupt host application flows. If network fails or hardware hangs, errors are silently absorbed.
  2. Delta Log Ingestion: Tracks last_sent_epoch_ms. Each flush only transmits new logs generated since the last push, avoiding redundant data transfer.
  3. In-Memory Gzip Compression: Compresses wire payloads by >85% (average 200 logs shrink from ~22 KB to ~2 KB).
  4. Hardware Keystore Isolation: Wraps FlutterSecureStorage reads in an 800ms race timeout to prevent deadlocks on older Android devices.
  5. PII & Secret Sanitization: Built-in regex redacts tokens (tf_access_token, refresh_token), passwords, PINs, OTPs, and PII before serialization.
  6. Approach C Kill-Switch: Backend can globally bypass all Redis processing with a 1-line check (TELEMETRY_ENABLED !== 'false'), responding in <1ms without requiring app releases.

2. Performance Benchmarks #

Measured on physical ARM64 runtime:

  • Single Log Ingestion: ~1.3 microseconds per entry ($0.0013\text{ ms}$).
  • Gzip Compression (200 Logs): ~1.7 milliseconds.
  • Idle Memory Footprint: $< 50\text{ KB}$ (instant memory drain upon push).
  • UI Thread Impact: 0 frame drops (60/120 fps preserved).

3. Flutter Integration #

pubspec.yaml #

dependencies:
  flutter_flight_recorder:
    path: ../flutter_flight_recorder

lib/main.dart #

import 'package:flutter_flight_recorder/flutter_flight_recorder.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Initialize Crashlytics first so FlightRecorder can chain onto it
  await Firebase.initializeApp();
  FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError;

  // Initialize FlightRecorder
  await FlightRecorder.init(
    FRConfig(
      redisIngestUrl: "https://api.altdrx.com/api/v1/telemetry/report",
      flushInterval: const Duration(minutes: 3),
      appVersion: "15.2.215",
      runtimeAttributesProvider: () => {
        'session.tokenType': SessionHandler.instance.type.name,
        'session.expiryTimestamp': SessionHandler.instance.expiryTimestamp,
        'user.user_id': User.user_id,
      },
    ),
  );

  runApp(const MyApp());
}

Network Logging #

Option A: Automatic Wrapper

final client = FlightRecorder.wrapClient(http.Client());

Option B: In Custom HTTP Interceptor

FlightRecorder.logHttp(
  method: 'POST',
  url: 'https://api.altdrx.com/v1/kyc/verify',
  statusCode: response.statusCode,
  durationMs: durationMs,
);

User Identification #

// On sign in
FlightRecorder.setUserId("usr_98124");

// On sign out
FlightRecorder.clearUserId();

4. Backend Ingestion Gateway (ingest_server.js) #

High-throughput Node.js / Express gateway handling Gzip payloads and Approach C kill-switch:

const express = require('express');
const zlib = require('zlib');
const Redis = require('ioredis');

const app = express();
const redis = new Redis(process.env.REDIS_URL || 'redis://127.0.0.1:6379');

// Raw body parser for Gzip payloads
app.use(express.raw({ type: '*/*', limit: '1mb' }));

app.post('/api/v1/telemetry/report', async (req, res) => {
  // Approach C Kill-Switch: Immediate 200 OK without Redis push
  if (process.env.TELEMETRY_ENABLED === 'false') {
    return res.status(200).json({ status: 'bypassed' });
  }

  try {
    let payloadString;

    // Handle Gzip decompression if encoded
    if (req.headers['content-encoding'] === 'gzip') {
      payloadString = zlib.gunzipSync(req.body).toString('utf-8');
    } else {
      payloadString = req.body.toString('utf-8');
    }

    const doc = JSON.parse(payloadString);
    doc.receivedAt = new Date().toISOString();

    // Push into Redis queue with 5-minute TTL safety net
    await redis.lpush('telemetry:log_queue', JSON.stringify(doc));

    return res.status(200).json({ status: 'queued' });
  } catch (err) {
    // Return 200 to prevent mobile clients from retrying
    return res.status(200).json({ status: 'swallowed_parse_error' });
  }
});

app.listen(3000, () => console.log('Telemetry Ingestion Gateway running on :3000'));

5. Background Redis-to-MongoDB Worker (worker.js) #

const Redis = require('ioredis');
const { MongoClient } = require('mongodb');

const redis = new Redis(process.env.REDIS_URL || 'redis://127.0.0.1:6379');
const mongo = new MongoClient(process.env.MONGO_URI || 'mongodb://127.0.0.1:27017');

async function startWorker() {
  await mongo.connect();
  const db = mongo.db('altdrx_observability');
  const collection = db.collection('device_log_sets');

  // Automatic 5-day TTL index
  await collection.createIndex({ "receivedAt": 1 }, { expireAfterSeconds: 432000 });
  await collection.createIndex({ "investorId": 1, "sentAtEpoch": 1 });
  await collection.createIndex({ "sessionId": 1 });

  console.log('Telemetry Worker active. Draining Redis queue into MongoDB...');

  while (true) {
    try {
      const pipeline = redis.pipeline();
      for (let i = 0; i < 100; i++) {
        pipeline.rpop('telemetry:log_queue');
      }
      const results = await pipeline.exec();

      const batch = results
        .map(([err, val]) => val)
        .filter(Boolean)
        .map(jsonStr => {
          const doc = JSON.parse(jsonStr);
          doc.receivedAt = new Date(doc.receivedAt);
          return doc;
        });

      if (batch.length > 0) {
        await collection.insertMany(batch);
        console.log(`[Worker] Flushed ${batch.length} log sets to MongoDB.`);
      } else {
        await new Promise(r => setTimeout(r, 2000));
      }
    } catch (e) {
      console.error('Worker error:', e);
      await new Promise(r => setTimeout(r, 3000));
    }
  }
}

startWorker();
0
likes
0
points
149
downloads

Publisher

verified publishergunjan.tech

Weekly Downloads

High-reliability, zero-impact silent telemetry and flight recorder for Flutter. Captures console breadcrumbs, storage snapshots, and network traces with zero UI thread overhead.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter, flutter_secure_storage, http, shared_preferences

More

Packages that depend on telemetry_flight_recorder