
Security News
Re-Enabled GitHub Actions Expose Thousands of Repositories to Mini Shai-Hulud
Two compromised GitHub Actions were re-enabled with malicious tags intact, exposing thousands of downstream repositories to Mini Shai-Hulud.
react-native-nitro-logger
Advanced tools
Structured, privacy-tiered logging for React Native — TS core with a native Nitro file/os_log sink
Structured, privacy-tiered logging for React Native. A TypeScript core with native file and system-console sinks built on Nitro Modules.
npm install react-native-nitro-logger react-native-nitro-modules
react-native-nitro-modules is a required peer — this library is built on
Nitro Modules.
import {
Log,
ConsoleDestination,
FileDestination,
createFileSink,
} from 'react-native-nitro-logger';
Log.addDestination(new ConsoleDestination());
Log.addDestination(new FileDestination(createFileSink()));
Log.info('app started');
Log.warning('retrying upload', { attempt: 2, statusCode: 503 });
A scoped logger tags every line with a correlation ID, so one request's lines can be picked out of a busy file. Omit the correlation argument and one is generated for you — which is also what the lint rules want, since a correlation ID must never be derived from an identifier you already have:
const scope = Log.scoped(undefined, 'checkout', { orderKind: 'subscription' });
scope.info('payment authorised');
scope.error('capture failed', { statusCode: 502 });
verbose · debug · info · warning · error · todo
Log.minimumLevel('info'); // globally
Log.subsystem('networking', 'debug'); // and per subsystem
The default profile renders metadata values and is fine for open-source and general application use. Apps handling regulated data should switch to the strict one in their entry point, before anything logs:
import { Log, pub, priv } from 'react-native-nitro-logger';
Log.privacyDefault('private');
Log.metadataKeyCatalog(['requestId', 'statusCode', 'durationMs']);
function onRequestFinished(id: string) {
Log.info('request finished', {
requestId: pub(id), // rendered
statusCode: 200, // redacted — unwrapped, and the default is private
});
}
In a release build (__DEV__ false):
'public' (default) | 'private' | |
|---|---|---|
| bare value | rendered | <private> |
pub(v) | rendered | rendered |
priv(v) | <private> | <private> |
A debug build renders private payloads in the clear, to every destination including the file — so the fail-closed behaviour above is a property of the builds that ship, not of every build. A build where reveal is possible is a build for synthetic data only.
privacyDefault is first-set-wins and tighten-only, so a dependency cannot
loosen your setting.
Message text, metadata keys, subsystems and correlation IDs are public by contract — they are never redacted at runtime. The bundled ESLint plugin constrains them at build time instead, but only once you enable it: installing this package ships the rules, it does not apply them.
// eslint.config.mjs
import nitroLogger from 'react-native-nitro-logger/eslint-plugin';
export default [nitroLogger.configs.strict];
Read docs/PRIVACY.md before using this in an app that handles regulated data. It covers what the contract does and does not promise, the approved-key catalog, and the compliance boundary — in short, a build where reveal is possible is a build for synthetic data only.
const file = new FileDestination(createFileSink(), {
rotation: {
maxFileSizeBytes: 10 * 1024 * 1024,
maxArchivedFilesCount: 5,
compressArchives: true,
maxTotalLogBytes: 50 * 1024 * 1024,
},
});
Log.addDestination(file);
Logs live in app-private storage — noBackupFilesDir on Android, Library/Logs
on iOS. Owner-only modes are applied to every artifact; where the platform
refuses, the sink reports a protection degradation and keeps logging rather
than failing shut.
file.getLogFilePaths(); // for a consent-gated support upload
const outcome = file.purge(5000); // the compliance purge
if (!outcome.durable) {
// Something survived, or the deadline blew. The destination stays fenced
// until an explicit retry, so nothing is written into a pending deletion.
}
purge is synchronous and deadline-bounded. It reports durable (every
pre-purge artifact is gone) separately from rebound (the destination is
writable again), because a complete deletion can still be followed by a failed
reopen, and a caller that resumes on durable alone would write into a
destination with nowhere to put anything. A purge that rebinds opens a fresh,
empty active file — durable is a statement about the data that was there, not
a promise that the directory is left empty.
It clears the file sink's artifacts and nothing else. Anything already
handed to os_log or logcat is outside this library's reach.
NativeConsoleDestination writes into the platform log stream — os_log on iOS,
logcat on Android — so your JavaScript lines interleave with native ones in
Console.app, Xcode, and adb logcat.
import {
Log,
NativeConsoleDestination,
createNativeConsoleSink,
} from 'react-native-nitro-logger';
Log.addDestination(
new NativeConsoleDestination(createNativeConsoleSink(), {
subsystem: 'com.example.app',
category: 'network',
})
);
Unlike ConsoleDestination, this one batches: a drain crosses the bridge once
with two parallel primitive arrays rather than once per entry.
It is best-effort by design, and that is the important thing to know about
it. There is no backpressure and no loss accounting against a disk, because
os_log accepts what it is given and never blocks. What it does account for is
its own buffer — dropped() returns the entries lost at the ceiling or to a
sink that threw, because a diagnostic channel that quietly loses records is
worse than one that admits it. After three consecutive native failures the
destination reports isEnabled === false and the logger stops formatting for
it, so a dead sink stops costing render work as well as bridge calls.
The durable copy is FileDestination's. Nothing here is a system of record,
which is what makes best-effort the right posture.
| Option | Default | Notes |
|---|---|---|
subsystem | the bundle's, chosen natively | Reverse-DNS, as os_log expects. Empty is legal and produces a logger nobody can find. |
category | 'log' | Becomes the logcat tag on Android. |
formatter | DefaultFormatter | os_log wants a line, not a JSON record. |
label | 'native-console' | Registration key for removeDestination. |
minimumLevel | inherited | Per-destination floor. |
batchSize | 64 | Entries per bridge crossing. |
flushIntervalMs | 100 | Idle coalescing window. |
maxPendingEntries | 1000 | Buffer ceiling; oldest survive, newest are dropped. |
Long lines are split rather than silently cut off — the two platforms have genuinely different limits, and docs/PARITY.md records both the chunk sizes and why the split boundary differs between them.
import { installErrorHandler, flushOnBackground } from 'react-native-nitro-logger';
const uninstallHandler = installErrorHandler();
const uninstallFlush = flushOnBackground();
The error handler logs uncaught errors with the message dropped outside dev, the class name reduced to a built-in or a fixed token, and stack frames reduced to a position in a file whose name was already known. It flushes on fatal errors, then chains to whatever handler was installed before it. Both functions return idempotent uninstall handles.
JsonLinesFormatter is the default for files — one JSON object per line, with
a framing guarantee the native crash-tail recovery depends on. It is asserted
byte-identical to SwiftLogger's JSONLogFormatter over a
generated corpus, against the specific revision
docs/PARITY.md pins.
{"timestamp":"2026-07-28T10:15:00.123Z","level":"INFO","message":"app started"}
DefaultFormatter is the human-readable one used by the console destination.
Custom formatters implement LogFormatter; declare framing: 'line' to opt
into crash-tail trimming.
| Supported | Verified | |
|---|---|---|
| iOS | React Native ≥ 0.78 | CI builds, launches and exercises a pristine 0.78 consumer app |
| Android | React Native at the example's version; ≥ 0.78 experimental. minSdk 24 | CI runs the writer's unit suite and builds the example; no minimum-version consumer job exists yet |
New Architecture only. The compatibility claims are split per platform on purpose — see docs/PARITY.md for what backs each one, and for where the two native writers genuinely differ.
Absolute links, because these files are not in the npm tarball — they are
repository documents, and a relative link to them dies for anyone reading this
README inside node_modules.
MIT
FAQs
Structured, privacy-tiered logging for React Native — TS core with a native Nitro file/os_log sink
The npm package react-native-nitro-logger receives a total of 2 weekly downloads. As such, react-native-nitro-logger popularity was classified as not popular.
We found that react-native-nitro-logger demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
Two compromised GitHub Actions were re-enabled with malicious tags intact, exposing thousands of downstream repositories to Mini Shai-Hulud.

Research
/Security News
A malicious Firefox extension fetches its payload after installation to evade detection, steal Google session cookies, and automate account takeover.

Research
/Security News
The compromise affects MemTensor's MemOS, an open source memory framework for large language models (LLMs) and AI agents. Both npm package @memtensor/memos-cloud-openclaw-plugin and the PyPI package MemoryOS are compromised. They drop cross-platform Go binaries that exfiltrate developer secrets.