Skip to content

Stream Android and iOS logs without Android Studio or Console.app, with programmatic Node.js API for log analysis.

License

Notifications You must be signed in to change notification settings

wix-incubator/logkitten

 
 

Repository files navigation

logkitten

Version PRs Welcome MIT License

Stream Android and iOS logs without Android Studio or Console.app, with programmatic Node.js API for log analysis.

This is a Wix Incubator fork of the original logkitty project.

Installation

npm install logkitten
# or
yarn add logkitten

Usage

import { logkitten, Level } from 'logkitten';

// Basic usage - get all Android logs
const emitter = logkitten({
  platform: 'android',
});

emitter.on('entry', (entry) => {
  // Process the structured log entry
  console.log({
    ts: new Date(entry.ts),
    level: entry.level,
    pid: entry.pid,
    tid: entry.tid,
    message: entry.msg,
  });
});

emitter.on('error', (error: Error) => {
  console.error('Logging error:', error.message);
});

// When you're done listening to logs, close the stream programmatically
await emitter.close(); // or emitter.close(callback)

API Reference

Main Function

logkitten(options)

The main function that accepts platform-specific options:

// Android
const androidEmitter = logkitten({
  platform: 'android',
  // Optional: specify custom adb path
  adbPath: '/custom/path/to/adb',
  // Optional: target specific device
  deviceId: 'emulator-5554',
  // Optional: filter entries
  filter: (entry) => entry.tag === 'MyApp'
});

// iOS
const iosEmitter = logkitten({
  platform: 'ios',
  // Optional: target specific simulator (defaults to 'booted')
  deviceId: 'A1B2C3D4-E5F6-7890-ABCD-EF1234567890',
  // Optional: filter entries
  filter: (entry) => entry.processImagePath.includes('/MyApp')
});

Entry Structure

Each log entry is a structured object with platform-specific extensions:

interface Entry {
  msg: string;           // Message content
  ts: number;           // Timestamp in milliseconds
  pid: number;          // Process ID
  tid: number;          // Thread ID
  level: number;        // Log level/priority (Level enum)
}

interface AndroidEntry extends Entry {
  tag: string;          // Log tag
  uid: string;          // User ID
}

interface IosEntry extends Entry {
  category: string;           // iOS category
  formatString: string;       // Format string used for the log message
  processImagePath: string;   // Path to the process executable
  subsystem: string;          // iOS subsystem
  userID: number;            // User ID
}

LogkittenEmitter

The emitter returned by all functions extends Node's EventEmitter and provides:

  • Events

    • entry (arguments: entry: AndroidEntry | IosEntry) – Emitted when a new log entry passes filters.
    • error (arguments: error: Error) – Emitted when parsing fails or the underlying process encounters errors.
    • close – Emitted once the logging process is terminated via .close().
  • Methods

    • close(cb?) – Gracefully stops the underlying logging process and returns a Promise that resolves when cleanup is complete.

Levels

import { Level } from 'logkitten';

// Available priorities (from lowest to highest):
Level.TRACE    // 10 - Trace level (Android verbose)
Level.DEBUG    // 20 - Debug level
Level.INFO     // 30 - Info level
Level.WARN     // 40 - Warning level
Level.ERROR    // 50 - Error level
Level.FATAL    // 60 - Fatal level

Filtering

You can provide a custom filter function to control which log entries are emitted:

const emitter = logkitten({
  platform: 'android',
  filter: (entry) => {
    // Only include entries with specific tags
    return entry.tag === 'MyApp' || entry.tag === 'ReactNative';
  }
});

Filter Examples

Filter by tag (Android):

(entry) => entry.tag === 'MyApp'

Filter by process (both platforms):

(entry) => entry.pid === myAppPid

Filter by message content:

filter: (entry) => /error|warning/i.test(entry.msg)

Filter by subsystem (iOS):

filter: (entry) => entry.subsystem === 'com.mycompany.myapp'

Multi-criteria filtering:

(entry) => {
  const isMyApp = entry.tag === 'MyApp' || entry.processImagePath?.includes('MyApp');
  const isError = entry.level >= Level.ERROR;
  const hasKeyword = entry.msg.includes('network');
  return isMyApp && (isError || hasKeyword);
}

Examples

Log Analysis and Error Tracking

const emitter = logkitten({
  platform: 'android',
});

const errorCount = new Map();

emitter.on('entry', (entry) => {
  if (entry.level >= Level.ERROR) {
    const count = errorCount.get(entry.tag) || 0;
    errorCount.set(entry.tag, count + 1);
  }
});

// Log error summary every 10 seconds
setInterval(() => {
  console.log('Error counts by tag:', Object.fromEntries(errorCount));
}, 10000);

Structured Log Storage

const emitter = logkitten({
  platform: 'ios',
  filter: (entry) => entry.processImagePath.includes('/MyApp'),
});

const logs = [];

emitter.on('entry', (entry) => {
  // Store structured log data
  logs.push({
    timestamp: entry.ts,
    level: entry.level,
    source: entry.subsystem,
    content: entry.msg,
  });

  // Keep only last 1000 entries
  if (logs.length > 1000) {
    logs.shift();
  }
});

Credits

This project is a fork and continuation of the original logkitty by Paweł Trysła (@zamotany), maintained by Wix Incubator.

The original project has been inactive since 2020, and this fork continues development with modern dependencies and bug fixes. While we maintain this fork primarily for our own use, we welcome community contributions and will consider issues and feature requests as time permits.

About

Stream Android and iOS logs without Android Studio or Console.app, with programmatic Node.js API for log analysis.

Topics

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published

Languages

  • TypeScript 96.8%
  • JavaScript 3.2%