marsrs-react-native-xlog 0.1.0-alpha.2 → 0.1.0-alpha.4
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 +21 -0
- package/README.md +12 -8
- package/android/build.gradle.kts +1 -1
- package/android/src/main/kotlin/io/github/orangeboychen/marsrs/xlog/reactnative/XlogModule.kt +248 -43
- package/ios/Frameworks/marsrs-xlog.xcframework/Info.plist +83 -0
- package/ios/Frameworks/marsrs-xlog.xcframework/ios-arm64/Headers/mars_xlog.h +242 -0
- package/ios/Frameworks/marsrs-xlog.xcframework/ios-arm64/Headers/module.modulemap +4 -0
- package/ios/Frameworks/marsrs-xlog.xcframework/ios-arm64/libmars_ffi.a +0 -0
- package/ios/Frameworks/marsrs-xlog.xcframework/ios-arm64_x86_64-simulator/Headers/mars_xlog.h +242 -0
- package/ios/Frameworks/marsrs-xlog.xcframework/ios-arm64_x86_64-simulator/Headers/module.modulemap +4 -0
- package/ios/Frameworks/marsrs-xlog.xcframework/ios-arm64_x86_64-simulator/libmars_ffi.a +0 -0
- package/ios/Frameworks/marsrs-xlog.xcframework/watchos-arm64-simulator/Headers/mars_xlog.h +242 -0
- package/ios/Frameworks/marsrs-xlog.xcframework/watchos-arm64-simulator/Headers/module.modulemap +4 -0
- package/ios/Frameworks/marsrs-xlog.xcframework/watchos-arm64-simulator/libmars_ffi.a +0 -0
- package/ios/Frameworks/marsrs-xlog.xcframework/watchos-arm64_arm64_32/Headers/mars_xlog.h +242 -0
- package/ios/Frameworks/marsrs-xlog.xcframework/watchos-arm64_arm64_32/Headers/module.modulemap +4 -0
- package/ios/Frameworks/marsrs-xlog.xcframework/watchos-arm64_arm64_32/libmars_ffi.a +0 -0
- package/ios/Xlog.mm +2 -2
- package/ios/Xlog.swift +263 -37
- package/ios/include/mars_xlog.h +242 -0
- package/package.json +1 -1
- package/src/NativeXlog.ts +28 -3
- package/src/index.ts +143 -20
- package/android/.gradle/9.2.0/checksums/checksums.lock +0 -0
- package/android/.gradle/9.2.0/fileChanges/last-build.bin +0 -0
- package/android/.gradle/9.2.0/fileHashes/fileHashes.bin +0 -0
- package/android/.gradle/9.2.0/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/9.2.0/gc.properties +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +0 -2
- package/android/.gradle/vcs-1/gc.properties +0 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 orangeboyChen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -16,18 +16,23 @@ const xlog = Xlog.open({
|
|
|
16
16
|
});
|
|
17
17
|
xlog.i('Net', 'hello');
|
|
18
18
|
xlog.log(LogLevel.Debug, 'Net', 'a debug line');
|
|
19
|
-
xlog.
|
|
19
|
+
xlog.flushNow();
|
|
20
20
|
xlog.close();
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
`Xlog` is one appender: `Xlog.open(config)` opens it and `close()` closes it —
|
|
24
24
|
the same class, under the same name, the Swift package,
|
|
25
|
-
`platforms/kmp/marsrs-xlog` and the Android AAR publish.
|
|
26
|
-
a `Promise` and nothing needs an `await`: the module is a TurboModule, so a
|
|
27
|
-
call is made on the JS thread and returned from, and the settings are the
|
|
25
|
+
`platforms/kmp/marsrs-xlog` and the Android AAR publish. The settings are the
|
|
28
26
|
properties they are in Kotlin and in Swift — `xlog.level = LogLevel.Debug` —
|
|
29
27
|
and not `setLevel` / `getLevel` pairs.
|
|
30
28
|
|
|
29
|
+
The drain is three calls and not one with a flag: `xlog.requestFlush()` tells
|
|
30
|
+
the writer thread it may take the cache to the file and returns at once,
|
|
31
|
+
`xlog.flushNow()` does the same on the calling thread and is over when it
|
|
32
|
+
returns, and `await xlog.flush()` is `flushNow` off the JS thread — the one
|
|
33
|
+
call here that answers a `Promise`, because a drain blocks the thread it runs
|
|
34
|
+
on and the JS thread is not one to block.
|
|
35
|
+
|
|
31
36
|
That is what the New Architecture buys, and it is the only thing asked for in
|
|
32
37
|
return: a TurboModule is not a bridge module, so an app still on the old
|
|
33
38
|
architecture has no module to `TurboModuleRegistry.getEnforcing`.
|
|
@@ -54,7 +59,7 @@ compiles — there is no build step, and `npm run typecheck` is the only script.
|
|
|
54
59
|
`src/NativeXlog.ts` is the spec React Native's codegen reads, and
|
|
55
60
|
`codegenConfig` in `package.json` is what points it at `src`: `NativeXlogSpec`
|
|
56
61
|
is generated into the app's `React-Codegen` pod and into the Android build, and
|
|
57
|
-
neither half of the module has to be told the
|
|
62
|
+
neither half of the module has to be told the sixteen signatures twice.
|
|
58
63
|
|
|
59
64
|
## What each platform resolves
|
|
60
65
|
|
|
@@ -76,9 +81,8 @@ package out of it.
|
|
|
76
81
|
|
|
77
82
|
## Why it is called xlog's
|
|
78
83
|
|
|
79
|
-
The C ABI is
|
|
80
|
-
|
|
81
|
-
framework and the AAR are called xlog's. `marsrs-react-native` is the umbrella
|
|
84
|
+
The C ABI is 17 `mars_xlog_*` symbols and nothing else, which is the same
|
|
85
|
+
reason the framework and the AAR are called xlog's. `marsrs-react-native` is the umbrella
|
|
82
86
|
the whole port joins under: STN and SDT land there and not here, so an app that
|
|
83
87
|
only logs keeps this one and downloads nothing it does not call. Today the two
|
|
84
88
|
are the same module under two names, and taking the umbrella costs exactly what
|
package/android/build.gradle.kts
CHANGED
|
@@ -45,5 +45,5 @@ dependencies {
|
|
|
45
45
|
// `react-android` and not `react-native`: the artifact React Native has
|
|
46
46
|
// published since 0.71, and the one the app's own repository resolves.
|
|
47
47
|
implementation("com.facebook.react:react-android")
|
|
48
|
-
implementation("io.github.orangeboychen.marsrs:xlog:0.1.0-alpha.
|
|
48
|
+
implementation("io.github.orangeboychen.marsrs:xlog:0.1.0-alpha.4")
|
|
49
49
|
}
|
package/android/src/main/kotlin/io/github/orangeboychen/marsrs/xlog/reactnative/XlogModule.kt
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
// The Android half of `marsrs-react-native-xlog`: the
|
|
2
|
-
// `Xlog` native module, each of them a straight call of a member of `Xlog` —
|
|
3
|
-
// Kotlin face of `libmarsrsxlog.so` in the `marsrs-xlog` AAR, and the same
|
|
1
|
+
// The Android half of `marsrs-react-native-xlog`: the sixteen methods of the
|
|
2
|
+
// `Xlog` native module, each of them a straight call of a member of `Xlog` —
|
|
3
|
+
// the Kotlin face of `libmarsrsxlog.so` in the `marsrs-xlog` AAR, and the same
|
|
4
4
|
// class `platforms/kmp/marsrs-xlog` publishes to a Kotlin Multiplatform app
|
|
5
5
|
// and `platforms/apple/MarsRSXlog/Xlog.swift` to a Swift one. So what this
|
|
6
6
|
// file is is a native module over an API that already exists, and nothing of
|
|
7
7
|
// the API is invented here: `Xlog.open(XlogConfig(...))`, `log`,
|
|
8
|
-
// `isLoggable`, `flush`, `close`, and the five
|
|
9
|
-
// every other platform of the port gives them.
|
|
8
|
+
// `isLoggable`, `requestFlush`, `flushNow`, `flush`, `close`, and the five
|
|
9
|
+
// settings, under the names every other platform of the port gives them.
|
|
10
10
|
//
|
|
11
11
|
// A TurboModule, and not a bridge module: `src/NativeXlog.ts` is the spec
|
|
12
12
|
// codegen reads, and `NativeXlogSpec` — the abstract class it generates into
|
|
@@ -30,14 +30,19 @@
|
|
|
30
30
|
|
|
31
31
|
package io.github.orangeboychen.marsrs.xlog.reactnative
|
|
32
32
|
|
|
33
|
+
import com.facebook.react.bridge.Arguments
|
|
34
|
+
import com.facebook.react.bridge.Promise
|
|
33
35
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
34
36
|
import com.facebook.react.bridge.ReadableMap
|
|
37
|
+
import com.facebook.react.bridge.WritableArray
|
|
35
38
|
import io.github.orangeboychen.marsrs.xlog.AppenderMode
|
|
36
39
|
import io.github.orangeboychen.marsrs.xlog.CompressMode
|
|
37
40
|
import io.github.orangeboychen.marsrs.xlog.LogLevel
|
|
38
41
|
import io.github.orangeboychen.marsrs.xlog.Xlog
|
|
39
42
|
import io.github.orangeboychen.marsrs.xlog.XlogConfig
|
|
40
43
|
import java.util.concurrent.ConcurrentHashMap
|
|
44
|
+
import java.util.concurrent.Executors
|
|
45
|
+
import java.util.concurrent.RejectedExecutionException
|
|
41
46
|
|
|
42
47
|
/** The Android half of `Xlog`. */
|
|
43
48
|
class XlogModule(reactContext: ReactApplicationContext) : NativeXlogSpec(reactContext) {
|
|
@@ -49,15 +54,22 @@ class XlogModule(reactContext: ReactApplicationContext) : NativeXlogSpec(reactCo
|
|
|
49
54
|
*/
|
|
50
55
|
private val appenders = ConcurrentHashMap<String, Xlog>()
|
|
51
56
|
|
|
57
|
+
/**
|
|
58
|
+
* The one thread `flush` drains on: a drain blocks the thread it runs on,
|
|
59
|
+
* and the JS thread is not one to block.
|
|
60
|
+
*/
|
|
61
|
+
private val flushQueue = Executors.newSingleThreadExecutor()
|
|
62
|
+
|
|
52
63
|
/**
|
|
53
64
|
* `Xlog.open(XlogConfig(...))`: opens the appender of the configuration
|
|
54
65
|
* `src/index.ts` sent, and answers whether it took it.
|
|
55
66
|
*
|
|
56
|
-
* One appender per prefix:
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
67
|
+
* One appender per prefix: `Xlog.open` answers the appender the prefix
|
|
68
|
+
* already has rather than a second one over its files, so asking again is
|
|
69
|
+
* how a prefix another part of the app closed is opened again — and a
|
|
70
|
+
* prefix whose appender is still open is answered as it is. `src/index.ts`
|
|
71
|
+
* does the same, so the two names an app holds for one prefix are one
|
|
72
|
+
* appender and `close` on either closes it for both.
|
|
61
73
|
*
|
|
62
74
|
* [Xlog.open] is what refuses a configuration the appender cannot honour —
|
|
63
75
|
* a blank `logDir` or `namePrefix`, a compression level out of range — and
|
|
@@ -67,26 +79,36 @@ class XlogModule(reactContext: ReactApplicationContext) : NativeXlogSpec(reactCo
|
|
|
67
79
|
*/
|
|
68
80
|
override fun open(config: ReadableMap): Boolean {
|
|
69
81
|
val namePrefix = config.string("namePrefix").ifBlank { DEFAULT_NAME_PREFIX }
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
compressLevel = config.int("compressLevel", DEFAULT_COMPRESS_LEVEL),
|
|
81
|
-
cacheDir = config.optionalString("cacheDir"),
|
|
82
|
-
cacheDays = config.int("cacheDays", NO_CACHE_DAYS)
|
|
83
|
-
)
|
|
82
|
+
// [appenders] holding one is not the same as there being one: the
|
|
83
|
+
// appender of a prefix is one for the whole process, so an `Xlog` of
|
|
84
|
+
// this prefix the app closed itself took it away, and answering `true`
|
|
85
|
+
// for it would leave every later call writing through a handle that
|
|
86
|
+
// has no appender behind it.
|
|
87
|
+
appenders[namePrefix]?.takeIf { it.isOpen }?.let { return true }
|
|
88
|
+
// Both calls and not the open only: `XlogConfig` is where a blank
|
|
89
|
+
// `logDir`, a negative `cacheDays` and a compression level out of
|
|
90
|
+
// range are refused, and it throws before `Xlog.open` is ever reached
|
|
91
|
+
// — so a `try` around the open alone is a `try` around nothing.
|
|
84
92
|
val xlog = try {
|
|
85
|
-
Xlog.open(
|
|
93
|
+
Xlog.open(
|
|
94
|
+
XlogConfig(
|
|
95
|
+
logDir = config.string("logDir"),
|
|
96
|
+
namePrefix = namePrefix,
|
|
97
|
+
level = LogLevel.of(config.int("level", LogLevel.INFO.ordinal)),
|
|
98
|
+
mode = appenderModeOf(config.int("mode", AppenderMode.ASYNC.ordinal)),
|
|
99
|
+
pubKey = config.string("pubKey"),
|
|
100
|
+
compressMode = compressModeOf(
|
|
101
|
+
config.int("compressMode", CompressMode.ZLIB.ordinal)
|
|
102
|
+
),
|
|
103
|
+
compressLevel = config.int("compressLevel", DEFAULT_COMPRESS_LEVEL),
|
|
104
|
+
cacheDir = config.optionalString("cacheDir"),
|
|
105
|
+
cacheDays = config.int("cacheDays", NO_CACHE_DAYS)
|
|
106
|
+
)
|
|
107
|
+
)
|
|
86
108
|
} catch (e: IllegalArgumentException) {
|
|
87
109
|
return false
|
|
88
110
|
}
|
|
89
|
-
appenders[
|
|
111
|
+
appenders[xlog.namePrefix] = xlog
|
|
90
112
|
return true
|
|
91
113
|
}
|
|
92
114
|
|
|
@@ -96,30 +118,100 @@ class XlogModule(reactContext: ReactApplicationContext) : NativeXlogSpec(reactCo
|
|
|
96
118
|
* too.
|
|
97
119
|
*/
|
|
98
120
|
override fun log(namePrefix: String, level: Double, tag: String, message: String) {
|
|
99
|
-
|
|
121
|
+
val native = int32(level) ?: return
|
|
122
|
+
appender(namePrefix)?.log(LogLevel.of(native), tag, message)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** `Xlog.currentLogPath`: the directory this appender writes its files
|
|
126
|
+
* into, or `null` once it is closed. */
|
|
127
|
+
override fun currentLogPath(namePrefix: String): String? = appender(namePrefix)?.currentLogPath
|
|
128
|
+
|
|
129
|
+
/** `Xlog.logFiles`: the day's files that are there. */
|
|
130
|
+
override fun logFiles(namePrefix: String, daysAgo: Double): WritableArray? {
|
|
131
|
+
val day = int32(daysAgo) ?: return Arguments.createArray()
|
|
132
|
+
return appender(namePrefix)?.logFiles(day.toLong())?.let { Arguments.fromList(it) }
|
|
133
|
+
?: Arguments.createArray()
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** `Xlog.logFileNames`: the day's names, whether or not they are there yet. */
|
|
137
|
+
override fun logFileNames(namePrefix: String, daysAgo: Double): WritableArray? {
|
|
138
|
+
val day = int32(daysAgo) ?: return Arguments.createArray()
|
|
139
|
+
return appender(namePrefix)?.logFileNames(day.toLong())?.let { Arguments.fromList(it) }
|
|
140
|
+
?: Arguments.createArray()
|
|
100
141
|
}
|
|
101
142
|
|
|
102
143
|
/** `Xlog.isLoggable`: whether a record of the level would be written. */
|
|
103
|
-
override fun isLoggable(namePrefix: String, level: Double): Boolean
|
|
104
|
-
|
|
144
|
+
override fun isLoggable(namePrefix: String, level: Double): Boolean {
|
|
145
|
+
val native = int32(level) ?: return false
|
|
146
|
+
return appender(namePrefix)?.isLoggable(LogLevel.of(native)) ?: false
|
|
147
|
+
}
|
|
105
148
|
|
|
106
|
-
/**
|
|
149
|
+
/**
|
|
150
|
+
* `Xlog.level`, read: what `marsrs-jni` answers, and not what JS holds.
|
|
151
|
+
*
|
|
152
|
+
* `NONE` and not a level the enum does not carry, which is what the iOS
|
|
153
|
+
* half of this module answers for the same numbers and the answer that is
|
|
154
|
+
* safe: `VERBOSE` is the ordinal `0`, so a `-1` read as a level is the
|
|
155
|
+
* level that logs everything — the opposite of what an appender that
|
|
156
|
+
* writes nothing was asked. An appender there is none of is `NONE` on
|
|
157
|
+
* both halves.
|
|
158
|
+
*/
|
|
107
159
|
override fun getLevel(namePrefix: String): Double =
|
|
108
160
|
appender(namePrefix)?.level?.ordinal?.toDouble() ?: LogLevel.NONE.ordinal.toDouble()
|
|
109
161
|
|
|
110
|
-
/** `Xlog.
|
|
111
|
-
override fun
|
|
112
|
-
appender(namePrefix)?.
|
|
162
|
+
/** `Xlog.requestFlush`: asks the writer thread to drain, answers nothing. */
|
|
163
|
+
override fun requestFlush(namePrefix: String) {
|
|
164
|
+
appender(namePrefix)?.requestFlush()
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** `Xlog.flushNow`: the drain that is over, records on disk, when it returns. */
|
|
168
|
+
override fun flushNow(namePrefix: String) {
|
|
169
|
+
appender(namePrefix)?.flushNow()
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* `Xlog.flushNow` off the thread JS runs on: the same drain, on a thread of
|
|
174
|
+
* this module's own, and `promise` settled when it is over.
|
|
175
|
+
*
|
|
176
|
+
* A TurboModule method that answers a promise is the one codegen calls off
|
|
177
|
+
* the JS thread, which is the whole reason this one answers one: a drain
|
|
178
|
+
* blocks the thread it runs on, and the JS thread is not one to block.
|
|
179
|
+
*
|
|
180
|
+
* A flush asked of a module [invalidate] has already shut the queue down
|
|
181
|
+
* is no drain and a settled promise: the `await` in JS does not return
|
|
182
|
+
* from a promise nobody resolved, and there is no appender left to drain.
|
|
183
|
+
*/
|
|
184
|
+
override fun flush(namePrefix: String, promise: Promise) {
|
|
185
|
+
try {
|
|
186
|
+
flushQueue.execute {
|
|
187
|
+
// Settled whatever the drain does, and not only when it ends
|
|
188
|
+
// well: a throw inside a `Runnable` is swallowed by the
|
|
189
|
+
// executor that was handed it, and a promise nobody settles
|
|
190
|
+
// is an `await` in JS that never comes back.
|
|
191
|
+
try {
|
|
192
|
+
appender(namePrefix)?.flushNow()
|
|
193
|
+
promise.resolve(null)
|
|
194
|
+
} catch (e: Exception) {
|
|
195
|
+
promise.reject(FLUSH_FAILED, e.message)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
} catch (e: RejectedExecutionException) {
|
|
199
|
+
// The queue is shut down, so the drain it was handed is one it
|
|
200
|
+
// will never run: settle here, and not in it.
|
|
201
|
+
promise.resolve(null)
|
|
202
|
+
}
|
|
113
203
|
}
|
|
114
204
|
|
|
115
205
|
/** `Xlog.level`. */
|
|
116
206
|
override fun setLevel(namePrefix: String, level: Double) {
|
|
117
|
-
|
|
207
|
+
val native = int32(level) ?: return
|
|
208
|
+
appender(namePrefix)?.level = LogLevel.of(native)
|
|
118
209
|
}
|
|
119
210
|
|
|
120
211
|
/** `Xlog.mode`. */
|
|
121
212
|
override fun setMode(namePrefix: String, mode: Double) {
|
|
122
|
-
|
|
213
|
+
val native = int32(mode) ?: return
|
|
214
|
+
appender(namePrefix)?.mode = appenderModeOf(native)
|
|
123
215
|
}
|
|
124
216
|
|
|
125
217
|
/** `Xlog.consoleLogEnabled`. */
|
|
@@ -130,25 +222,96 @@ class XlogModule(reactContext: ReactApplicationContext) : NativeXlogSpec(reactCo
|
|
|
130
222
|
/** `Xlog.maxFileSizeBytes`. A `Double` and not a `Long`: the module carries
|
|
131
223
|
* every JS number as one, and a file size is below 2^53. */
|
|
132
224
|
override fun setMaxFileSize(namePrefix: String, bytes: Double) {
|
|
133
|
-
|
|
225
|
+
val native = int64(bytes) ?: return
|
|
226
|
+
appender(namePrefix)?.maxFileSizeBytes = native
|
|
134
227
|
}
|
|
135
228
|
|
|
136
229
|
/** `Xlog.maxAliveTimeSeconds`. */
|
|
137
230
|
override fun setMaxAliveTime(namePrefix: String, seconds: Double) {
|
|
138
|
-
|
|
231
|
+
val native = int64(seconds) ?: return
|
|
232
|
+
appender(namePrefix)?.maxAliveTimeSeconds = native
|
|
139
233
|
}
|
|
140
234
|
|
|
141
|
-
/**
|
|
235
|
+
/**
|
|
236
|
+
* `Xlog.close`: releases the appender `open` made.
|
|
237
|
+
*
|
|
238
|
+
* A method that answers no promise is a call codegen makes on the JS
|
|
239
|
+
* thread, and `Xlog.close` is a drain of everything the appender is still
|
|
240
|
+
* holding and a write of the banner that ends the file — so this one is
|
|
241
|
+
* handed to the queue, the way [flush] is, and the JS thread is free again
|
|
242
|
+
* at once.
|
|
243
|
+
*
|
|
244
|
+
* Taken out of [appenders] here and not on the queue: a reopen of the
|
|
245
|
+
* prefix that lands while the drain is queued is otherwise an appender
|
|
246
|
+
* this close never held. What a queue that was shut down already means is
|
|
247
|
+
* that [closeAll] ran, and with it this close.
|
|
248
|
+
*/
|
|
142
249
|
override fun close(namePrefix: String) {
|
|
143
|
-
appenders.remove(namePrefix)
|
|
250
|
+
val appender = appenders.remove(namePrefix) ?: return
|
|
251
|
+
try {
|
|
252
|
+
flushQueue.execute { appender.close() }
|
|
253
|
+
} catch (e: RejectedExecutionException) {
|
|
254
|
+
appender.close()
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* What React Native calls when the bridge this module was made for goes
|
|
260
|
+
* away: the one place every appender [open] made is closed — see
|
|
261
|
+
* [closeAll] — and the one place the thread [flushQueue] runs on is shut
|
|
262
|
+
* down:
|
|
263
|
+
* `Executors.newSingleThreadExecutor` keeps a non-daemon thread alive
|
|
264
|
+
* until something shuts it, so a module that did not would leave one
|
|
265
|
+
* behind per bridge reload — each of them idle, and each of them a
|
|
266
|
+
* process that will not end while they are there.
|
|
267
|
+
*/
|
|
268
|
+
override fun invalidate() {
|
|
269
|
+
super.invalidate()
|
|
270
|
+
closeAll()
|
|
271
|
+
flushQueue.shutdown()
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Every appender [open] made, closed: what a module that is going away
|
|
276
|
+
* owes the files it was writing, and the only drain they will get — the
|
|
277
|
+
* queue is shut down below, so an appender left open is one whose records
|
|
278
|
+
* stay in its buffer, whose writer thread stays alive and whose cache
|
|
279
|
+
* stays claimed for the life of the process.
|
|
280
|
+
*
|
|
281
|
+
* Closed on the queue and not on this thread, which is the JS thread,
|
|
282
|
+
* because `Xlog.close` is itself a drain; and `shutdown` runs what it was
|
|
283
|
+
* handed before it stops, so these do run. What is not left to the queue
|
|
284
|
+
* is the map: [appenders] is emptied before the drain is handed over, so a
|
|
285
|
+
* `close` that arrives in the window between the two has no appender of
|
|
286
|
+
* its own to drain on the JS thread. A queue that refuses the drain — an
|
|
287
|
+
* `invalidate` before this one — is one this closes here instead.
|
|
288
|
+
*/
|
|
289
|
+
private fun closeAll() {
|
|
290
|
+
// Taken out of [appenders] here, and not on the queue below: a `close`
|
|
291
|
+
// of a prefix that lands before the drain runs is then a `close` with
|
|
292
|
+
// no appender to find, where the old order left it in the map for the
|
|
293
|
+
// length of the drain — and the window between this call and
|
|
294
|
+
// `flushQueue.shutdown()` is exactly when such a `close` arrives,
|
|
295
|
+
// because `shutdown` refuses what it is handed next and not what is
|
|
296
|
+
// already queued.
|
|
297
|
+
val closing = appenders.values.toList()
|
|
298
|
+
appenders.clear()
|
|
299
|
+
try {
|
|
300
|
+
flushQueue.execute { closing.forEach { it.close() } }
|
|
301
|
+
} catch (e: RejectedExecutionException) {
|
|
302
|
+
// A queue that refuses the drain is one this closes here: an
|
|
303
|
+
// appender that is going away with the module is drained now or
|
|
304
|
+
// not at all, and the records it still holds go with it.
|
|
305
|
+
closing.forEach { it.close() }
|
|
306
|
+
}
|
|
144
307
|
}
|
|
145
308
|
|
|
146
309
|
/**
|
|
147
310
|
* The appender of [namePrefix], or `null` when there is none — which is a
|
|
148
311
|
* no-op and not a crash, the same answer the Swift and the Kotlin give an
|
|
149
|
-
* `Xlog` that is closed:
|
|
150
|
-
* `marsrs-jni`, so a call that went on without one would write
|
|
151
|
-
*
|
|
312
|
+
* `Xlog` that is closed: a handle whose appender is gone is a no-op to
|
|
313
|
+
* `marsrs-jni`, so a call that went on without one would silently write
|
|
314
|
+
* nothing.
|
|
152
315
|
*/
|
|
153
316
|
private fun appender(namePrefix: String): Xlog? = appenders[namePrefix]?.takeIf { it.isOpen }
|
|
154
317
|
|
|
@@ -164,9 +327,12 @@ class XlogModule(reactContext: ReactApplicationContext) : NativeXlogSpec(reactCo
|
|
|
164
327
|
/** `0` — what the C++ passes on, which is the appender's own 6. */
|
|
165
328
|
private const val DEFAULT_COMPRESS_LEVEL = 0
|
|
166
329
|
|
|
167
|
-
/** `0` keeps every
|
|
330
|
+
/** `0` keeps every cache file, which is what the C++'s default is. */
|
|
168
331
|
private const val NO_CACHE_DAYS = 0
|
|
169
332
|
|
|
333
|
+
/** What a drain that threw is answered with: see [flush]. */
|
|
334
|
+
private const val FLUSH_FAILED = "E_XLOG_FLUSH"
|
|
335
|
+
|
|
170
336
|
/**
|
|
171
337
|
* `AppenderMode` of the number `mars_xlog.h` gives a mode: an ordinal
|
|
172
338
|
* the enum does not carry is one the caller sent by mistake, and `ASYNC`
|
|
@@ -182,6 +348,45 @@ class XlogModule(reactContext: ReactApplicationContext) : NativeXlogSpec(reactCo
|
|
|
182
348
|
CompressMode.ZSTD.ordinal -> CompressMode.ZSTD
|
|
183
349
|
else -> CompressMode.ZLIB
|
|
184
350
|
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* The number JS sent for a level, a mode or a day, and `null` when it
|
|
354
|
+
* is not one: a `NaN`, an infinity, and anything the `Int` the C ABI
|
|
355
|
+
* takes cannot hold.
|
|
356
|
+
*
|
|
357
|
+
* `Double.toInt()` answers `0` for a `NaN`, and `LogLevel.of` answers
|
|
358
|
+
* `VERBOSE` for a `0`, so a level JS computed and came to no level
|
|
359
|
+
* with — a sum with an `undefined` in it — was one the appender was
|
|
360
|
+
* *moved* to, and the one it was moved to is the level that logs
|
|
361
|
+
* everything: an app that meant to log nothing logged all of it. The
|
|
362
|
+
* iOS half of this module read the same number with a trap rather
|
|
363
|
+
* than a `0` — `Int32(_:)` on a `NaN` ends the app — and neither is
|
|
364
|
+
* an answer, so both leave the appender alone now.
|
|
365
|
+
*/
|
|
366
|
+
private fun int32(value: Double): Int? {
|
|
367
|
+
if (!value.isFinite() || value < Int.MIN_VALUE.toDouble() || value > Int.MAX_VALUE.toDouble()) {
|
|
368
|
+
return null
|
|
369
|
+
}
|
|
370
|
+
return value.toInt()
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* The number JS sent for a size or a file lifetime, and `null` when it
|
|
375
|
+
* is not one: the same three, over the `Long` the Kotlin takes. A
|
|
376
|
+
* `NaN` was answered as `0` — "never split" for a size, and for a
|
|
377
|
+
* lifetime the one an appender opened with none keeps, the C++'s own
|
|
378
|
+
* ten days, which no setter can ask for either — and that is a setting
|
|
379
|
+
* an app never asked for and not one it was told had been refused.
|
|
380
|
+
*/
|
|
381
|
+
private fun int64(value: Double): Long? {
|
|
382
|
+
if (!value.isFinite() ||
|
|
383
|
+
value < Long.MIN_VALUE.toDouble() ||
|
|
384
|
+
value >= Long.MAX_VALUE.toDouble()
|
|
385
|
+
) {
|
|
386
|
+
return null
|
|
387
|
+
}
|
|
388
|
+
return value.toLong()
|
|
389
|
+
}
|
|
185
390
|
}
|
|
186
391
|
}
|
|
187
392
|
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>AvailableLibraries</key>
|
|
6
|
+
<array>
|
|
7
|
+
<dict>
|
|
8
|
+
<key>BinaryPath</key>
|
|
9
|
+
<string>libmars_ffi.a</string>
|
|
10
|
+
<key>HeadersPath</key>
|
|
11
|
+
<string>Headers</string>
|
|
12
|
+
<key>LibraryIdentifier</key>
|
|
13
|
+
<string>ios-arm64_x86_64-simulator</string>
|
|
14
|
+
<key>LibraryPath</key>
|
|
15
|
+
<string>libmars_ffi.a</string>
|
|
16
|
+
<key>SupportedArchitectures</key>
|
|
17
|
+
<array>
|
|
18
|
+
<string>arm64</string>
|
|
19
|
+
<string>x86_64</string>
|
|
20
|
+
</array>
|
|
21
|
+
<key>SupportedPlatform</key>
|
|
22
|
+
<string>ios</string>
|
|
23
|
+
<key>SupportedPlatformVariant</key>
|
|
24
|
+
<string>simulator</string>
|
|
25
|
+
</dict>
|
|
26
|
+
<dict>
|
|
27
|
+
<key>BinaryPath</key>
|
|
28
|
+
<string>libmars_ffi.a</string>
|
|
29
|
+
<key>HeadersPath</key>
|
|
30
|
+
<string>Headers</string>
|
|
31
|
+
<key>LibraryIdentifier</key>
|
|
32
|
+
<string>watchos-arm64_arm64_32</string>
|
|
33
|
+
<key>LibraryPath</key>
|
|
34
|
+
<string>libmars_ffi.a</string>
|
|
35
|
+
<key>SupportedArchitectures</key>
|
|
36
|
+
<array>
|
|
37
|
+
<string>arm64</string>
|
|
38
|
+
<string>arm64_32</string>
|
|
39
|
+
</array>
|
|
40
|
+
<key>SupportedPlatform</key>
|
|
41
|
+
<string>watchos</string>
|
|
42
|
+
</dict>
|
|
43
|
+
<dict>
|
|
44
|
+
<key>BinaryPath</key>
|
|
45
|
+
<string>libmars_ffi.a</string>
|
|
46
|
+
<key>HeadersPath</key>
|
|
47
|
+
<string>Headers</string>
|
|
48
|
+
<key>LibraryIdentifier</key>
|
|
49
|
+
<string>watchos-arm64-simulator</string>
|
|
50
|
+
<key>LibraryPath</key>
|
|
51
|
+
<string>libmars_ffi.a</string>
|
|
52
|
+
<key>SupportedArchitectures</key>
|
|
53
|
+
<array>
|
|
54
|
+
<string>arm64</string>
|
|
55
|
+
</array>
|
|
56
|
+
<key>SupportedPlatform</key>
|
|
57
|
+
<string>watchos</string>
|
|
58
|
+
<key>SupportedPlatformVariant</key>
|
|
59
|
+
<string>simulator</string>
|
|
60
|
+
</dict>
|
|
61
|
+
<dict>
|
|
62
|
+
<key>BinaryPath</key>
|
|
63
|
+
<string>libmars_ffi.a</string>
|
|
64
|
+
<key>HeadersPath</key>
|
|
65
|
+
<string>Headers</string>
|
|
66
|
+
<key>LibraryIdentifier</key>
|
|
67
|
+
<string>ios-arm64</string>
|
|
68
|
+
<key>LibraryPath</key>
|
|
69
|
+
<string>libmars_ffi.a</string>
|
|
70
|
+
<key>SupportedArchitectures</key>
|
|
71
|
+
<array>
|
|
72
|
+
<string>arm64</string>
|
|
73
|
+
</array>
|
|
74
|
+
<key>SupportedPlatform</key>
|
|
75
|
+
<string>ios</string>
|
|
76
|
+
</dict>
|
|
77
|
+
</array>
|
|
78
|
+
<key>CFBundlePackageType</key>
|
|
79
|
+
<string>XFWK</string>
|
|
80
|
+
<key>XCFrameworkFormatVersion</key>
|
|
81
|
+
<string>1.0</string>
|
|
82
|
+
</dict>
|
|
83
|
+
</plist>
|