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.
Files changed (205) hide show
  1. package/LICENSE +20 -0
  2. package/NitroLogger.podspec +29 -0
  3. package/README.md +214 -0
  4. package/android/CMakeLists.txt +24 -0
  5. package/android/build.gradle +124 -0
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/cpp/cpp-adapter.cpp +11 -0
  8. package/android/src/main/java/com/margelo/nitro/nitrologger/AndroidPlatformIo.kt +100 -0
  9. package/android/src/main/java/com/margelo/nitro/nitrologger/HybridFileSink.kt +292 -0
  10. package/android/src/main/java/com/margelo/nitro/nitrologger/HybridNativeConsoleSink.kt +23 -0
  11. package/android/src/main/java/com/margelo/nitro/nitrologger/LogFileWriter.kt +1328 -0
  12. package/android/src/main/java/com/margelo/nitro/nitrologger/LogRotationPolicy.kt +186 -0
  13. package/android/src/main/java/com/margelo/nitro/nitrologger/LogSecureFile.kt +80 -0
  14. package/android/src/main/java/com/margelo/nitro/nitrologger/LogWriterRegistry.kt +420 -0
  15. package/android/src/main/java/com/margelo/nitro/nitrologger/NativeConsoleWriter.kt +237 -0
  16. package/android/src/main/java/com/margelo/nitro/nitrologger/NitroLoggerPackage.kt +22 -0
  17. package/android/src/main/java/com/margelo/nitro/nitrologger/PlatformIo.kt +165 -0
  18. package/docs/PARITY.md +330 -0
  19. package/docs/PRIVACY.md +341 -0
  20. package/eslint-plugin/README.md +238 -0
  21. package/eslint-plugin/index.js +62 -0
  22. package/eslint-plugin/rules/literal-subsystem.js +102 -0
  23. package/eslint-plugin/rules/no-computed-metadata-key.js +174 -0
  24. package/eslint-plugin/rules/no-derived-correlation.js +171 -0
  25. package/eslint-plugin/rules/no-dynamic-message.js +60 -0
  26. package/eslint-plugin/shared.js +1948 -0
  27. package/ios/Gzip.swift +203 -0
  28. package/ios/HybridFileSink.swift +291 -0
  29. package/ios/HybridNativeConsoleSink.swift +20 -0
  30. package/ios/LogFileWriter.swift +1280 -0
  31. package/ios/LogSecureFile.swift +296 -0
  32. package/ios/LogWriterRegistry.swift +444 -0
  33. package/ios/NativeConsoleWriter.swift +180 -0
  34. package/lib/module/Logger.js +381 -0
  35. package/lib/module/Logger.js.map +1 -0
  36. package/lib/module/ScopedLogger.js +70 -0
  37. package/lib/module/ScopedLogger.js.map +1 -0
  38. package/lib/module/config.js +23 -0
  39. package/lib/module/config.js.map +1 -0
  40. package/lib/module/destinations/Batcher.js +647 -0
  41. package/lib/module/destinations/Batcher.js.map +1 -0
  42. package/lib/module/destinations/ConsoleDestination.js +51 -0
  43. package/lib/module/destinations/ConsoleDestination.js.map +1 -0
  44. package/lib/module/destinations/FileDestination.js +360 -0
  45. package/lib/module/destinations/FileDestination.js.map +1 -0
  46. package/lib/module/destinations/NativeConsoleDestination.js +170 -0
  47. package/lib/module/destinations/NativeConsoleDestination.js.map +1 -0
  48. package/lib/module/destinations/types.js +4 -0
  49. package/lib/module/destinations/types.js.map +1 -0
  50. package/lib/module/formatters/DefaultFormatter.js +136 -0
  51. package/lib/module/formatters/DefaultFormatter.js.map +1 -0
  52. package/lib/module/formatters/JsonLinesFormatter.js +162 -0
  53. package/lib/module/formatters/JsonLinesFormatter.js.map +1 -0
  54. package/lib/module/formatters/timestamp.js +15 -0
  55. package/lib/module/formatters/timestamp.js.map +1 -0
  56. package/lib/module/formatters/types.js +4 -0
  57. package/lib/module/formatters/types.js.map +1 -0
  58. package/lib/module/index.js +31 -0
  59. package/lib/module/index.js.map +1 -0
  60. package/lib/module/integrations/appState.js +81 -0
  61. package/lib/module/integrations/appState.js.map +1 -0
  62. package/lib/module/integrations/errorHandler.js +166 -0
  63. package/lib/module/integrations/errorHandler.js.map +1 -0
  64. package/lib/module/integrations/sanitizeError.js +238 -0
  65. package/lib/module/integrations/sanitizeError.js.map +1 -0
  66. package/lib/module/levels.js +35 -0
  67. package/lib/module/levels.js.map +1 -0
  68. package/lib/module/metadata.js +52 -0
  69. package/lib/module/metadata.js.map +1 -0
  70. package/lib/module/package.json +1 -0
  71. package/lib/module/privacy.js +349 -0
  72. package/lib/module/privacy.js.map +1 -0
  73. package/lib/module/specs/FileSink.nitro.js +4 -0
  74. package/lib/module/specs/FileSink.nitro.js.map +1 -0
  75. package/lib/module/specs/NativeConsoleSink.nitro.js +4 -0
  76. package/lib/module/specs/NativeConsoleSink.nitro.js.map +1 -0
  77. package/lib/module/types.js +4 -0
  78. package/lib/module/types.js.map +1 -0
  79. package/lib/module/utf8.js +42 -0
  80. package/lib/module/utf8.js.map +1 -0
  81. package/lib/typescript/package.json +1 -0
  82. package/lib/typescript/src/Logger.d.ts +122 -0
  83. package/lib/typescript/src/Logger.d.ts.map +1 -0
  84. package/lib/typescript/src/ScopedLogger.d.ts +36 -0
  85. package/lib/typescript/src/ScopedLogger.d.ts.map +1 -0
  86. package/lib/typescript/src/config.d.ts +12 -0
  87. package/lib/typescript/src/config.d.ts.map +1 -0
  88. package/lib/typescript/src/destinations/Batcher.d.ts +244 -0
  89. package/lib/typescript/src/destinations/Batcher.d.ts.map +1 -0
  90. package/lib/typescript/src/destinations/ConsoleDestination.d.ts +27 -0
  91. package/lib/typescript/src/destinations/ConsoleDestination.d.ts.map +1 -0
  92. package/lib/typescript/src/destinations/FileDestination.d.ts +200 -0
  93. package/lib/typescript/src/destinations/FileDestination.d.ts.map +1 -0
  94. package/lib/typescript/src/destinations/NativeConsoleDestination.d.ts +86 -0
  95. package/lib/typescript/src/destinations/NativeConsoleDestination.d.ts.map +1 -0
  96. package/lib/typescript/src/destinations/types.d.ts +19 -0
  97. package/lib/typescript/src/destinations/types.d.ts.map +1 -0
  98. package/lib/typescript/src/formatters/DefaultFormatter.d.ts +22 -0
  99. package/lib/typescript/src/formatters/DefaultFormatter.d.ts.map +1 -0
  100. package/lib/typescript/src/formatters/JsonLinesFormatter.d.ts +73 -0
  101. package/lib/typescript/src/formatters/JsonLinesFormatter.d.ts.map +1 -0
  102. package/lib/typescript/src/formatters/timestamp.d.ts +6 -0
  103. package/lib/typescript/src/formatters/timestamp.d.ts.map +1 -0
  104. package/lib/typescript/src/formatters/types.d.ts +27 -0
  105. package/lib/typescript/src/formatters/types.d.ts.map +1 -0
  106. package/lib/typescript/src/index.d.ts +34 -0
  107. package/lib/typescript/src/index.d.ts.map +1 -0
  108. package/lib/typescript/src/integrations/appState.d.ts +33 -0
  109. package/lib/typescript/src/integrations/appState.d.ts.map +1 -0
  110. package/lib/typescript/src/integrations/errorHandler.d.ts +55 -0
  111. package/lib/typescript/src/integrations/errorHandler.d.ts.map +1 -0
  112. package/lib/typescript/src/integrations/sanitizeError.d.ts +89 -0
  113. package/lib/typescript/src/integrations/sanitizeError.d.ts.map +1 -0
  114. package/lib/typescript/src/levels.d.ts +9 -0
  115. package/lib/typescript/src/levels.d.ts.map +1 -0
  116. package/lib/typescript/src/metadata.d.ts +23 -0
  117. package/lib/typescript/src/metadata.d.ts.map +1 -0
  118. package/lib/typescript/src/privacy.d.ts +103 -0
  119. package/lib/typescript/src/privacy.d.ts.map +1 -0
  120. package/lib/typescript/src/specs/FileSink.nitro.d.ts +131 -0
  121. package/lib/typescript/src/specs/FileSink.nitro.d.ts.map +1 -0
  122. package/lib/typescript/src/specs/NativeConsoleSink.nitro.d.ts +21 -0
  123. package/lib/typescript/src/specs/NativeConsoleSink.nitro.d.ts.map +1 -0
  124. package/lib/typescript/src/types.d.ts +37 -0
  125. package/lib/typescript/src/types.d.ts.map +1 -0
  126. package/lib/typescript/src/utf8.d.ts +15 -0
  127. package/lib/typescript/src/utf8.d.ts.map +1 -0
  128. package/nitro.json +33 -0
  129. package/nitrogen/generated/android/c++/JAppendResult.hpp +79 -0
  130. package/nitrogen/generated/android/c++/JClearOutcome.hpp +88 -0
  131. package/nitrogen/generated/android/c++/JFlushOutcome.hpp +81 -0
  132. package/nitrogen/generated/android/c++/JHybridFileSinkSpec.cpp +120 -0
  133. package/nitrogen/generated/android/c++/JHybridFileSinkSpec.hpp +69 -0
  134. package/nitrogen/generated/android/c++/JHybridNativeConsoleSinkSpec.cpp +71 -0
  135. package/nitrogen/generated/android/c++/JHybridNativeConsoleSinkSpec.hpp +64 -0
  136. package/nitrogen/generated/android/c++/JRejectReason.hpp +64 -0
  137. package/nitrogen/generated/android/c++/JRotationConfig.hpp +77 -0
  138. package/nitrogen/generated/android/c++/JSinkStatus.hpp +69 -0
  139. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrologger/AppendResult.kt +76 -0
  140. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrologger/ClearOutcome.kt +66 -0
  141. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrologger/FlushOutcome.kt +81 -0
  142. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrologger/HybridFileSinkSpec.kt +80 -0
  143. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrologger/HybridNativeConsoleSinkSpec.kt +58 -0
  144. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrologger/RejectReason.kt +25 -0
  145. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrologger/RotationConfig.kt +76 -0
  146. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrologger/SinkStatus.kt +66 -0
  147. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrologger/nitrologgerOnLoad.kt +35 -0
  148. package/nitrogen/generated/android/nitrologger+autolinking.cmake +83 -0
  149. package/nitrogen/generated/android/nitrologger+autolinking.gradle +27 -0
  150. package/nitrogen/generated/android/nitrologgerOnLoad.cpp +70 -0
  151. package/nitrogen/generated/android/nitrologgerOnLoad.hpp +34 -0
  152. package/nitrogen/generated/ios/NitroLogger+autolinking.rb +62 -0
  153. package/nitrogen/generated/ios/NitroLogger-Swift-Cxx-Bridge.cpp +50 -0
  154. package/nitrogen/generated/ios/NitroLogger-Swift-Cxx-Bridge.hpp +216 -0
  155. package/nitrogen/generated/ios/NitroLogger-Swift-Cxx-Umbrella.hpp +69 -0
  156. package/nitrogen/generated/ios/NitroLoggerAutolinking.mm +41 -0
  157. package/nitrogen/generated/ios/NitroLoggerAutolinking.swift +38 -0
  158. package/nitrogen/generated/ios/c++/HybridFileSinkSpecSwift.cpp +11 -0
  159. package/nitrogen/generated/ios/c++/HybridFileSinkSpecSwift.hpp +150 -0
  160. package/nitrogen/generated/ios/c++/HybridNativeConsoleSinkSpecSwift.cpp +11 -0
  161. package/nitrogen/generated/ios/c++/HybridNativeConsoleSinkSpecSwift.hpp +87 -0
  162. package/nitrogen/generated/ios/swift/AppendResult.swift +60 -0
  163. package/nitrogen/generated/ios/swift/ClearOutcome.swift +50 -0
  164. package/nitrogen/generated/ios/swift/FlushOutcome.swift +59 -0
  165. package/nitrogen/generated/ios/swift/HybridFileSinkSpec.swift +61 -0
  166. package/nitrogen/generated/ios/swift/HybridFileSinkSpec_cxx.swift +227 -0
  167. package/nitrogen/generated/ios/swift/HybridNativeConsoleSinkSpec.swift +56 -0
  168. package/nitrogen/generated/ios/swift/HybridNativeConsoleSinkSpec_cxx.swift +148 -0
  169. package/nitrogen/generated/ios/swift/RejectReason.swift +48 -0
  170. package/nitrogen/generated/ios/swift/RotationConfig.swift +93 -0
  171. package/nitrogen/generated/ios/swift/SinkStatus.swift +44 -0
  172. package/nitrogen/generated/shared/c++/AppendResult.hpp +105 -0
  173. package/nitrogen/generated/shared/c++/ClearOutcome.hpp +96 -0
  174. package/nitrogen/generated/shared/c++/FlushOutcome.hpp +107 -0
  175. package/nitrogen/generated/shared/c++/HybridFileSinkSpec.cpp +28 -0
  176. package/nitrogen/generated/shared/c++/HybridFileSinkSpec.hpp +84 -0
  177. package/nitrogen/generated/shared/c++/HybridNativeConsoleSinkSpec.cpp +22 -0
  178. package/nitrogen/generated/shared/c++/HybridNativeConsoleSinkSpec.hpp +64 -0
  179. package/nitrogen/generated/shared/c++/RejectReason.hpp +84 -0
  180. package/nitrogen/generated/shared/c++/RotationConfig.hpp +103 -0
  181. package/nitrogen/generated/shared/c++/SinkStatus.hpp +95 -0
  182. package/package.json +151 -0
  183. package/src/Logger.ts +417 -0
  184. package/src/ScopedLogger.ts +94 -0
  185. package/src/config.ts +25 -0
  186. package/src/destinations/Batcher.ts +805 -0
  187. package/src/destinations/ConsoleDestination.ts +60 -0
  188. package/src/destinations/FileDestination.ts +473 -0
  189. package/src/destinations/NativeConsoleDestination.ts +220 -0
  190. package/src/destinations/types.ts +19 -0
  191. package/src/formatters/DefaultFormatter.ts +150 -0
  192. package/src/formatters/JsonLinesFormatter.ts +195 -0
  193. package/src/formatters/timestamp.ts +12 -0
  194. package/src/formatters/types.ts +29 -0
  195. package/src/index.tsx +106 -0
  196. package/src/integrations/appState.ts +111 -0
  197. package/src/integrations/errorHandler.ts +223 -0
  198. package/src/integrations/sanitizeError.ts +287 -0
  199. package/src/levels.ts +35 -0
  200. package/src/metadata.ts +52 -0
  201. package/src/privacy.ts +389 -0
  202. package/src/specs/FileSink.nitro.ts +144 -0
  203. package/src/specs/NativeConsoleSink.nitro.ts +21 -0
  204. package/src/types.ts +45 -0
  205. 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,2 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ </manifest>
@@ -0,0 +1,11 @@
1
+ #include <jni.h>
2
+ #include "nitrologgerOnLoad.hpp"
3
+
4
+ #include <fbjni/fbjni.h>
5
+
6
+
7
+ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
8
+ return facebook::jni::initialize(vm, []() {
9
+ margelo::nitro::nitrologger::registerAllNatives();
10
+ });
11
+ }
@@ -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
+ }