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
@@ -0,0 +1,292 @@
1
+ package com.margelo.nitro.nitrologger
2
+
3
+ import com.facebook.proguard.annotations.DoNotStrip
4
+ import com.margelo.nitro.NitroModules
5
+ import java.io.File
6
+ import java.util.concurrent.locks.ReentrantLock
7
+ import kotlin.concurrent.withLock
8
+
9
+ /**
10
+ * The Nitro adapter. Everything interesting is in [LogFileWriter]; this
11
+ * translates the bridge's types and holds the handle.
12
+ *
13
+ * The split is deliberate and mirrors iOS: the writer imports nothing from
14
+ * Nitro or Android, so the whole of it runs under plain JUnit. What is left
15
+ * here is argument marshalling, which is the part that cannot be tested off
16
+ * device anyway.
17
+ */
18
+ @DoNotStrip
19
+ class HybridFileSink : HybridFileSinkSpec() {
20
+ /**
21
+ * Guards [handle] and [mayHaveArtifacts] together, matching the iOS adapter.
22
+ *
23
+ * They have to move under one lock rather than being individually volatile:
24
+ * the question `clearLogs` asks is the *pair* — no handle AND nothing was
25
+ * ever created. Reading them separately lets a close land in between and
26
+ * produce "no handle, never opened", which is the one answer that is never
27
+ * true and the one that reports a durable purge over surviving files.
28
+ */
29
+ private val lock = ReentrantLock()
30
+ private var handle: LogFileHandle? = null
31
+
32
+ /**
33
+ * Whether this sink may have put anything on disk.
34
+ *
35
+ * A null handle conflates two states that owe the caller opposite answers:
36
+ * nothing was ever created, and the files are still there but out of reach.
37
+ *
38
+ * Set *before* the acquisition attempt, not after it succeeds. Acquiring
39
+ * creates the log directory and can then fail on the file itself, so a throw
40
+ * is not evidence that nothing was written — and this flag exists precisely
41
+ * to keep a later purge from claiming otherwise. Never cleared: closing
42
+ * releases the handle, it does not unmake the files.
43
+ */
44
+ private var mayHaveArtifacts = false
45
+
46
+ /**
47
+ * An [open] past its check and inside `acquire`.
48
+ *
49
+ * Published so a second open is refused instead of racing to install a handle
50
+ * over the first one's, and so [close] can tell "nothing here" from
51
+ * "something is on its way".
52
+ */
53
+ private var opening = false
54
+
55
+ /**
56
+ * A [close] that arrived while [opening] was true.
57
+ *
58
+ * That close had no handle to flush and has already returned; this tells the
59
+ * in-flight open to throw away what it acquired rather than install it.
60
+ */
61
+ private var closePending = false
62
+
63
+ private fun current(): LogFileHandle? = lock.withLock { handle }
64
+
65
+ /**
66
+ * The manual release, which JavaScript can call and usually does not.
67
+ *
68
+ * `@CallSuper`, so `super.dispose()` is not optional — it is what frees the
69
+ * C++ side.
70
+ */
71
+ override fun dispose() {
72
+ releaseHandle()
73
+ super.dispose()
74
+ }
75
+
76
+ /**
77
+ * The release that happens whether or not anything asked for one, and the
78
+ * reason this class needs both.
79
+ *
80
+ * A Metro reload tears the JavaScript context down without running any of
81
+ * it, so a JS `dispose()` is never a guarantee. Without this the writer
82
+ * survives the reload holding the registry slot and the descriptor, and the
83
+ * next `open` with a different rotation config fails `CONFIG_CONFLICT`
84
+ * against a sink nothing can reach to close — an every-reload failure during
85
+ * development. iOS gets this from `deinit`; on ART the equivalent is
86
+ * `finalize`, deprecated and still the only hook there is.
87
+ *
88
+ * Best effort by nature: the finalizer thread may never run this. It is a
89
+ * backstop under `dispose`, not a replacement for it.
90
+ */
91
+ @Suppress("removal", "DEPRECATION")
92
+ protected fun finalize() {
93
+ releaseHandle()
94
+ }
95
+
96
+ /**
97
+ * Deliberately routed through [close] rather than reaching for the handle
98
+ * directly. `close` is the one place that also records [closePending], and a
99
+ * `dispose` racing an in-flight `open` is exactly when that matters: JS can
100
+ * call `dispose` from another thread while `open` is inside `acquire`, and
101
+ * without the flag that acquisition installs a live writer into a sink
102
+ * nothing can reach to release. (`finalize` cannot hit that race — a thread
103
+ * inside `open` keeps the object reachable — but it costs nothing to be
104
+ * right by construction rather than by argument.)
105
+ *
106
+ * Idempotent: `close` detaches under the lock, so a second call finds
107
+ * nothing. Both callers above can fire for the same object.
108
+ *
109
+ * Zero deadline: a teardown must not wait on a wedged disk, and on the
110
+ * finalizer thread blocking would stall every other object's release.
111
+ */
112
+ private fun releaseHandle() {
113
+ runCatching { close(0.0) }
114
+ }
115
+
116
+ override val defaultLogDirectory: String
117
+ get() {
118
+ val context = NitroModules.applicationContext
119
+ // `noBackupFilesDir`, not `filesDir`: the latter is eligible for Android
120
+ // Auto Backup, which would upload the log file to Google Drive. For a
121
+ // package whose entire privacy design is about what may leave the device,
122
+ // shipping the logs off it by default is the one unacceptable default.
123
+ val base = context?.noBackupFilesDir
124
+ ?: File(System.getProperty("java.io.tmpdir") ?: "/tmp")
125
+ return File(base, "logs").absolutePath
126
+ }
127
+
128
+ override fun open(path: String, rotation: RotationConfig?, lineFramed: Boolean?) {
129
+ lock.withLock {
130
+ // `opening` counts as open. Acquisition does real I/O — creates the
131
+ // directory, opens the file, may scan a torn tail — so the lock is not
132
+ // held across it, or every `getStatus` would queue behind disk latency,
133
+ // which is the one thing that call must not do. The in-flight attempt is
134
+ // published instead, and a second one is refused rather than allowed to
135
+ // acquire a writer that would overwrite the first: the loser's handle
136
+ // would be unreachable, and unreachable means a later purge never
137
+ // deletes its files.
138
+ if (handle != null || opening) {
139
+ throw LogWriterException(
140
+ LogWriterException.Kind.CONFIG_CONFLICT,
141
+ "this sink is already open"
142
+ )
143
+ }
144
+ opening = true
145
+ closePending = false
146
+ // Before the attempt: see [mayHaveArtifacts].
147
+ mayHaveArtifacts = true
148
+ }
149
+
150
+ val acquired = try {
151
+ LogWriterRegistry.shared.acquire(
152
+ path = path,
153
+ policy = policyOf(rotation),
154
+ // Absent means absent: without a declared one-record-per-line contract
155
+ // the startup scan must not trim a trailing record, because it cannot
156
+ // tell a torn one from an intentional newline.
157
+ lineFramed = lineFramed ?: false,
158
+ platform = AndroidPlatformIo
159
+ )
160
+ } catch (e: Throwable) {
161
+ // Failed attempts have to clear the flag, or a retry is refused forever.
162
+ lock.withLock { opening = false }
163
+ throw e
164
+ }
165
+
166
+ val abandon = lock.withLock {
167
+ opening = false
168
+ val pending = closePending
169
+ closePending = false
170
+ // A close that arrived mid-acquisition found nothing to hand back and has
171
+ // already returned. Installing now would leave a live writer holding a
172
+ // descriptor nothing can reach or release.
173
+ if (!pending) handle = acquired
174
+ pending
175
+ }
176
+
177
+ // Zero deadline: the caller has already been told this sink is closed.
178
+ if (abandon) acquired.close(0.0)
179
+ }
180
+
181
+ private fun policyOf(rotation: RotationConfig?): LogRotationPolicy {
182
+ if (rotation == null) return LogRotationPolicy.of()
183
+ return LogRotationPolicy.of(
184
+ maxFileSizeBytes = rotation.maxFileSizeBytes,
185
+ maxArchivedFilesCount = rotation.maxArchivedFilesCount,
186
+ maxFileAgeSeconds = rotation.maxFileAgeSeconds,
187
+ compressArchives = rotation.compressArchives,
188
+ maxArchiveAgeSeconds = rotation.maxArchiveAgeSeconds,
189
+ maxTotalLogBytes = rotation.maxTotalLogBytes
190
+ )
191
+ }
192
+
193
+ override fun appendBatch(batch: String, entryCount: Double): AppendResult {
194
+ val live = current() ?: return AppendResult(
195
+ false, RejectReason.CLOSED, 0.0, 0.0, 0.0, 0.0
196
+ )
197
+ // Refused rather than coerced, matching `Int(exactly:)` on iOS. A count
198
+ // that does not survive the round trip cannot be trusted to describe the
199
+ // batch it arrived with. See [BridgeNumber.exactLong].
200
+ val exact = BridgeNumber.exactLong(entryCount)
201
+ ?: return wireResult(
202
+ LogAppendResult(false, LogRejectReason.FAILED, live.status())
203
+ )
204
+ return wireResult(live.appendBatch(batch, exact))
205
+ }
206
+
207
+ private fun wireResult(result: LogAppendResult): AppendResult = AppendResult(
208
+ result.accepted,
209
+ result.rejectReason?.let { wireReason(it) },
210
+ result.status.queuedBytes.toDouble(),
211
+ result.status.lostBytes.toDouble(),
212
+ result.status.lostEntries.toDouble(),
213
+ result.status.degraded.toDouble()
214
+ )
215
+
216
+ private fun wireReason(reason: LogRejectReason): RejectReason = when (reason) {
217
+ LogRejectReason.FULL -> RejectReason.FULL
218
+ LogRejectReason.STALE_GENERATION -> RejectReason.STALEGENERATION
219
+ LogRejectReason.CLOSED -> RejectReason.CLOSED
220
+ LogRejectReason.FAILED -> RejectReason.FAILED
221
+ }
222
+
223
+ override fun getStatus(): SinkStatus {
224
+ val live = current() ?: return SinkStatus(0.0, 0.0, 0.0, 0.0)
225
+ val status = live.status()
226
+ return SinkStatus(
227
+ status.queuedBytes.toDouble(),
228
+ status.lostBytes.toDouble(),
229
+ status.lostEntries.toDouble(),
230
+ status.degraded.toDouble()
231
+ )
232
+ }
233
+
234
+ override fun flush(deadlineMs: Double): FlushOutcome {
235
+ val live = current() ?: return FlushOutcome(false, false, 0.0, 0.0, 0.0, 0.0, 0.0)
236
+ return outcomeOf(live.flush(deadlineMs))
237
+ }
238
+
239
+ override fun close(deadlineMs: Double): FlushOutcome {
240
+ // Detached before the close runs, so a concurrent caller cannot pick up a
241
+ // handle that is on its way out.
242
+ val live = lock.withLock {
243
+ val current = handle
244
+ handle = null
245
+ // Nothing to close, but an acquisition is in flight and will finish after
246
+ // this returns. Recording the intent keeps that writer from being
247
+ // installed into a sink the caller has already closed.
248
+ if (current == null && opening) closePending = true
249
+ current
250
+ } ?: return FlushOutcome(false, false, 0.0, 0.0, 0.0, 0.0, 0.0)
251
+ return outcomeOf(live.close(deadlineMs))
252
+ }
253
+
254
+ private fun outcomeOf(outcome: LogFlushOutcome) = FlushOutcome(
255
+ outcome.durable,
256
+ outcome.timedOut,
257
+ outcome.pendingBytes.toDouble(),
258
+ outcome.status.queuedBytes.toDouble(),
259
+ outcome.status.lostBytes.toDouble(),
260
+ outcome.status.lostEntries.toDouble(),
261
+ outcome.status.degraded.toDouble()
262
+ )
263
+
264
+ override fun getLogFilePaths(): Array<String> =
265
+ current()?.logFilePaths()?.toTypedArray() ?: emptyArray()
266
+
267
+ override fun clearLogs(deadlineMs: Double): ClearOutcome {
268
+ // Never opened: nothing was created, so "every artifact is gone" holds
269
+ // vacuously. Opened and since closed: the files are still on disk and this
270
+ // object cannot reach them, so the only honest answer is `durable = false`.
271
+ // The registry draws the same distinction for a released handle, but
272
+ // `close` nils the handle below before that branch is reachable.
273
+ //
274
+ // Reporting a durable purge over surviving files is the worst lie this API
275
+ // can tell: the caller asking is the one deleting patient data on request.
276
+ //
277
+ // Both fields are read in one critical section: a close landing between two
278
+ // separate reads would show "no handle, nothing created", which is the one
279
+ // combination that is never true and the one that lies in the dangerous
280
+ // direction.
281
+ val live = lock.withLock {
282
+ handle ?: return ClearOutcome(0.0, emptyArray(), !mayHaveArtifacts, false)
283
+ }
284
+ val outcome = live.clearLogs(deadlineMs)
285
+ return ClearOutcome(
286
+ outcome.deletedCount.toDouble(),
287
+ outcome.failedPaths.toTypedArray(),
288
+ outcome.durable,
289
+ outcome.rebound
290
+ )
291
+ }
292
+ }
@@ -0,0 +1,23 @@
1
+ package com.margelo.nitro.nitrologger
2
+
3
+ import com.facebook.proguard.annotations.DoNotStrip
4
+
5
+ /**
6
+ * Routes pre-formatted lines into logcat so JS entries interleave with
7
+ * native ones. Tag = the installed category.
8
+ *
9
+ * Marshalling only. The level map and the chunking that keeps logcat from
10
+ * silently eating the tail of a long line live in [NativeConsoleWriter], which
11
+ * imports nothing from Nitro and is therefore testable — the same split the
12
+ * file sink uses, and for the same reason.
13
+ */
14
+ @DoNotStrip
15
+ class HybridNativeConsoleSink : HybridNativeConsoleSinkSpec() {
16
+ private val writer = NativeConsoleWriter()
17
+
18
+ override fun install(subsystem: String, category: String) =
19
+ writer.install(subsystem, category)
20
+
21
+ override fun logBatch(levels: DoubleArray, messages: Array<String>) =
22
+ writer.logBatch(levels, messages)
23
+ }