react-native-nitro-logger 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -0
- package/NitroLogger.podspec +29 -0
- package/README.md +214 -0
- package/android/CMakeLists.txt +24 -0
- package/android/build.gradle +124 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/cpp-adapter.cpp +11 -0
- package/android/src/main/java/com/margelo/nitro/nitrologger/AndroidPlatformIo.kt +100 -0
- package/android/src/main/java/com/margelo/nitro/nitrologger/HybridFileSink.kt +292 -0
- package/android/src/main/java/com/margelo/nitro/nitrologger/HybridNativeConsoleSink.kt +23 -0
- package/android/src/main/java/com/margelo/nitro/nitrologger/LogFileWriter.kt +1328 -0
- package/android/src/main/java/com/margelo/nitro/nitrologger/LogRotationPolicy.kt +186 -0
- package/android/src/main/java/com/margelo/nitro/nitrologger/LogSecureFile.kt +80 -0
- package/android/src/main/java/com/margelo/nitro/nitrologger/LogWriterRegistry.kt +420 -0
- package/android/src/main/java/com/margelo/nitro/nitrologger/NativeConsoleWriter.kt +237 -0
- package/android/src/main/java/com/margelo/nitro/nitrologger/NitroLoggerPackage.kt +22 -0
- package/android/src/main/java/com/margelo/nitro/nitrologger/PlatformIo.kt +165 -0
- package/docs/PARITY.md +330 -0
- package/docs/PRIVACY.md +341 -0
- package/eslint-plugin/README.md +238 -0
- package/eslint-plugin/index.js +62 -0
- package/eslint-plugin/rules/literal-subsystem.js +102 -0
- package/eslint-plugin/rules/no-computed-metadata-key.js +174 -0
- package/eslint-plugin/rules/no-derived-correlation.js +171 -0
- package/eslint-plugin/rules/no-dynamic-message.js +60 -0
- package/eslint-plugin/shared.js +1948 -0
- package/ios/Gzip.swift +203 -0
- package/ios/HybridFileSink.swift +291 -0
- package/ios/HybridNativeConsoleSink.swift +20 -0
- package/ios/LogFileWriter.swift +1280 -0
- package/ios/LogSecureFile.swift +296 -0
- package/ios/LogWriterRegistry.swift +444 -0
- package/ios/NativeConsoleWriter.swift +180 -0
- package/lib/module/Logger.js +381 -0
- package/lib/module/Logger.js.map +1 -0
- package/lib/module/ScopedLogger.js +70 -0
- package/lib/module/ScopedLogger.js.map +1 -0
- package/lib/module/config.js +23 -0
- package/lib/module/config.js.map +1 -0
- package/lib/module/destinations/Batcher.js +647 -0
- package/lib/module/destinations/Batcher.js.map +1 -0
- package/lib/module/destinations/ConsoleDestination.js +51 -0
- package/lib/module/destinations/ConsoleDestination.js.map +1 -0
- package/lib/module/destinations/FileDestination.js +360 -0
- package/lib/module/destinations/FileDestination.js.map +1 -0
- package/lib/module/destinations/NativeConsoleDestination.js +170 -0
- package/lib/module/destinations/NativeConsoleDestination.js.map +1 -0
- package/lib/module/destinations/types.js +4 -0
- package/lib/module/destinations/types.js.map +1 -0
- package/lib/module/formatters/DefaultFormatter.js +136 -0
- package/lib/module/formatters/DefaultFormatter.js.map +1 -0
- package/lib/module/formatters/JsonLinesFormatter.js +162 -0
- package/lib/module/formatters/JsonLinesFormatter.js.map +1 -0
- package/lib/module/formatters/timestamp.js +15 -0
- package/lib/module/formatters/timestamp.js.map +1 -0
- package/lib/module/formatters/types.js +4 -0
- package/lib/module/formatters/types.js.map +1 -0
- package/lib/module/index.js +31 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/integrations/appState.js +81 -0
- package/lib/module/integrations/appState.js.map +1 -0
- package/lib/module/integrations/errorHandler.js +166 -0
- package/lib/module/integrations/errorHandler.js.map +1 -0
- package/lib/module/integrations/sanitizeError.js +238 -0
- package/lib/module/integrations/sanitizeError.js.map +1 -0
- package/lib/module/levels.js +35 -0
- package/lib/module/levels.js.map +1 -0
- package/lib/module/metadata.js +52 -0
- package/lib/module/metadata.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/privacy.js +349 -0
- package/lib/module/privacy.js.map +1 -0
- package/lib/module/specs/FileSink.nitro.js +4 -0
- package/lib/module/specs/FileSink.nitro.js.map +1 -0
- package/lib/module/specs/NativeConsoleSink.nitro.js +4 -0
- package/lib/module/specs/NativeConsoleSink.nitro.js.map +1 -0
- package/lib/module/types.js +4 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/utf8.js +42 -0
- package/lib/module/utf8.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/Logger.d.ts +122 -0
- package/lib/typescript/src/Logger.d.ts.map +1 -0
- package/lib/typescript/src/ScopedLogger.d.ts +36 -0
- package/lib/typescript/src/ScopedLogger.d.ts.map +1 -0
- package/lib/typescript/src/config.d.ts +12 -0
- package/lib/typescript/src/config.d.ts.map +1 -0
- package/lib/typescript/src/destinations/Batcher.d.ts +244 -0
- package/lib/typescript/src/destinations/Batcher.d.ts.map +1 -0
- package/lib/typescript/src/destinations/ConsoleDestination.d.ts +27 -0
- package/lib/typescript/src/destinations/ConsoleDestination.d.ts.map +1 -0
- package/lib/typescript/src/destinations/FileDestination.d.ts +200 -0
- package/lib/typescript/src/destinations/FileDestination.d.ts.map +1 -0
- package/lib/typescript/src/destinations/NativeConsoleDestination.d.ts +86 -0
- package/lib/typescript/src/destinations/NativeConsoleDestination.d.ts.map +1 -0
- package/lib/typescript/src/destinations/types.d.ts +19 -0
- package/lib/typescript/src/destinations/types.d.ts.map +1 -0
- package/lib/typescript/src/formatters/DefaultFormatter.d.ts +22 -0
- package/lib/typescript/src/formatters/DefaultFormatter.d.ts.map +1 -0
- package/lib/typescript/src/formatters/JsonLinesFormatter.d.ts +73 -0
- package/lib/typescript/src/formatters/JsonLinesFormatter.d.ts.map +1 -0
- package/lib/typescript/src/formatters/timestamp.d.ts +6 -0
- package/lib/typescript/src/formatters/timestamp.d.ts.map +1 -0
- package/lib/typescript/src/formatters/types.d.ts +27 -0
- package/lib/typescript/src/formatters/types.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +34 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/integrations/appState.d.ts +33 -0
- package/lib/typescript/src/integrations/appState.d.ts.map +1 -0
- package/lib/typescript/src/integrations/errorHandler.d.ts +55 -0
- package/lib/typescript/src/integrations/errorHandler.d.ts.map +1 -0
- package/lib/typescript/src/integrations/sanitizeError.d.ts +89 -0
- package/lib/typescript/src/integrations/sanitizeError.d.ts.map +1 -0
- package/lib/typescript/src/levels.d.ts +9 -0
- package/lib/typescript/src/levels.d.ts.map +1 -0
- package/lib/typescript/src/metadata.d.ts +23 -0
- package/lib/typescript/src/metadata.d.ts.map +1 -0
- package/lib/typescript/src/privacy.d.ts +103 -0
- package/lib/typescript/src/privacy.d.ts.map +1 -0
- package/lib/typescript/src/specs/FileSink.nitro.d.ts +131 -0
- package/lib/typescript/src/specs/FileSink.nitro.d.ts.map +1 -0
- package/lib/typescript/src/specs/NativeConsoleSink.nitro.d.ts +21 -0
- package/lib/typescript/src/specs/NativeConsoleSink.nitro.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +37 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/lib/typescript/src/utf8.d.ts +15 -0
- package/lib/typescript/src/utf8.d.ts.map +1 -0
- package/nitro.json +33 -0
- package/nitrogen/generated/android/c++/JAppendResult.hpp +79 -0
- package/nitrogen/generated/android/c++/JClearOutcome.hpp +88 -0
- package/nitrogen/generated/android/c++/JFlushOutcome.hpp +81 -0
- package/nitrogen/generated/android/c++/JHybridFileSinkSpec.cpp +120 -0
- package/nitrogen/generated/android/c++/JHybridFileSinkSpec.hpp +69 -0
- package/nitrogen/generated/android/c++/JHybridNativeConsoleSinkSpec.cpp +71 -0
- package/nitrogen/generated/android/c++/JHybridNativeConsoleSinkSpec.hpp +64 -0
- package/nitrogen/generated/android/c++/JRejectReason.hpp +64 -0
- package/nitrogen/generated/android/c++/JRotationConfig.hpp +77 -0
- package/nitrogen/generated/android/c++/JSinkStatus.hpp +69 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrologger/AppendResult.kt +76 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrologger/ClearOutcome.kt +66 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrologger/FlushOutcome.kt +81 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrologger/HybridFileSinkSpec.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrologger/HybridNativeConsoleSinkSpec.kt +58 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrologger/RejectReason.kt +25 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrologger/RotationConfig.kt +76 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrologger/SinkStatus.kt +66 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrologger/nitrologgerOnLoad.kt +35 -0
- package/nitrogen/generated/android/nitrologger+autolinking.cmake +83 -0
- package/nitrogen/generated/android/nitrologger+autolinking.gradle +27 -0
- package/nitrogen/generated/android/nitrologgerOnLoad.cpp +70 -0
- package/nitrogen/generated/android/nitrologgerOnLoad.hpp +34 -0
- package/nitrogen/generated/ios/NitroLogger+autolinking.rb +62 -0
- package/nitrogen/generated/ios/NitroLogger-Swift-Cxx-Bridge.cpp +50 -0
- package/nitrogen/generated/ios/NitroLogger-Swift-Cxx-Bridge.hpp +216 -0
- package/nitrogen/generated/ios/NitroLogger-Swift-Cxx-Umbrella.hpp +69 -0
- package/nitrogen/generated/ios/NitroLoggerAutolinking.mm +41 -0
- package/nitrogen/generated/ios/NitroLoggerAutolinking.swift +38 -0
- package/nitrogen/generated/ios/c++/HybridFileSinkSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridFileSinkSpecSwift.hpp +150 -0
- package/nitrogen/generated/ios/c++/HybridNativeConsoleSinkSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridNativeConsoleSinkSpecSwift.hpp +87 -0
- package/nitrogen/generated/ios/swift/AppendResult.swift +60 -0
- package/nitrogen/generated/ios/swift/ClearOutcome.swift +50 -0
- package/nitrogen/generated/ios/swift/FlushOutcome.swift +59 -0
- package/nitrogen/generated/ios/swift/HybridFileSinkSpec.swift +61 -0
- package/nitrogen/generated/ios/swift/HybridFileSinkSpec_cxx.swift +227 -0
- package/nitrogen/generated/ios/swift/HybridNativeConsoleSinkSpec.swift +56 -0
- package/nitrogen/generated/ios/swift/HybridNativeConsoleSinkSpec_cxx.swift +148 -0
- package/nitrogen/generated/ios/swift/RejectReason.swift +48 -0
- package/nitrogen/generated/ios/swift/RotationConfig.swift +93 -0
- package/nitrogen/generated/ios/swift/SinkStatus.swift +44 -0
- package/nitrogen/generated/shared/c++/AppendResult.hpp +105 -0
- package/nitrogen/generated/shared/c++/ClearOutcome.hpp +96 -0
- package/nitrogen/generated/shared/c++/FlushOutcome.hpp +107 -0
- package/nitrogen/generated/shared/c++/HybridFileSinkSpec.cpp +28 -0
- package/nitrogen/generated/shared/c++/HybridFileSinkSpec.hpp +84 -0
- package/nitrogen/generated/shared/c++/HybridNativeConsoleSinkSpec.cpp +22 -0
- package/nitrogen/generated/shared/c++/HybridNativeConsoleSinkSpec.hpp +64 -0
- package/nitrogen/generated/shared/c++/RejectReason.hpp +84 -0
- package/nitrogen/generated/shared/c++/RotationConfig.hpp +103 -0
- package/nitrogen/generated/shared/c++/SinkStatus.hpp +95 -0
- package/package.json +151 -0
- package/src/Logger.ts +417 -0
- package/src/ScopedLogger.ts +94 -0
- package/src/config.ts +25 -0
- package/src/destinations/Batcher.ts +805 -0
- package/src/destinations/ConsoleDestination.ts +60 -0
- package/src/destinations/FileDestination.ts +473 -0
- package/src/destinations/NativeConsoleDestination.ts +220 -0
- package/src/destinations/types.ts +19 -0
- package/src/formatters/DefaultFormatter.ts +150 -0
- package/src/formatters/JsonLinesFormatter.ts +195 -0
- package/src/formatters/timestamp.ts +12 -0
- package/src/formatters/types.ts +29 -0
- package/src/index.tsx +106 -0
- package/src/integrations/appState.ts +111 -0
- package/src/integrations/errorHandler.ts +223 -0
- package/src/integrations/sanitizeError.ts +287 -0
- package/src/levels.ts +35 -0
- package/src/metadata.ts +52 -0
- package/src/privacy.ts +389 -0
- package/src/specs/FileSink.nitro.ts +144 -0
- package/src/specs/NativeConsoleSink.nitro.ts +21 -0
- package/src/types.ts +45 -0
- package/src/utf8.ts +39 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Amir Shayegh
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
SOFTWARE.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = "NitroLogger"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = package["description"]
|
|
9
|
+
s.homepage = package["homepage"]
|
|
10
|
+
s.license = package["license"]
|
|
11
|
+
s.authors = package["author"]
|
|
12
|
+
|
|
13
|
+
s.platforms = { :ios => min_ios_version_supported }
|
|
14
|
+
s.source = { :git => "https://github.com/AmirShayegh/react-native-nitro-logger.git", :tag => "#{s.version}" }
|
|
15
|
+
|
|
16
|
+
s.source_files = [
|
|
17
|
+
"ios/**/*.{swift}",
|
|
18
|
+
"ios/**/*.{m,mm}",
|
|
19
|
+
"cpp/**/*.{hpp,cpp}",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
s.dependency 'React-jsi'
|
|
23
|
+
s.dependency 'React-callinvoker'
|
|
24
|
+
|
|
25
|
+
load 'nitrogen/generated/ios/NitroLogger+autolinking.rb'
|
|
26
|
+
add_nitrogen_files(s)
|
|
27
|
+
|
|
28
|
+
install_modules_dependencies(s)
|
|
29
|
+
end
|
package/README.md
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# react-native-nitro-logger
|
|
2
|
+
|
|
3
|
+
Structured, privacy-tiered logging for React Native. A TypeScript core with
|
|
4
|
+
native file and system-console sinks built on [Nitro Modules][nitro].
|
|
5
|
+
|
|
6
|
+
- **Privacy is a setting, not a habit.** One call at startup decides whether an
|
|
7
|
+
unwrapped metadata value renders or redacts. In the builds that ship, the
|
|
8
|
+
strict profile is fail-closed: a forgotten wrapper hides data rather than
|
|
9
|
+
leaking it.
|
|
10
|
+
- **Designed around the things that actually go wrong.** Rotation, gzip and
|
|
11
|
+
retention; crash-tail recovery; detection of an externally deleted file, with
|
|
12
|
+
a reopen into a fresh one; and a compliance purge that reports honestly when
|
|
13
|
+
it could not finish. Where records cannot be saved they are *accounted for*
|
|
14
|
+
rather than papered over: a full disk loses records and counts what it lost,
|
|
15
|
+
and the partial write is rolled back to the last record boundary so the
|
|
16
|
+
damage does not spread — an attempt, not a guarantee, since the rollback is
|
|
17
|
+
itself I/O that can fail. An external deletion is detected and recovered
|
|
18
|
+
from, but the unlinked file and anything written before detection are gone.
|
|
19
|
+
- **New Architecture only**, iOS and Android, no bridge fallback.
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
npm install react-native-nitro-logger react-native-nitro-modules
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`react-native-nitro-modules` is a required peer — this library is built on
|
|
26
|
+
[Nitro Modules][nitro].
|
|
27
|
+
|
|
28
|
+
## Quick start
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import {
|
|
32
|
+
Log,
|
|
33
|
+
ConsoleDestination,
|
|
34
|
+
FileDestination,
|
|
35
|
+
createFileSink,
|
|
36
|
+
} from 'react-native-nitro-logger';
|
|
37
|
+
|
|
38
|
+
Log.addDestination(new ConsoleDestination());
|
|
39
|
+
Log.addDestination(new FileDestination(createFileSink()));
|
|
40
|
+
|
|
41
|
+
Log.info('app started');
|
|
42
|
+
Log.warning('retrying upload', { attempt: 2, statusCode: 503 });
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
A scoped logger tags every line with a correlation ID, so one request's lines
|
|
46
|
+
can be picked out of a busy file. Omit the correlation argument and one is
|
|
47
|
+
generated for you — which is also what the lint rules want, since a correlation
|
|
48
|
+
ID must never be derived from an identifier you already have:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
const scope = Log.scoped(undefined, 'checkout', { orderKind: 'subscription' });
|
|
52
|
+
scope.info('payment authorised');
|
|
53
|
+
scope.error('capture failed', { statusCode: 502 });
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Levels
|
|
57
|
+
|
|
58
|
+
`verbose` · `debug` · `info` · `warning` · `error` · `todo`
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
Log.minimumLevel('info'); // globally
|
|
62
|
+
Log.subsystem('networking', 'debug'); // and per subsystem
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Privacy
|
|
66
|
+
|
|
67
|
+
The default profile renders metadata values and is fine for open-source and
|
|
68
|
+
general application use. Apps handling regulated data should switch to the
|
|
69
|
+
strict one in their entry point, before anything logs:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import { Log, pub, priv } from 'react-native-nitro-logger';
|
|
73
|
+
|
|
74
|
+
Log.privacyDefault('private');
|
|
75
|
+
Log.metadataKeyCatalog(['requestId', 'statusCode', 'durationMs']);
|
|
76
|
+
|
|
77
|
+
Log.info('request finished', {
|
|
78
|
+
requestId: pub(id), // rendered
|
|
79
|
+
statusCode: 200, // redacted — unwrapped, and the default is private
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**In a release build** (`__DEV__` false):
|
|
84
|
+
|
|
85
|
+
| | `'public'` (default) | `'private'` |
|
|
86
|
+
| --- | --- | --- |
|
|
87
|
+
| bare value | rendered | `<private>` |
|
|
88
|
+
| `pub(v)` | rendered | rendered |
|
|
89
|
+
| `priv(v)` | `<private>` | `<private>` |
|
|
90
|
+
|
|
91
|
+
**A debug build renders private payloads in the clear**, to every destination
|
|
92
|
+
including the file — so the fail-closed behaviour above is a property of the
|
|
93
|
+
builds that ship, not of every build. A build where reveal is possible is a
|
|
94
|
+
build for synthetic data only.
|
|
95
|
+
|
|
96
|
+
`privacyDefault` is first-set-wins and tighten-only, so a dependency cannot
|
|
97
|
+
loosen your setting.
|
|
98
|
+
|
|
99
|
+
Message text, metadata keys, subsystems and correlation IDs are **public by
|
|
100
|
+
contract** — they are never redacted at runtime. The bundled ESLint plugin
|
|
101
|
+
constrains them at build time instead, but only once you enable it: installing
|
|
102
|
+
this package ships the rules, it does not apply them.
|
|
103
|
+
|
|
104
|
+
```js
|
|
105
|
+
// eslint.config.mjs
|
|
106
|
+
import nitroLogger from 'react-native-nitro-logger/eslint-plugin';
|
|
107
|
+
|
|
108
|
+
export default [nitroLogger.configs.strict];
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**Read [docs/PRIVACY.md](docs/PRIVACY.md) before using this in an app that
|
|
112
|
+
handles regulated data.** It covers what the contract does and does not
|
|
113
|
+
promise, the approved-key catalog, and the compliance boundary — in short, a
|
|
114
|
+
build where reveal is possible is a build for synthetic data only.
|
|
115
|
+
|
|
116
|
+
## Writing to a file
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
const file = new FileDestination(createFileSink(), {
|
|
120
|
+
rotation: {
|
|
121
|
+
maxFileSizeBytes: 10 * 1024 * 1024,
|
|
122
|
+
maxArchivedFilesCount: 5,
|
|
123
|
+
compressArchives: true,
|
|
124
|
+
maxTotalLogBytes: 50 * 1024 * 1024,
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
Log.addDestination(file);
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Logs live in app-private storage — `noBackupFilesDir` on Android, `Library/Logs`
|
|
131
|
+
on iOS. Owner-only modes are applied to every artifact; where the platform
|
|
132
|
+
refuses, the sink reports a `protection` degradation and keeps logging rather
|
|
133
|
+
than failing shut.
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
file.getLogFilePaths(); // for a consent-gated support upload
|
|
137
|
+
|
|
138
|
+
const outcome = file.purge(5000); // the compliance purge
|
|
139
|
+
if (!outcome.durable) {
|
|
140
|
+
// Something survived, or the deadline blew. The destination stays fenced
|
|
141
|
+
// until an explicit retry, so nothing is written into a pending deletion.
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`purge` is synchronous and deadline-bounded. It reports `durable` (every
|
|
146
|
+
pre-purge artifact is gone) separately from `rebound` (the destination is
|
|
147
|
+
writable again), because a complete deletion can still be followed by a failed
|
|
148
|
+
reopen, and a caller that resumes on `durable` alone would write into a
|
|
149
|
+
destination with nowhere to put anything. A purge that rebinds opens a fresh,
|
|
150
|
+
empty active file — `durable` is a statement about the data that was there, not
|
|
151
|
+
a promise that the directory is left empty.
|
|
152
|
+
|
|
153
|
+
It clears the **file sink's** artifacts and nothing else. Anything already
|
|
154
|
+
handed to `os_log` or `logcat` is outside this library's reach.
|
|
155
|
+
|
|
156
|
+
## Crash handling and backgrounding
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
import { installErrorHandler, flushOnBackground } from 'react-native-nitro-logger';
|
|
160
|
+
|
|
161
|
+
const uninstallHandler = installErrorHandler();
|
|
162
|
+
const uninstallFlush = flushOnBackground();
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The error handler logs uncaught errors with the message dropped outside dev,
|
|
166
|
+
the class name reduced to a built-in or a fixed token, and stack frames reduced
|
|
167
|
+
to a position in a file whose name was already known. It flushes on fatal
|
|
168
|
+
errors, then chains to whatever handler was installed before it. Both functions
|
|
169
|
+
return idempotent uninstall handles.
|
|
170
|
+
|
|
171
|
+
## Output format
|
|
172
|
+
|
|
173
|
+
`JsonLinesFormatter` is the default for files — one JSON object per line, with
|
|
174
|
+
a framing guarantee the native crash-tail recovery depends on. It is asserted
|
|
175
|
+
byte-identical to [SwiftLogger][swiftlogger]'s `JSONLogFormatter` over a
|
|
176
|
+
generated corpus.
|
|
177
|
+
|
|
178
|
+
```json
|
|
179
|
+
{"timestamp":"2026-07-28T10:15:00.123Z","level":"INFO","message":"app started"}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
`DefaultFormatter` is the human-readable one used by the console destination.
|
|
183
|
+
Custom formatters implement `LogFormatter`; declare `framing: 'line'` to opt
|
|
184
|
+
into crash-tail trimming.
|
|
185
|
+
|
|
186
|
+
## Compatibility
|
|
187
|
+
|
|
188
|
+
| | Supported | Verified |
|
|
189
|
+
| --- | --- | --- |
|
|
190
|
+
| iOS | React Native ≥ 0.78 | CI builds, launches and exercises a pristine 0.78 consumer app |
|
|
191
|
+
| 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 |
|
|
192
|
+
|
|
193
|
+
New Architecture only. The compatibility claims are split per platform on
|
|
194
|
+
purpose — see [docs/PARITY.md](docs/PARITY.md) for what backs each one, and for
|
|
195
|
+
where the two native writers genuinely differ.
|
|
196
|
+
|
|
197
|
+
## Documentation
|
|
198
|
+
|
|
199
|
+
- [docs/PRIVACY.md](docs/PRIVACY.md) — the privacy contract and compliance boundary
|
|
200
|
+
- [docs/PARITY.md](docs/PARITY.md) — parity with SwiftLogger, and between the two native writers
|
|
201
|
+
- [eslint-plugin/README.md](eslint-plugin/README.md) — what the lint rules protect, and what they do not
|
|
202
|
+
|
|
203
|
+
## Contributing
|
|
204
|
+
|
|
205
|
+
- [Development workflow](CONTRIBUTING.md#development-workflow)
|
|
206
|
+
- [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
|
|
207
|
+
- [Code of conduct](CODE_OF_CONDUCT.md)
|
|
208
|
+
|
|
209
|
+
## License
|
|
210
|
+
|
|
211
|
+
MIT
|
|
212
|
+
|
|
213
|
+
[nitro]: https://nitro.margelo.com/
|
|
214
|
+
[swiftlogger]: https://github.com/amirshayegh/logger
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
project(nitrologger)
|
|
2
|
+
cmake_minimum_required(VERSION 3.9.0)
|
|
3
|
+
|
|
4
|
+
set(PACKAGE_NAME nitrologger)
|
|
5
|
+
set(CMAKE_VERBOSE_MAKEFILE ON)
|
|
6
|
+
set(CMAKE_CXX_STANDARD 20)
|
|
7
|
+
|
|
8
|
+
# Define C++ library and add all sources
|
|
9
|
+
add_library(${PACKAGE_NAME} SHARED src/main/cpp/cpp-adapter.cpp)
|
|
10
|
+
|
|
11
|
+
# Add Nitrogen specs :)
|
|
12
|
+
include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/nitrologger+autolinking.cmake)
|
|
13
|
+
|
|
14
|
+
# Set up local includes
|
|
15
|
+
include_directories("src/main/cpp" "../cpp")
|
|
16
|
+
|
|
17
|
+
find_library(LOG_LIB log)
|
|
18
|
+
|
|
19
|
+
# Link all libraries together
|
|
20
|
+
target_link_libraries(
|
|
21
|
+
${PACKAGE_NAME}
|
|
22
|
+
${LOG_LIB}
|
|
23
|
+
android # <-- Android core
|
|
24
|
+
)
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.NitroLogger = [
|
|
3
|
+
kotlinVersion: "2.0.21",
|
|
4
|
+
minSdkVersion: 24,
|
|
5
|
+
compileSdkVersion: 36
|
|
6
|
+
]
|
|
7
|
+
|
|
8
|
+
ext.getExtOrDefault = { prop ->
|
|
9
|
+
if (rootProject.ext.has(prop)) {
|
|
10
|
+
return rootProject.ext.get(prop)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return NitroLogger[prop]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
repositories {
|
|
17
|
+
google()
|
|
18
|
+
mavenCentral()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
dependencies {
|
|
22
|
+
classpath "com.android.tools.build:gradle:8.7.2"
|
|
23
|
+
// noinspection DifferentKotlinGradleVersion
|
|
24
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
def reactNativeArchitectures() {
|
|
29
|
+
def value = rootProject.getProperties().get("reactNativeArchitectures")
|
|
30
|
+
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
apply plugin: "com.android.library"
|
|
34
|
+
apply plugin: "kotlin-android"
|
|
35
|
+
apply from: '../nitrogen/generated/android/nitrologger+autolinking.gradle'
|
|
36
|
+
|
|
37
|
+
apply plugin: "com.facebook.react"
|
|
38
|
+
|
|
39
|
+
android {
|
|
40
|
+
namespace "com.margelo.nitro.nitrologger"
|
|
41
|
+
|
|
42
|
+
compileSdkVersion getExtOrDefault("compileSdkVersion")
|
|
43
|
+
|
|
44
|
+
defaultConfig {
|
|
45
|
+
minSdkVersion getExtOrDefault("minSdkVersion")
|
|
46
|
+
|
|
47
|
+
externalNativeBuild {
|
|
48
|
+
cmake {
|
|
49
|
+
cppFlags "-frtti -fexceptions -Wall -fstack-protector-all"
|
|
50
|
+
arguments "-DANDROID_STL=c++_shared", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
|
|
51
|
+
abiFilters (*reactNativeArchitectures())
|
|
52
|
+
|
|
53
|
+
buildTypes {
|
|
54
|
+
debug {
|
|
55
|
+
cppFlags "-O1 -g"
|
|
56
|
+
}
|
|
57
|
+
release {
|
|
58
|
+
cppFlags "-O2"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
externalNativeBuild {
|
|
66
|
+
cmake {
|
|
67
|
+
path "CMakeLists.txt"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
packaging {
|
|
72
|
+
resources {
|
|
73
|
+
excludes += [
|
|
74
|
+
"META-INF",
|
|
75
|
+
"META-INF/**"
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
jniLibs {
|
|
80
|
+
excludes += [
|
|
81
|
+
"**/libc++_shared.so",
|
|
82
|
+
"**/libfbjni.so",
|
|
83
|
+
"**/libjsi.so",
|
|
84
|
+
"**/libfolly_json.so",
|
|
85
|
+
"**/libfolly_runtime.so",
|
|
86
|
+
"**/libglog.so",
|
|
87
|
+
"**/libhermes.so",
|
|
88
|
+
"**/libhermes-executor-debug.so",
|
|
89
|
+
"**/libhermes_executor.so",
|
|
90
|
+
"**/libreactnative.so",
|
|
91
|
+
"**/libreactnativejni.so",
|
|
92
|
+
"**/libturbomodulejsijni.so",
|
|
93
|
+
"**/libreact_nativemodule_core.so",
|
|
94
|
+
"**/libjscexecutor.so"
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
buildFeatures {
|
|
100
|
+
prefab true
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
compileOptions {
|
|
104
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
105
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
testOptions {
|
|
109
|
+
unitTests {
|
|
110
|
+
// The writer imports nothing from `android.*` — every syscall it needs
|
|
111
|
+
// is behind `PlatformIo` — so its suite is plain JUnit on the JVM and
|
|
112
|
+
// needs neither a device nor Robolectric. This flag only covers the
|
|
113
|
+
// stray android.* symbol reached through a shared constant.
|
|
114
|
+
returnDefaultValues = true
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
dependencies {
|
|
120
|
+
implementation "com.facebook.react:react-android"
|
|
121
|
+
implementation project(":react-native-nitro-modules")
|
|
122
|
+
|
|
123
|
+
testImplementation "junit:junit:4.13.2"
|
|
124
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
package com.margelo.nitro.nitrologger
|
|
2
|
+
|
|
3
|
+
import android.system.ErrnoException
|
|
4
|
+
import android.system.Os
|
|
5
|
+
import android.system.OsConstants
|
|
6
|
+
import java.io.File
|
|
7
|
+
import java.io.FileDescriptor
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* [PlatformIo] against the real syscalls.
|
|
11
|
+
*
|
|
12
|
+
* Split out from the interface so that every `android.*` import in the writer's
|
|
13
|
+
* dependency graph lives in this one file, and the writer itself stays
|
|
14
|
+
* runnable — and testable — on a plain JVM.
|
|
15
|
+
*
|
|
16
|
+
* Every method here is answered with `Os`, which is API 21. Nothing delegates
|
|
17
|
+
* to [PlatformIo.Jvm] except [restrictToOwner], whose fallback uses only
|
|
18
|
+
* `java.io`: `java.nio.file` is API 26 and this library supports 24, and a
|
|
19
|
+
* missing class there would raise `NoClassDefFoundError` rather than something
|
|
20
|
+
* a `catch (Exception)` would contain.
|
|
21
|
+
*/
|
|
22
|
+
object AndroidPlatformIo : PlatformIo {
|
|
23
|
+
override fun linkCount(descriptor: FileDescriptor): Int = try {
|
|
24
|
+
if (!descriptor.valid()) -1 else Os.fstat(descriptor).st_nlink.toInt()
|
|
25
|
+
} catch (_: Throwable) {
|
|
26
|
+
// Unknowable, which is not the same as zero. Reporting zero here would
|
|
27
|
+
// make the writer reopen its file on every health check.
|
|
28
|
+
-1
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
override fun syncDirectory(directory: File): Boolean {
|
|
32
|
+
var fd: FileDescriptor? = null
|
|
33
|
+
return try {
|
|
34
|
+
fd = Os.open(directory.absolutePath, OsConstants.O_RDONLY, 0)
|
|
35
|
+
Os.fsync(fd)
|
|
36
|
+
true
|
|
37
|
+
} catch (_: Throwable) {
|
|
38
|
+
false
|
|
39
|
+
} finally {
|
|
40
|
+
if (fd != null) {
|
|
41
|
+
try {
|
|
42
|
+
Os.close(fd)
|
|
43
|
+
} catch (_: Throwable) {
|
|
44
|
+
// Nothing left to do about it, and the sync result already stands.
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* `lstat`, with the errno preserved.
|
|
52
|
+
*
|
|
53
|
+
* Only `ENOENT` means the path is gone. Everything else — `EACCES` on a
|
|
54
|
+
* directory whose mode changed underneath us, `EIO` on failing storage — is
|
|
55
|
+
* [PlatformIo.Presence.UNKNOWN], and the purge reports it as a failure
|
|
56
|
+
* instead of counting a file it never saw removed as deleted.
|
|
57
|
+
*/
|
|
58
|
+
override fun lookup(file: File): PlatformIo.Presence = try {
|
|
59
|
+
Os.lstat(file.absolutePath)
|
|
60
|
+
PlatformIo.Presence.PRESENT
|
|
61
|
+
} catch (e: ErrnoException) {
|
|
62
|
+
if (e.errno == OsConstants.ENOENT) PlatformIo.Presence.ABSENT
|
|
63
|
+
else PlatformIo.Presence.UNKNOWN
|
|
64
|
+
} catch (_: Throwable) {
|
|
65
|
+
PlatformIo.Presence.UNKNOWN
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
override fun isSymbolicLink(file: File): Boolean = try {
|
|
69
|
+
OsConstants.S_ISLNK(Os.lstat(file.absolutePath).st_mode)
|
|
70
|
+
} catch (e: ErrnoException) {
|
|
71
|
+
// A path that is not there is not a symlink. Anything else is unresolvable,
|
|
72
|
+
// and unresolvable has to fail closed.
|
|
73
|
+
e.errno != OsConstants.ENOENT
|
|
74
|
+
} catch (_: Throwable) {
|
|
75
|
+
true
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
override fun restrictToOwner(file: File, isDirectory: Boolean): Boolean = try {
|
|
79
|
+
// chmod through the path rather than the java.io.File permission helpers:
|
|
80
|
+
// those go through three separate syscalls with observable intermediate
|
|
81
|
+
// states, and cannot express 0700 in one step.
|
|
82
|
+
Os.chmod(file.absolutePath, if (isDirectory) 448 /* 0700 */ else 384 /* 0600 */)
|
|
83
|
+
true
|
|
84
|
+
} catch (_: Throwable) {
|
|
85
|
+
PlatformIo.Jvm.restrictToOwner(file, isDirectory)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Always null on Android — the age sidecar is the mechanism here.
|
|
90
|
+
*
|
|
91
|
+
* `BasicFileAttributes.creationTime()` is API 26, and where it does exist it
|
|
92
|
+
* is not reliably populated: several Android filesystems have no birth time
|
|
93
|
+
* and return the mtime instead. An mtime advances on every write, so trusting
|
|
94
|
+
* it would make the active file look freshly created at every restart and
|
|
95
|
+
* postpone age-based rotation forever. Reporting nothing is what tells
|
|
96
|
+
* `LogFileWriter.creationTimeOf` to seed the sidecar from the clock and treat
|
|
97
|
+
* it as authoritative.
|
|
98
|
+
*/
|
|
99
|
+
override fun creationTimeMillis(file: File): Long? = null
|
|
100
|
+
}
|