Skip to content

React Native Logging

React Native’s console.log writes to stdout, which means it shows up as undifferentiated ReactNativeJS noise in the system log. No subsystem, no category, no levels — just a wall of text. For an AI agent trying to debug your app, this is almost useless.

The @quern/react-native-os-logger package routes your JS logs through Apple’s unified logging system (os_log) on iOS and android.util.Log on Android, with proper subsystem and category support. This means Quern can filter, search, and correlate your React Native logs exactly like native app logs.

Terminal window
npm install @quern/react-native-os-logger
cd ios && pod install

Requires React Native 0.76+ (New Architecture / TurboModules).

If you want every console.log in your app to appear in Quern with zero code changes:

import { patchConsole } from '@quern/react-native-os-logger';
// Add this one line at app startup (e.g. index.js or App.tsx)
patchConsole('com.yourcompany.yourapp');

That’s it. All console methods now route through os_log:

console methodos_log levelPersisted on iOS?
console.debug()DEBUGNo
console.info()INFONo
console.log()DEFAULTYes
console.warn()ERRORYes
console.error()ERRORYes

Original console methods are preserved — logs still appear in Metro as usual. Objects and errors are automatically serialized.

The real power comes from creating separate loggers with distinct categories. This lets your agent filter logs by domain:

import { createLogger } from '@quern/react-native-os-logger';
const netLogger = createLogger('com.myapp', 'networking');
const uiLogger = createLogger('com.myapp', 'ui');
const authLogger = createLogger('com.myapp', 'auth');
// In your API layer
netLogger.info('GET /api/users → 200 OK (142ms)');
netLogger.error('POST /api/login → 401 Unauthorized');
// In your components
uiLogger.debug('ProfileScreen rendered in 12ms');
// In your auth flow
authLogger.info('Token refresh started');
authLogger.error('Refresh token expired, redirecting to login');

Each createLogger call creates a separate os_log_t instance on iOS and a distinct Log tag on Android. In Quern, your agent can then ask for “just the networking logs” or “auth errors in the last 5 minutes” and get exactly that.

When your agent starts simulator logging filtered to your subsystem:

start_simulator_logging(subsystem: "com.myapp", level: "debug")

Logs arrive with full metadata:

[info] com.myapp / networking — GET /api/users → 200 OK (142ms)
[error] com.myapp / networking — POST /api/login → 401 Unauthorized
[debug] com.myapp / ui — ProfileScreen rendered in 12ms
[info] com.myapp / auth — Token refresh started

Your agent can then query by category, level, or text search — the same tools that work for native Swift/Kotlin logs work identically for React Native.

You can use both patchConsole and createLogger together. A common pattern:

// Catch all console.* output under the "console" category
patchConsole('com.myapp', 'console');
// Use structured loggers for your own code
const netLogger = createLogger('com.myapp', 'networking');
const authLogger = createLogger('com.myapp', 'auth');

This way, third-party library output (which uses console.log) gets captured under the “console” category, while your own code uses specific categories. Your agent can filter out the library noise and focus on your app’s logs.

LevelUse foriOS behavior
debugVerbose tracing, render times, internal stateNot persisted — only visible when actively streaming
infoNoteworthy events: API calls, screen transitionsNot persisted by default
defaultImportant milestones: app startup, user actionsPersisted to disk
errorFailures that need attentionPersisted, includes caller info
faultShould-never-happen conditionsPersisted across reboots, includes full backtrace

Tip: Use default level (or console.log) for anything you want to survive across app restarts. info and debug are only visible when a log consumer (Quern, Console.app) is actively attached.

On Android, logs appear in logcat with subsystem:category as the tag:

I/com.myapp:networking GET /api/users → 200 OK (142ms)
E/com.myapp:networking POST /api/login → 401 Unauthorized
D/com.myapp:ui ProfileScreen rendered in 12ms

The level mapping: debugLog.d, info/defaultLog.i, errorLog.e, faultLog.wtf.

  • Log at the boundaries. Network request/response, screen navigation, user actions. Don’t log every internal function call.

  • Use distinct categories. networking, ui, auth, storage — whatever makes sense for your app. The more specific your categories, the faster your agent can find what matters.

  • Include IDs. If your API returns a request ID or correlation ID, include it in the log. When your agent sees a failed request in Quern’s network capture, a shared ID makes connecting it to the corresponding log entries instant.

  • Use patchConsole early. Call it before any other code runs (top of index.js). This ensures even early startup logs from React Native internals get captured.