marsrs-react-native-xlog 0.1.0-alpha.2
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/README.md +88 -0
- 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 +2 -0
- package/android/.gradle/vcs-1/gc.properties +0 -0
- package/android/build.gradle.kts +49 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/kotlin/io/github/orangeboychen/marsrs/xlog/reactnative/XlogModule.kt +198 -0
- package/android/src/main/kotlin/io/github/orangeboychen/marsrs/xlog/reactnative/XlogPackage.kt +65 -0
- package/ios/Xlog-Bridging-Header.h +14 -0
- package/ios/Xlog.mm +63 -0
- package/ios/Xlog.swift +253 -0
- package/marsrs-react-native-xlog.podspec +57 -0
- package/package.json +46 -0
- package/src/NativeXlog.ts +63 -0
- package/src/index.ts +315 -0
package/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# marsrs-react-native-xlog
|
|
2
|
+
|
|
3
|
+
The React Native module of the xlog half of
|
|
4
|
+
[mars-rs](https://github.com/orangeboyChen/mars-rs), a Rust implementation of
|
|
5
|
+
[Tencent/mars](https://github.com/Tencent/mars): it writes the `.xlog` the C++
|
|
6
|
+
implementation produced, encrypted or plain, zlib- or zstd-compressed, sync or
|
|
7
|
+
async.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { Xlog, LogLevel } from 'marsrs-react-native-xlog';
|
|
11
|
+
|
|
12
|
+
const xlog = Xlog.open({
|
|
13
|
+
logDir: RNFS.DocumentDirectoryPath,
|
|
14
|
+
namePrefix: 'marsrs',
|
|
15
|
+
pubKey: '...',
|
|
16
|
+
});
|
|
17
|
+
xlog.i('Net', 'hello');
|
|
18
|
+
xlog.log(LogLevel.Debug, 'Net', 'a debug line');
|
|
19
|
+
xlog.flush(true);
|
|
20
|
+
xlog.close();
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`Xlog` is one appender: `Xlog.open(config)` opens it and `close()` closes it —
|
|
24
|
+
the same class, under the same name, the Swift package,
|
|
25
|
+
`platforms/kmp/marsrs-xlog` and the Android AAR publish. Nothing here answers
|
|
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
|
|
28
|
+
properties they are in Kotlin and in Swift — `xlog.level = LogLevel.Debug` —
|
|
29
|
+
and not `setLevel` / `getLevel` pairs.
|
|
30
|
+
|
|
31
|
+
That is what the New Architecture buys, and it is the only thing asked for in
|
|
32
|
+
return: a TurboModule is not a bridge module, so an app still on the old
|
|
33
|
+
architecture has no module to `TurboModuleRegistry.getEnforcing`.
|
|
34
|
+
|
|
35
|
+
## Installing it
|
|
36
|
+
|
|
37
|
+
`npm install marsrs-react-native-xlog`, and `pod install` for the iOS half: the
|
|
38
|
+
module is published to npm, and the release of a tag is what publishes it.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm install marsrs-react-native-xlog
|
|
42
|
+
cd ios && pod install
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
| | |
|
|
46
|
+
|---|---|
|
|
47
|
+
| version | the version of the release; `npm install` takes the newest published one |
|
|
48
|
+
| React Native | >= 0.74, on the New Architecture |
|
|
49
|
+
| iOS | 12.0 |
|
|
50
|
+
| Android | `minSdk` 24 |
|
|
51
|
+
|
|
52
|
+
The package ships its TypeScript as the `main` entry, which is what Metro
|
|
53
|
+
compiles — there is no build step, and `npm run typecheck` is the only script.
|
|
54
|
+
`src/NativeXlog.ts` is the spec React Native's codegen reads, and
|
|
55
|
+
`codegenConfig` in `package.json` is what points it at `src`: `NativeXlogSpec`
|
|
56
|
+
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 eleven signatures twice.
|
|
58
|
+
|
|
59
|
+
## What each platform resolves
|
|
60
|
+
|
|
61
|
+
The two halves do not get their native the same way, and the difference is the
|
|
62
|
+
ecosystem's, not the port's:
|
|
63
|
+
|
|
64
|
+
* **Android** depends on the `xlog` AAR of the same version
|
|
65
|
+
(`io.github.orangeboychen.marsrs:xlog`) from JitPack — the same coordinate,
|
|
66
|
+
resolved the same way, as for an app that takes the AAR directly. The `.so`
|
|
67
|
+
files are the AAR's, so nothing is built here and nothing Rust is needed.
|
|
68
|
+
* **iOS** carries `marsrs-xlog.xcframework` with it, in `ios/Frameworks`, and
|
|
69
|
+
`mars_xlog.h` next to it in `ios/include`. CocoaPods cannot resolve the SwiftPM
|
|
70
|
+
binary target of `Package.swift`, and there is no CocoaPods pod for it, so the
|
|
71
|
+
release drops the framework of its own tag into the package before packing it.
|
|
72
|
+
|
|
73
|
+
Both files arrive with the release and not with this directory: what is checked
|
|
74
|
+
in is the module, and `scripts/package_react_native.sh` is what makes a complete
|
|
75
|
+
package out of it.
|
|
76
|
+
|
|
77
|
+
## Why it is called xlog's
|
|
78
|
+
|
|
79
|
+
The C ABI is 28 `mars_xlog_*` symbols and nothing else, and
|
|
80
|
+
`scripts/build_xcframework.sh` fails the day it is not — the same reason the
|
|
81
|
+
framework and the AAR are called xlog's. `marsrs-react-native` is the umbrella
|
|
82
|
+
the whole port joins under: STN and SDT land there and not here, so an app that
|
|
83
|
+
only logs keeps this one and downloads nothing it does not call. Today the two
|
|
84
|
+
are the same module under two names, and taking the umbrella costs exactly what
|
|
85
|
+
this one costs.
|
|
86
|
+
|
|
87
|
+
Take one of the two and not both: both carry the same `libmarsrsxlog.so`, and an
|
|
88
|
+
app with two of it does not build.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
Binary file
|
|
File without changes
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// The Android half of `marsrs-react-native-xlog`: a `ReactPackage` over `Xlog`,
|
|
2
|
+
// the Kotlin face of `libmarsrsxlog.so` in the `xlog` AAR.
|
|
3
|
+
// `marsrs-react-native` is the same module over the `marsrs` AAR — the whole
|
|
4
|
+
// port, which today is the same xlog — and it is the one an app takes when it
|
|
5
|
+
// wants more than logging.
|
|
6
|
+
//
|
|
7
|
+
// Nothing is compiled from Rust here, and no `.so` is packaged either: the AAR
|
|
8
|
+
// of the release this module was packaged for carries them, which is what the
|
|
9
|
+
// dependency below is. `scripts/package_react_native.sh` stamps its version.
|
|
10
|
+
|
|
11
|
+
plugins {
|
|
12
|
+
id("com.android.library")
|
|
13
|
+
// No version, and no `apply false`: the app that takes this module is the
|
|
14
|
+
// one that pins the Kotlin plugin, and Gradle resolves the request below
|
|
15
|
+
// from there. The AAR modules of `platforms/android/` need no plugin at
|
|
16
|
+
// all — their AGP is pinned by this repository and is 9, which compiles
|
|
17
|
+
// Kotlin on its own — but an app's is not, and is 8 today.
|
|
18
|
+
id("org.jetbrains.kotlin.android")
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
android {
|
|
22
|
+
namespace = "io.github.orangeboychen.marsrs.xlog.reactnative"
|
|
23
|
+
compileSdk = 36
|
|
24
|
+
|
|
25
|
+
defaultConfig {
|
|
26
|
+
// React Native's own floor since 0.76, and above the 24 `mars-jni` is
|
|
27
|
+
// built against.
|
|
28
|
+
minSdk = 24
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
compileOptions {
|
|
32
|
+
sourceCompatibility = JavaVersion.VERSION_17
|
|
33
|
+
targetCompatibility = JavaVersion.VERSION_17
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
repositories {
|
|
38
|
+
google()
|
|
39
|
+
mavenCentral()
|
|
40
|
+
// `xlog`, the AAR of the release; JitPack is where it is published.
|
|
41
|
+
maven("https://jitpack.io")
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
dependencies {
|
|
45
|
+
// `react-android` and not `react-native`: the artifact React Native has
|
|
46
|
+
// published since 0.71, and the one the app's own repository resolves.
|
|
47
|
+
implementation("com.facebook.react:react-android")
|
|
48
|
+
implementation("io.github.orangeboychen.marsrs:xlog:0.1.0-alpha.2")
|
|
49
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
// The Android half of `marsrs-react-native-xlog`: the eleven methods of the
|
|
2
|
+
// `Xlog` native module, each of them a straight call of a member of `Xlog` — the
|
|
3
|
+
// Kotlin face of `libmarsrsxlog.so` in the `marsrs-xlog` AAR, and the same
|
|
4
|
+
// class `platforms/kmp/marsrs-xlog` publishes to a Kotlin Multiplatform app
|
|
5
|
+
// and `platforms/apple/MarsRSXlog/Xlog.swift` to a Swift one. So what this
|
|
6
|
+
// file is is a native module over an API that already exists, and nothing of
|
|
7
|
+
// the API is invented here: `Xlog.open(XlogConfig(...))`, `log`,
|
|
8
|
+
// `isLoggable`, `flush`, `close`, and the five settings, under the names
|
|
9
|
+
// every other platform of the port gives them.
|
|
10
|
+
//
|
|
11
|
+
// A TurboModule, and not a bridge module: `src/NativeXlog.ts` is the spec
|
|
12
|
+
// codegen reads, and `NativeXlogSpec` — the abstract class it generates into
|
|
13
|
+
// this package — is what this class extends. What that buys is a call that is
|
|
14
|
+
// made where it is asked for and returned from: a method that answers no promise
|
|
15
|
+
// is a call and not an `await`, and one that answers a value answers it before
|
|
16
|
+
// the call returns. That, and only that, is why `src/index.ts` has no `Promise`
|
|
17
|
+
// in it and no `await` in front of a line.
|
|
18
|
+
//
|
|
19
|
+
// `marsrs-react-native` is the same module over the `marsrs` AAR — the whole
|
|
20
|
+
// port, which today is the same xlog — and this file is its Kotlin with the two
|
|
21
|
+
// names changed: xlog is the whole C ABI today, so the two are one package under
|
|
22
|
+
// two names, and they diverge the day STN and SDT land — there, and not here.
|
|
23
|
+
//
|
|
24
|
+
// Every key below is a field `src/index.ts` put there, and every number is the
|
|
25
|
+
// one `mars_xlog.h` gives a level, a mode and a compression. The appenders are
|
|
26
|
+
// kept by the prefix they were opened with because that is what an appender is
|
|
27
|
+
// known by in the C ABI and in `marsrs-jni`: two `Xlog`s of two prefixes are
|
|
28
|
+
// two appenders, and one of a prefix the module already has is the appender it
|
|
29
|
+
// already has.
|
|
30
|
+
|
|
31
|
+
package io.github.orangeboychen.marsrs.xlog.reactnative
|
|
32
|
+
|
|
33
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
34
|
+
import com.facebook.react.bridge.ReadableMap
|
|
35
|
+
import io.github.orangeboychen.marsrs.xlog.AppenderMode
|
|
36
|
+
import io.github.orangeboychen.marsrs.xlog.CompressMode
|
|
37
|
+
import io.github.orangeboychen.marsrs.xlog.LogLevel
|
|
38
|
+
import io.github.orangeboychen.marsrs.xlog.Xlog
|
|
39
|
+
import io.github.orangeboychen.marsrs.xlog.XlogConfig
|
|
40
|
+
import java.util.concurrent.ConcurrentHashMap
|
|
41
|
+
|
|
42
|
+
/** The Android half of `Xlog`. */
|
|
43
|
+
class XlogModule(reactContext: ReactApplicationContext) : NativeXlogSpec(reactContext) {
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The appender of every prefix `open` has opened, by the prefix: what keeps
|
|
47
|
+
* two `Xlog`s apart, which a single field cannot — the C ABI and
|
|
48
|
+
* `marsrs-jni` both answer an appender by the name it was opened with.
|
|
49
|
+
*/
|
|
50
|
+
private val appenders = ConcurrentHashMap<String, Xlog>()
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* `Xlog.open(XlogConfig(...))`: opens the appender of the configuration
|
|
54
|
+
* `src/index.ts` sent, and answers whether it took it.
|
|
55
|
+
*
|
|
56
|
+
* One appender per prefix: a prefix [appenders] already holds is answered
|
|
57
|
+
* as `true` without a second `Xlog` over the first, which would be a handle
|
|
58
|
+
* nothing releases. `src/index.ts` does the same, so the two names an app
|
|
59
|
+
* holds for one prefix are one appender and `close` on either closes it for
|
|
60
|
+
* both.
|
|
61
|
+
*
|
|
62
|
+
* [Xlog.open] is what refuses a configuration the appender cannot honour —
|
|
63
|
+
* a blank `logDir` or `namePrefix`, a compression level out of range — and
|
|
64
|
+
* its `IllegalArgumentException` is what would cross back into JS as a
|
|
65
|
+
* crash, so it is caught here and answered as `false`: the caller's mistake
|
|
66
|
+
* to throw on, and not one to take the app with.
|
|
67
|
+
*/
|
|
68
|
+
override fun open(config: ReadableMap): Boolean {
|
|
69
|
+
val namePrefix = config.string("namePrefix").ifBlank { DEFAULT_NAME_PREFIX }
|
|
70
|
+
if (appenders.containsKey(namePrefix)) {
|
|
71
|
+
return true
|
|
72
|
+
}
|
|
73
|
+
val xlogConfig = XlogConfig(
|
|
74
|
+
logDir = config.string("logDir"),
|
|
75
|
+
namePrefix = namePrefix,
|
|
76
|
+
level = LogLevel.of(config.int("level", LogLevel.INFO.ordinal)),
|
|
77
|
+
mode = appenderModeOf(config.int("mode", AppenderMode.ASYNC.ordinal)),
|
|
78
|
+
pubKey = config.string("pubKey"),
|
|
79
|
+
compressMode = compressModeOf(config.int("compressMode", CompressMode.ZLIB.ordinal)),
|
|
80
|
+
compressLevel = config.int("compressLevel", DEFAULT_COMPRESS_LEVEL),
|
|
81
|
+
cacheDir = config.optionalString("cacheDir"),
|
|
82
|
+
cacheDays = config.int("cacheDays", NO_CACHE_DAYS)
|
|
83
|
+
)
|
|
84
|
+
val xlog = try {
|
|
85
|
+
Xlog.open(xlogConfig)
|
|
86
|
+
} catch (e: IllegalArgumentException) {
|
|
87
|
+
return false
|
|
88
|
+
}
|
|
89
|
+
appenders[xlogConfig.namePrefix] = xlog
|
|
90
|
+
return true
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* `Xlog.log`. The file, the function and the line are the C++'s own
|
|
95
|
+
* defaults: there is no JS frame to name, and the C++ writes an empty one
|
|
96
|
+
* too.
|
|
97
|
+
*/
|
|
98
|
+
override fun log(namePrefix: String, level: Double, tag: String, message: String) {
|
|
99
|
+
appender(namePrefix)?.log(LogLevel.of(level.toInt()), tag, message)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** `Xlog.isLoggable`: whether a record of the level would be written. */
|
|
103
|
+
override fun isLoggable(namePrefix: String, level: Double): Boolean =
|
|
104
|
+
appender(namePrefix)?.isLoggable(LogLevel.of(level.toInt())) ?: false
|
|
105
|
+
|
|
106
|
+
/** `Xlog.level`, read: what `marsrs-jni` answers, and not what JS holds. */
|
|
107
|
+
override fun getLevel(namePrefix: String): Double =
|
|
108
|
+
appender(namePrefix)?.level?.ordinal?.toDouble() ?: LogLevel.NONE.ordinal.toDouble()
|
|
109
|
+
|
|
110
|
+
/** `Xlog.flush`. */
|
|
111
|
+
override fun flush(namePrefix: String, sync: Boolean) {
|
|
112
|
+
appender(namePrefix)?.flush(sync)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** `Xlog.level`. */
|
|
116
|
+
override fun setLevel(namePrefix: String, level: Double) {
|
|
117
|
+
appender(namePrefix)?.level = LogLevel.of(level.toInt())
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** `Xlog.mode`. */
|
|
121
|
+
override fun setMode(namePrefix: String, mode: Double) {
|
|
122
|
+
appender(namePrefix)?.mode = appenderModeOf(mode.toInt())
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** `Xlog.consoleLogEnabled`. */
|
|
126
|
+
override fun setConsoleLogEnabled(namePrefix: String, enabled: Boolean) {
|
|
127
|
+
appender(namePrefix)?.consoleLogEnabled = enabled
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** `Xlog.maxFileSizeBytes`. A `Double` and not a `Long`: the module carries
|
|
131
|
+
* every JS number as one, and a file size is below 2^53. */
|
|
132
|
+
override fun setMaxFileSize(namePrefix: String, bytes: Double) {
|
|
133
|
+
appender(namePrefix)?.maxFileSizeBytes = bytes.toLong()
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** `Xlog.maxAliveTimeSeconds`. */
|
|
137
|
+
override fun setMaxAliveTime(namePrefix: String, seconds: Double) {
|
|
138
|
+
appender(namePrefix)?.maxAliveTimeSeconds = seconds.toLong()
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** `Xlog.close`: releases the appender `open` made. */
|
|
142
|
+
override fun close(namePrefix: String) {
|
|
143
|
+
appenders.remove(namePrefix)?.close()
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* The appender of [namePrefix], or `null` when there is none — which is a
|
|
148
|
+
* no-op and not a crash, the same answer the Swift and the Kotlin give an
|
|
149
|
+
* `Xlog` that is closed: no appender is the process-wide one to
|
|
150
|
+
* `marsrs-jni`, so a call that went on without one would write through
|
|
151
|
+
* whatever appender the rest of the process writes through.
|
|
152
|
+
*/
|
|
153
|
+
private fun appender(namePrefix: String): Xlog? = appenders[namePrefix]?.takeIf { it.isOpen }
|
|
154
|
+
|
|
155
|
+
internal companion object {
|
|
156
|
+
/** The name `src/index.ts` reads the module out of
|
|
157
|
+
* `TurboModuleRegistry` by — and the one codegen gave the generated
|
|
158
|
+
* `NativeXlogSpec`. */
|
|
159
|
+
const val NAME = "Xlog"
|
|
160
|
+
|
|
161
|
+
/** `XlogConfig.namePrefix` of the Kotlin, of the Swift and of the TS. */
|
|
162
|
+
private const val DEFAULT_NAME_PREFIX = "xlog"
|
|
163
|
+
|
|
164
|
+
/** `0` — what the C++ passes on, which is the appender's own 6. */
|
|
165
|
+
private const val DEFAULT_COMPRESS_LEVEL = 0
|
|
166
|
+
|
|
167
|
+
/** `0` keeps every log file, which is what the C++'s default is. */
|
|
168
|
+
private const val NO_CACHE_DAYS = 0
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* `AppenderMode` of the number `mars_xlog.h` gives a mode: an ordinal
|
|
172
|
+
* the enum does not carry is one the caller sent by mistake, and `ASYNC`
|
|
173
|
+
* is what the C++ opens with.
|
|
174
|
+
*/
|
|
175
|
+
fun appenderModeOf(ordinal: Int): AppenderMode = when (ordinal) {
|
|
176
|
+
AppenderMode.SYNC.ordinal -> AppenderMode.SYNC
|
|
177
|
+
else -> AppenderMode.ASYNC
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** `CompressMode` of the number `mars_xlog.h` gives a compression. */
|
|
181
|
+
fun compressModeOf(ordinal: Int): CompressMode = when (ordinal) {
|
|
182
|
+
CompressMode.ZSTD.ordinal -> CompressMode.ZSTD
|
|
183
|
+
else -> CompressMode.ZLIB
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** The number sent for [key], or [fallback] when none was sent: a field
|
|
189
|
+
* `src/index.ts` left out is not one it sent as `null`. */
|
|
190
|
+
private fun ReadableMap.int(key: String, fallback: Int): Int = if (hasKey(key)) getInt(key) else fallback
|
|
191
|
+
|
|
192
|
+
/** The string sent for [key], or the empty one. */
|
|
193
|
+
private fun ReadableMap.string(key: String): String = if (hasKey(key)) getString(key).orEmpty() else ""
|
|
194
|
+
|
|
195
|
+
/** The string sent for [key], or `null` — `cacheDir` is the field a caller may
|
|
196
|
+
* leave out, and `null` is what puts the cache in the log directory. */
|
|
197
|
+
private fun ReadableMap.optionalString(key: String): String? =
|
|
198
|
+
if (hasKey(key)) getString(key)?.takeIf { it.isNotBlank() } else null
|
package/android/src/main/kotlin/io/github/orangeboychen/marsrs/xlog/reactnative/XlogPackage.kt
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// The `ReactPackage` of the Android half of `marsrs-react-native-xlog`.
|
|
2
|
+
//
|
|
3
|
+
// Autolinking finds this class by the `ReactPackage` it is and the
|
|
4
|
+
// `react-native` directory it is in: it is what puts `XlogModule` in React
|
|
5
|
+
// Native's hands, and nothing in the app has to name either of them.
|
|
6
|
+
//
|
|
7
|
+
// A `BaseReactPackage` and not a `ReactPackage`: `BaseReactPackage` is the one
|
|
8
|
+
// that answers `getModule`, which is what React Native asks a TurboModule for,
|
|
9
|
+
// and the `ReactModuleInfo` below is what tells it this module is one. A package
|
|
10
|
+
// that only answered `createNativeModules` would be handed to the bridge, and
|
|
11
|
+
// every call would be a promise again — which is the whole of what
|
|
12
|
+
// `src/index.ts` is not.
|
|
13
|
+
|
|
14
|
+
package io.github.orangeboychen.marsrs.xlog.reactnative
|
|
15
|
+
|
|
16
|
+
import com.facebook.react.BaseReactPackage
|
|
17
|
+
import com.facebook.react.bridge.NativeModule
|
|
18
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
19
|
+
import com.facebook.react.module.model.ReactModuleInfo
|
|
20
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
21
|
+
|
|
22
|
+
/** The package of `Xlog`. */
|
|
23
|
+
class XlogPackage : BaseReactPackage() {
|
|
24
|
+
|
|
25
|
+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? =
|
|
26
|
+
if (name == XlogModule.NAME) XlogModule(reactContext) else null
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The one module this package has, and what React Native asks it about
|
|
30
|
+
* before it has an instance: its class, so that one can be made without
|
|
31
|
+
* asking, and `isTurboModule`, which is what sends the JSI after it rather
|
|
32
|
+
* than the bridge.
|
|
33
|
+
*/
|
|
34
|
+
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = ReactModuleInfoProvider {
|
|
35
|
+
mapOf(
|
|
36
|
+
XlogModule.NAME to ReactModuleInfo(
|
|
37
|
+
XlogModule.NAME,
|
|
38
|
+
XlogModule::class.java.name,
|
|
39
|
+
CAN_OVERRIDE_EXISTING_MODULE,
|
|
40
|
+
NEEDS_EAGER_INIT,
|
|
41
|
+
HAS_CONSTANTS,
|
|
42
|
+
IS_CXX_MODULE,
|
|
43
|
+
IS_TURBO_MODULE
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private companion object {
|
|
49
|
+
/** The module is the only one of its name an app has. */
|
|
50
|
+
const val CAN_OVERRIDE_EXISTING_MODULE = false
|
|
51
|
+
|
|
52
|
+
/** Nothing of the module is read before it is asked for. */
|
|
53
|
+
const val NEEDS_EAGER_INIT = false
|
|
54
|
+
|
|
55
|
+
/** The module carries no `constantsToExport`: a logger has none. */
|
|
56
|
+
const val HAS_CONSTANTS = false
|
|
57
|
+
|
|
58
|
+
/** Kotlin over a JNI bridge, and not a C++ module of React Native's
|
|
59
|
+
* own. */
|
|
60
|
+
const val IS_CXX_MODULE = false
|
|
61
|
+
|
|
62
|
+
/** What makes `src/index.ts` synchronous. */
|
|
63
|
+
const val IS_TURBO_MODULE = true
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// What the Swift of the pod sees of the C ABI: `mars_xlog.h`, copied out of
|
|
2
|
+
// `crates/marsrs-ffi/include` by `scripts/package_react_native.sh` rather than
|
|
3
|
+
// kept here, so it cannot drift from the header the Rust is built against.
|
|
4
|
+
//
|
|
5
|
+
// A bridging header and not an `import`, because the framework the pod carries
|
|
6
|
+
// is a static library with a C header: `MarsRSFFI`, the module SwiftPM makes of
|
|
7
|
+
// the two, is nothing CocoaPods can hand a pod.
|
|
8
|
+
//
|
|
9
|
+
// React Native is not here, and that is the point: `Xlog.swift` conforms to no
|
|
10
|
+
// React Native protocol, because the one it would conform to — `NativeXlogSpec`,
|
|
11
|
+
// which codegen wrote — is declared in a header that refuses to be imported by
|
|
12
|
+
// anything but Objective-C++. What the module owes React Native instead is in
|
|
13
|
+
// `Xlog.mm`, which says why there.
|
|
14
|
+
#include "mars_xlog.h"
|
package/ios/Xlog.mm
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// The one file of the iOS half of `marsrs-react-native-xlog` that Swift cannot
|
|
2
|
+
// be, and why: a TurboModule is made of a factory that answers a
|
|
3
|
+
// `std::shared_ptr<facebook::react::TurboModule>`, and `NativeXlogSpecJSI` — the
|
|
4
|
+
// C++ class codegen wrote out of `src/NativeXlog.ts` — is a class of templates
|
|
5
|
+
// and jsi types. Neither has a Swift spelling: Swift's C++ interop stops at
|
|
6
|
+
// templates and at the standard library. So what is here is the three things
|
|
7
|
+
// React Native asks a module for and cannot read out of `Xlog.swift`, and the
|
|
8
|
+
// eleven methods are there and not here.
|
|
9
|
+
//
|
|
10
|
+
// * `RCT_EXTERN_MODULE` registers the Swift class, which Swift cannot do for
|
|
11
|
+
// itself — registration is a constructor the runtime runs, and a Swift class
|
|
12
|
+
// has no `+load`. It is also what gives the class its `moduleName`, which is
|
|
13
|
+
// the name `src/index.ts` reads it out of `TurboModuleRegistry` by.
|
|
14
|
+
// * `RCTTurboModule` is adopted here, in a category, and not in the Swift:
|
|
15
|
+
// its one method is the C++ factory above, so a Swift class can neither name
|
|
16
|
+
// it nor be asked about it. `conformsToProtocol:` still answers `YES`, which
|
|
17
|
+
// is what `RCTTurboModuleManager` asks before it hands a module to JSI.
|
|
18
|
+
// * `methodQueue` answers `RCTJSThread`, the queue that is no queue: it is
|
|
19
|
+
// what makes a TurboModule's methods run on the JS thread, so a call is made
|
|
20
|
+
// where it is asked for and a method that answers a value answers it before
|
|
21
|
+
// it returns. Without it the eleven would be dispatched to a queue of the
|
|
22
|
+
// module's own and every answer would arrive as `undefined`.
|
|
23
|
+
//
|
|
24
|
+
// Objective-C++, and not Objective-C, because of the one `#include` a Swift
|
|
25
|
+
// class cannot make: `RCTNativeXlogSpec.h` opens with
|
|
26
|
+
// `#error This file must be compiled as Obj-C++`, and with good reason — it
|
|
27
|
+
// declares a C++ class.
|
|
28
|
+
|
|
29
|
+
#import <React/RCTBridgeModule.h>
|
|
30
|
+
#import <React/RCTJSThread.h>
|
|
31
|
+
#import <React/RCTTurboModule.h>
|
|
32
|
+
|
|
33
|
+
// What codegen wrote out of `src/NativeXlog.ts`: the protocol `NativeXlogSpec`
|
|
34
|
+
// and the C++ class `NativeXlogSpecJSI`, in one header. React Native names the
|
|
35
|
+
// file, and renamed it once — `RCTNativeXlogSpec.h` since 0.74,
|
|
36
|
+
// `NativeXlogSpec.h` before — so both spellings are asked for.
|
|
37
|
+
#if __has_include(<React-Codegen/RCTNativeXlogSpec.h>)
|
|
38
|
+
#import <React-Codegen/RCTNativeXlogSpec.h>
|
|
39
|
+
#else
|
|
40
|
+
#import <React-Codegen/NativeXlogSpec.h>
|
|
41
|
+
#endif
|
|
42
|
+
|
|
43
|
+
// `Xlog`, the Swift class, registered under the name `src/index.ts` reads it by.
|
|
44
|
+
@interface RCT_EXTERN_MODULE(Xlog, NSObject)
|
|
45
|
+
@end
|
|
46
|
+
|
|
47
|
+
@interface Xlog (XlogTurboModule) <RCTTurboModule>
|
|
48
|
+
@end
|
|
49
|
+
|
|
50
|
+
@implementation Xlog (XlogTurboModule)
|
|
51
|
+
|
|
52
|
+
- (dispatch_queue_t)methodQueue
|
|
53
|
+
{
|
|
54
|
+
return RCTJSThread;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
58
|
+
(const facebook::react::ObjCTurboModule::InitParams &)params
|
|
59
|
+
{
|
|
60
|
+
return std::make_shared<facebook::react::NativeXlogSpecJSI>(params);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@end
|
package/ios/Xlog.swift
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
// The iOS half of `marsrs-react-native-xlog`: the eleven methods of the `Xlog`
|
|
2
|
+
// native module, each of them a straight call of a `mars_xlog_*` symbol — the
|
|
3
|
+
// C ABI of `crates/marsrs-ffi`, in the `marsrs-xlog.xcframework` the pod carries.
|
|
4
|
+
//
|
|
5
|
+
// Swift, and not Objective-C — except for `Xlog.mm`, which is the one file of
|
|
6
|
+
// the module Swift cannot be, and which says why in its own header: a
|
|
7
|
+
// TurboModule is made of a factory that answers a `std::shared_ptr` of a C++
|
|
8
|
+
// class, and there is no Swift for either.
|
|
9
|
+
//
|
|
10
|
+
// The module is a TurboModule and not a bridge module. `src/NativeXlog.ts` is
|
|
11
|
+
// the spec codegen reads, and what it makes of it — `NativeXlogSpec`, the
|
|
12
|
+
// protocol, and `NativeXlogSpecJSI`, the C++ class behind it — is what lets a
|
|
13
|
+
// call land where it is asked for rather than cross a bridge on a queue: the
|
|
14
|
+
// module answers `RCTJSThread` as its method queue, and every method of it is
|
|
15
|
+
// therefore made on the JS thread and returned from. A method that answers no
|
|
16
|
+
// promise is a call and not an `await`, which is why `src/index.ts` can be the
|
|
17
|
+
// Kotlin and the Swift of the port member for member.
|
|
18
|
+
//
|
|
19
|
+
// The appenders are kept by the prefix they were opened with, because that is
|
|
20
|
+
// what an appender is known by in the C ABI: two `Xlog`s of two prefixes are two
|
|
21
|
+
// appenders, and one of a prefix the module already has is the appender it
|
|
22
|
+
// already has.
|
|
23
|
+
//
|
|
24
|
+
// `marsrs-react-native` is the whole-port module, and this file is its Swift
|
|
25
|
+
// with the two names changed: xlog is the whole C ABI today, so the two are one
|
|
26
|
+
// package under two names, and they diverge the day STN and SDT land — there,
|
|
27
|
+
// and not here.
|
|
28
|
+
|
|
29
|
+
import Foundation
|
|
30
|
+
|
|
31
|
+
/// The iOS half of `Xlog`.
|
|
32
|
+
///
|
|
33
|
+
/// `Xlog` and no suffix: the name `src/index.ts` reads the module out of
|
|
34
|
+
/// `TurboModuleRegistry` by is the class's.
|
|
35
|
+
@objc(Xlog)
|
|
36
|
+
internal final class Xlog: NSObject {
|
|
37
|
+
/// The handle of the appender of every prefix `open` has opened, by the
|
|
38
|
+
/// prefix: what keeps two `Xlog`s apart, which one handle cannot, because
|
|
39
|
+
/// the appender a `mars_xlog_release_instance` releases is the one of the
|
|
40
|
+
/// prefix it is given.
|
|
41
|
+
///
|
|
42
|
+
/// Written and read on the JS thread and nowhere else, which is what the
|
|
43
|
+
/// module's `RCTJSThread` method queue makes true: the dictionary is not
|
|
44
|
+
/// locked, and a logger asked to wait for one would be a logger an app
|
|
45
|
+
/// routes around.
|
|
46
|
+
private var handles: [String: Int64] = [:]
|
|
47
|
+
|
|
48
|
+
/// `mars_xlog_new_instance`: opens the appender of `config` and answers
|
|
49
|
+
/// whether it took the configuration.
|
|
50
|
+
///
|
|
51
|
+
/// `false` is a configuration it refused — an empty `logDir` or
|
|
52
|
+
/// `namePrefix`, or a directory it cannot write to — and it is what the JS
|
|
53
|
+
/// caller turns into a throw rather than a handle it would write through
|
|
54
|
+
/// the process-wide appender with.
|
|
55
|
+
@objc(open:)
|
|
56
|
+
internal func openAppender(_ config: [AnyHashable: Any]) -> Bool {
|
|
57
|
+
let logDir = string(config, "logDir")
|
|
58
|
+
guard !logDir.isEmpty else {
|
|
59
|
+
return false
|
|
60
|
+
}
|
|
61
|
+
let namePrefix = string(config, "namePrefix").isEmpty
|
|
62
|
+
? Self.defaultNamePrefix
|
|
63
|
+
: string(config, "namePrefix")
|
|
64
|
+
// One appender per prefix: a prefix this module has already opened is
|
|
65
|
+
// answered as it is, and not opened again over the first, which would be
|
|
66
|
+
// a handle nothing releases. `src/index.ts` keeps the same map, so the
|
|
67
|
+
// two names an app holds for one prefix are one appender, and `close` on
|
|
68
|
+
// either is `close` on both.
|
|
69
|
+
guard handles[namePrefix] == nil else {
|
|
70
|
+
return true
|
|
71
|
+
}
|
|
72
|
+
let level = int(config, "level", Int32(MarsLevelInfo.rawValue))
|
|
73
|
+
let mode = int(config, "mode", Int32(MarsAppenderAsync.rawValue))
|
|
74
|
+
let compressMode = int(config, "compressMode", Int32(MarsCompressZlib.rawValue))
|
|
75
|
+
let compressLevel = int(config, "compressLevel", Self.defaultCompressLevel)
|
|
76
|
+
let cacheDays = int(config, "cacheDays", Self.keepEveryFile)
|
|
77
|
+
|
|
78
|
+
var handle: Int64 = 0
|
|
79
|
+
// `pubKey` and `cacheDir` are the two fields a caller may leave out, and
|
|
80
|
+
// `mars_xlog.h` reads the empty string the way it reads `NULL`.
|
|
81
|
+
withCStrings([logDir, namePrefix, string(config, "pubKey"), string(config, "cacheDir")]) { pointers in
|
|
82
|
+
var native = MarsXLogConfig(
|
|
83
|
+
mode: mode,
|
|
84
|
+
log_dir: pointers[Field.logDir.rawValue],
|
|
85
|
+
name_prefix: pointers[Field.namePrefix.rawValue],
|
|
86
|
+
pub_key: pointers[Field.pubKey.rawValue],
|
|
87
|
+
compress_mode: compressMode,
|
|
88
|
+
compress_level: compressLevel,
|
|
89
|
+
cache_dir: pointers[Field.cacheDir.rawValue],
|
|
90
|
+
cache_days: cacheDays
|
|
91
|
+
)
|
|
92
|
+
handle = mars_xlog_new_instance(&native, level)
|
|
93
|
+
}
|
|
94
|
+
guard handle != 0 else {
|
|
95
|
+
return false
|
|
96
|
+
}
|
|
97
|
+
handles[namePrefix] = handle
|
|
98
|
+
return true
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// `mars_xlog_write_instance`. The file, the function and the line are left
|
|
102
|
+
/// empty: there is no JS frame to name, and the C++ writes an empty one too.
|
|
103
|
+
@objc(log:level:tag:message:)
|
|
104
|
+
internal func log(_ namePrefix: String, level: Double, tag: String, message: String) {
|
|
105
|
+
guard let handle = handles[namePrefix] else {
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
tag.withCString { cTag in
|
|
109
|
+
message.withCString { cMessage in
|
|
110
|
+
mars_xlog_write_instance(handle, Int32(level), cTag, nil, nil, 0, cMessage)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/// `mars_xlog_is_enabled_for`: whether a record of the level would be
|
|
116
|
+
/// written, which an app asks before it builds a message that is expensive
|
|
117
|
+
/// to build.
|
|
118
|
+
@objc(isLoggable:level:)
|
|
119
|
+
internal func isLoggable(_ namePrefix: String, level: Double) -> Bool {
|
|
120
|
+
guard let handle = handles[namePrefix] else {
|
|
121
|
+
return false
|
|
122
|
+
}
|
|
123
|
+
return mars_xlog_is_enabled_for(handle, Int32(level)) != 0
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/// `mars_xlog_get_level`: what the appender answers, and not what JS holds.
|
|
127
|
+
@objc(getLevel:)
|
|
128
|
+
internal func level(of namePrefix: String) -> Double {
|
|
129
|
+
guard let handle = handles[namePrefix] else {
|
|
130
|
+
return Double(MARS_LEVEL_NONE)
|
|
131
|
+
}
|
|
132
|
+
return Double(mars_xlog_get_level(handle))
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/// `mars_xlog_flush_instance`.
|
|
136
|
+
@objc(flush:sync:)
|
|
137
|
+
internal func flush(_ namePrefix: String, sync: Bool) {
|
|
138
|
+
guard let handle = handles[namePrefix] else {
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
mars_xlog_flush_instance(handle, sync ? 1 : 0)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/// `mars_xlog_set_level_instance`.
|
|
145
|
+
@objc(setLevel:level:)
|
|
146
|
+
internal func setLevel(_ namePrefix: String, level: Double) {
|
|
147
|
+
guard let handle = handles[namePrefix] else {
|
|
148
|
+
return
|
|
149
|
+
}
|
|
150
|
+
mars_xlog_set_level_instance(handle, Int32(level))
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/// `mars_xlog_set_mode_instance`.
|
|
154
|
+
@objc(setMode:mode:)
|
|
155
|
+
internal func setMode(_ namePrefix: String, mode: Double) {
|
|
156
|
+
guard let handle = handles[namePrefix] else {
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
mars_xlog_set_mode_instance(handle, Int32(mode))
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/// `mars_xlog_set_console_log_instance`.
|
|
163
|
+
@objc(setConsoleLogEnabled:enabled:)
|
|
164
|
+
internal func setConsoleLogEnabled(_ namePrefix: String, enabled: Bool) {
|
|
165
|
+
guard let handle = handles[namePrefix] else {
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
mars_xlog_set_console_log_instance(handle, enabled ? 1 : 0)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/// `mars_xlog_set_max_file_size_instance`. A `Double` and not a `UInt64`:
|
|
172
|
+
/// the module carries every JS number as one, and a file size is below
|
|
173
|
+
/// 2^53.
|
|
174
|
+
@objc(setMaxFileSize:bytes:)
|
|
175
|
+
internal func setMaxFileSize(_ namePrefix: String, bytes: Double) {
|
|
176
|
+
guard let handle = handles[namePrefix] else {
|
|
177
|
+
return
|
|
178
|
+
}
|
|
179
|
+
mars_xlog_set_max_file_size_instance(handle, UInt64(bytes))
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/// `mars_xlog_set_max_alive_duration_instance`.
|
|
183
|
+
@objc(setMaxAliveTime:seconds:)
|
|
184
|
+
internal func setMaxAliveTime(_ namePrefix: String, seconds: Double) {
|
|
185
|
+
guard let handle = handles[namePrefix] else {
|
|
186
|
+
return
|
|
187
|
+
}
|
|
188
|
+
mars_xlog_set_max_alive_duration_instance(handle, Int64(seconds))
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/// `mars_xlog_release_instance`: closes the appender `open` made.
|
|
192
|
+
@objc(close:)
|
|
193
|
+
internal func close(_ namePrefix: String) {
|
|
194
|
+
guard handles.removeValue(forKey: namePrefix) != nil else {
|
|
195
|
+
return
|
|
196
|
+
}
|
|
197
|
+
namePrefix.withCString { mars_xlog_release_instance($0) }
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/// Closes what `open` opened and `close` was not asked about: the module
|
|
201
|
+
/// goes away with the bridge, and an appender is a file its writer thread
|
|
202
|
+
/// holds open.
|
|
203
|
+
deinit {
|
|
204
|
+
for namePrefix in handles.keys {
|
|
205
|
+
namePrefix.withCString { mars_xlog_release_instance($0) }
|
|
206
|
+
}
|
|
207
|
+
handles.removeAll()
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/// `XlogConfig.namePrefix` of the Kotlin, of the Swift and of the TS: what
|
|
211
|
+
/// an appender is opened with when the caller gave none.
|
|
212
|
+
private static let defaultNamePrefix = "xlog"
|
|
213
|
+
|
|
214
|
+
/// What the C++ passes on, which is the appender's own 6.
|
|
215
|
+
private static let defaultCompressLevel: Int32 = 0
|
|
216
|
+
|
|
217
|
+
/// `0` keeps every log file, which is what the C++'s default is.
|
|
218
|
+
private static let keepEveryFile: Int32 = 0
|
|
219
|
+
|
|
220
|
+
/// The four fields of `MarsXLogConfig` that are C strings, in the order the
|
|
221
|
+
/// copies are made in: what names the index a pointer is read out of, which
|
|
222
|
+
/// is what a `pointers[2]` is not.
|
|
223
|
+
private enum Field: Int {
|
|
224
|
+
case logDir = 0
|
|
225
|
+
case namePrefix = 1
|
|
226
|
+
case pubKey = 2
|
|
227
|
+
case cacheDir = 3
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/// Runs `body` with every one of `strings` as a C string, and frees the
|
|
231
|
+
/// copies it made: `MarsXLogConfig` holds pointers rather than copies, and a
|
|
232
|
+
/// Swift `String` has no C string of its own to hand out — `withCString` is
|
|
233
|
+
/// the answer for one, and a configuration is four at once.
|
|
234
|
+
private func withCStrings(_ strings: [String], body: ([UnsafePointer<CChar>?]) -> Void) {
|
|
235
|
+
let copies: [UnsafeMutablePointer<CChar>?] = strings.map { strdup($0) }
|
|
236
|
+
defer { copies.forEach { free($0) } }
|
|
237
|
+
body(copies.map { $0.map { UnsafePointer($0) } })
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/// What the caller sent for `key`, read as a string; a field it left out is
|
|
241
|
+
/// not one it sent as `null`, and the empty string is what every field of
|
|
242
|
+
/// the configuration reads as "not given".
|
|
243
|
+
private func string(_ config: [AnyHashable: Any], _ key: String) -> String {
|
|
244
|
+
(config[key] as? String) ?? ""
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/// What the caller sent for `key`, read as an `Int32`, or `fallback` when it
|
|
248
|
+
/// sent nothing: a `Number`, because JS has one number type and the codec
|
|
249
|
+
/// hands over whichever the value fits in.
|
|
250
|
+
private func int(_ config: [AnyHashable: Any], _ key: String, _ fallback: Int32) -> Int32 {
|
|
251
|
+
(config[key] as? NSNumber)?.int32Value ?? fallback
|
|
252
|
+
}
|
|
253
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# The iOS half of `marsrs-react-native-xlog`.
|
|
2
|
+
#
|
|
3
|
+
# `marsrs-xlog.xcframework` is carried here and not resolved: CocoaPods cannot
|
|
4
|
+
# take the SwiftPM binary target of Package.swift, and there is no pod for it,
|
|
5
|
+
# so `.github/workflows/release.yml` drops the release's framework into
|
|
6
|
+
# `ios/Frameworks/` before the package is packed. `ios/include/mars_xlog.h` is
|
|
7
|
+
# the C ABI's own header, copied by `scripts/package_react_native.sh` out of
|
|
8
|
+
# `crates/marsrs-ffi/include` rather than kept here, so it cannot drift from it.
|
|
9
|
+
#
|
|
10
|
+
# The glue is Swift — `ios/Xlog.swift` — over the C header the framework
|
|
11
|
+
# carries, because `MarsRSFFI`, the module SwiftPM makes of the two, is nothing
|
|
12
|
+
# CocoaPods can hand a pod: `ios/Xlog-Bridging-Header.h` is what shows the Swift
|
|
13
|
+
# `mars_xlog.h`, and `ios/Xlog.mm` is the one file of the module that has no
|
|
14
|
+
# Swift spelling, which its own header says why.
|
|
15
|
+
#
|
|
16
|
+
# `React-Codegen` is what `src/NativeXlog.ts` is generated by: `NativeXlogSpec`,
|
|
17
|
+
# the protocol, and `NativeXlogSpecJSI`, the C++ class `Xlog.mm` hands React
|
|
18
|
+
# Native — a pod of the app's own `use_react_native!`, and the pod the generated
|
|
19
|
+
# headers land in.
|
|
20
|
+
#
|
|
21
|
+
# The version is package.json's, and not a number of its own: one place a
|
|
22
|
+
# release stamps.
|
|
23
|
+
|
|
24
|
+
require "json"
|
|
25
|
+
|
|
26
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
27
|
+
|
|
28
|
+
Pod::Spec.new do |s|
|
|
29
|
+
s.name = package["name"]
|
|
30
|
+
s.version = package["version"]
|
|
31
|
+
s.summary = package["description"]
|
|
32
|
+
s.homepage = package["homepage"]
|
|
33
|
+
s.license = package["license"]
|
|
34
|
+
s.author = { "orangeboyChen" => "https://github.com/orangeboyChen" }
|
|
35
|
+
s.source = { :git => "https://github.com/orangeboyChen/mars-rs.git",
|
|
36
|
+
:tag => "v#{s.version}" }
|
|
37
|
+
# What Package.swift declares for iOS; the framework's own floor.
|
|
38
|
+
s.platform = :ios, "12.0"
|
|
39
|
+
s.swift_version = "5.0"
|
|
40
|
+
s.source_files = "ios/*.{h,mm,swift}"
|
|
41
|
+
s.vendored_frameworks = "ios/Frameworks/marsrs-xlog.xcframework"
|
|
42
|
+
s.preserve_paths = "ios/include/mars_xlog.h"
|
|
43
|
+
# One header for the pod's Swift to be bridged to the C ABI with, and one for
|
|
44
|
+
# the app's to be bridged to this pod's ObjC with. The first is the module's
|
|
45
|
+
# own; the second is the one CocoaPods reads when it compiles a pod that mixes
|
|
46
|
+
# the two languages.
|
|
47
|
+
s.xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/ios/include\"",
|
|
48
|
+
"SWIFT_OBJC_BRIDGING_HEADER" => "\"$(PODS_TARGET_SRCROOT)/ios/Xlog-Bridging-Header.h\"" }
|
|
49
|
+
# `iana-time-zone` calls `CFTimeZone*`, and a Rust static library carries no
|
|
50
|
+
# link flags of its own: Package.swift names the framework for the same reason.
|
|
51
|
+
s.frameworks = "CoreFoundation"
|
|
52
|
+
# React Native's own, in place of `s.dependency "React-Core"`: it is what the
|
|
53
|
+
# app's `use_react_native!` provides, and it keeps the pod off a version of
|
|
54
|
+
# React Native the app did not choose.
|
|
55
|
+
install_modules_dependencies(s)
|
|
56
|
+
s.dependency "React-Codegen"
|
|
57
|
+
end
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "marsrs-react-native-xlog",
|
|
3
|
+
"version": "0.1.0-alpha.2",
|
|
4
|
+
"description": "React Native module for xlog, the logging half of mars-rs: a Rust implementation of Tencent/mars that writes the .xlog the C++ implementation produced.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://github.com/orangeboyChen/mars-rs",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/orangeboyChen/mars-rs.git",
|
|
10
|
+
"directory": "platforms/react-native/marsrs-xlog"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/orangeboyChen/mars-rs/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"mars",
|
|
17
|
+
"xlog",
|
|
18
|
+
"logging",
|
|
19
|
+
"react-native"
|
|
20
|
+
],
|
|
21
|
+
"main": "src/index.ts",
|
|
22
|
+
"types": "src/index.ts",
|
|
23
|
+
"files": [
|
|
24
|
+
"src",
|
|
25
|
+
"android",
|
|
26
|
+
"ios",
|
|
27
|
+
"*.podspec",
|
|
28
|
+
"README.md",
|
|
29
|
+
"LICENSE"
|
|
30
|
+
],
|
|
31
|
+
"scripts": {
|
|
32
|
+
"typecheck": "tsc --noEmit"
|
|
33
|
+
},
|
|
34
|
+
"codegenConfig": {
|
|
35
|
+
"name": "NativeXlogSpec",
|
|
36
|
+
"type": "modules",
|
|
37
|
+
"jsSrcsDir": "src",
|
|
38
|
+
"android": {
|
|
39
|
+
"javaPackageName": "io.github.orangeboychen.marsrs.xlog.reactnative"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"react": ">=18.0.0",
|
|
44
|
+
"react-native": ">=0.74.0"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// The TurboModule spec of `Xlog`: the file codegen reads, and what
|
|
2
|
+
// `src/index.ts` calls at runtime.
|
|
3
|
+
//
|
|
4
|
+
// Codegen makes two things of it — `NativeXlogSpec`, the abstract class the
|
|
5
|
+
// Android half extends, and `NativeXlogSpec`, the protocol the iOS half conforms
|
|
6
|
+
// to — and the two of them are what let a call land where it is made instead of
|
|
7
|
+
// crossing a bridge: a TurboModule method that answers no promise is called on
|
|
8
|
+
// the JS thread and returns, and one that answers a value answers it before it
|
|
9
|
+
// returns. That is the only reason `src/index.ts` can be the Kotlin and the
|
|
10
|
+
// Swift member for member — `Xlog.open(config)`, `xlog.level`,
|
|
11
|
+
// `xlog.i(tag, message)` — where the bridge's eleven `Promise`s left it with a
|
|
12
|
+
// `setLevel`, a `getLevel` and an `await` in front of every line.
|
|
13
|
+
//
|
|
14
|
+
// The configuration crosses as an `Object` and not as a declared type: codegen
|
|
15
|
+
// reads this file and not the ones it imports, so `XlogConfig` is documented in
|
|
16
|
+
// `src/index.ts`, where an app reads it — and a `ReadableMap` and an
|
|
17
|
+
// `NSDictionary` is what the two halves take either way.
|
|
18
|
+
|
|
19
|
+
import type { TurboModule } from 'react-native';
|
|
20
|
+
import { TurboModuleRegistry } from 'react-native';
|
|
21
|
+
|
|
22
|
+
export interface Spec extends TurboModule {
|
|
23
|
+
/** `Xlog.open`: whether the appender took the configuration. `false` is a
|
|
24
|
+
* configuration it refused, which is an empty `logDir` or `namePrefix`, or a
|
|
25
|
+
* directory it cannot write to. */
|
|
26
|
+
open(config: Object): boolean;
|
|
27
|
+
|
|
28
|
+
/** `mars_xlog_write_instance`. The file, the function and the line are left
|
|
29
|
+
* empty on the platform side: there is no JS frame to name, and the C++ writes
|
|
30
|
+
* an empty one too. */
|
|
31
|
+
log(namePrefix: string, level: number, tag: string, message: string): void;
|
|
32
|
+
|
|
33
|
+
/** `mars_xlog_is_enabled_for`: whether a record of the level would be written. */
|
|
34
|
+
isLoggable(namePrefix: string, level: number): boolean;
|
|
35
|
+
|
|
36
|
+
/** `mars_xlog_get_level`: the level the appender is at, and not the one JS
|
|
37
|
+
* holds. */
|
|
38
|
+
getLevel(namePrefix: string): number;
|
|
39
|
+
|
|
40
|
+
/** `mars_xlog_flush_instance`. */
|
|
41
|
+
flush(namePrefix: string, sync: boolean): void;
|
|
42
|
+
|
|
43
|
+
/** `mars_xlog_set_level_instance`. */
|
|
44
|
+
setLevel(namePrefix: string, level: number): void;
|
|
45
|
+
|
|
46
|
+
/** `mars_xlog_set_mode_instance`. */
|
|
47
|
+
setMode(namePrefix: string, mode: number): void;
|
|
48
|
+
|
|
49
|
+
/** `mars_xlog_set_console_log_instance`. */
|
|
50
|
+
setConsoleLogEnabled(namePrefix: string, enabled: boolean): void;
|
|
51
|
+
|
|
52
|
+
/** `mars_xlog_set_max_file_size_instance`. A `number` and not an `Int64`: the
|
|
53
|
+
* module carries every JS number as one, and a file size is below 2^53. */
|
|
54
|
+
setMaxFileSize(namePrefix: string, bytes: number): void;
|
|
55
|
+
|
|
56
|
+
/** `mars_xlog_set_max_alive_duration_instance`. */
|
|
57
|
+
setMaxAliveTime(namePrefix: string, seconds: number): void;
|
|
58
|
+
|
|
59
|
+
/** `mars_xlog_release_instance`: releases the appender `open` made. */
|
|
60
|
+
close(namePrefix: string): void;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export default TurboModuleRegistry.getEnforcing<Spec>('Xlog');
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
// The TypeScript face of the xlog half of mars-rs.
|
|
2
|
+
//
|
|
3
|
+
// A TurboModule, and not a bridge module: `src/NativeXlog.ts` is the spec
|
|
4
|
+
// codegen reads, and what it answers is a module whose methods are called where
|
|
5
|
+
// they are asked for and return before the next line runs — one that answers no
|
|
6
|
+
// promise is a call and not an `await`, and one that answers a value answers it
|
|
7
|
+
// before it returns. So this `Xlog` is the `Xlog` of
|
|
8
|
+
// `platforms/apple/MarsRSXlog/Xlog.swift`, of
|
|
9
|
+
// `platforms/android/marsrs-xlog` and of `platforms/kmp/marsrs-xlog` member
|
|
10
|
+
// for member: `Xlog.open(config)` answers the appender, `xlog.level` is the
|
|
11
|
+
// property it is in Kotlin and in Swift, and `xlog.i(tag, message)` has
|
|
12
|
+
// landed by the time it returns.
|
|
13
|
+
//
|
|
14
|
+
// Five settings are properties and not `setLevel` / `getLevel` pairs, because a
|
|
15
|
+
// JS property is the Kotlin and the Swift spelling and a TurboModule setter is
|
|
16
|
+
// called where it is written: `xlog.level` reads the appender's own and writes
|
|
17
|
+
// it, and `mode`, `consoleLogEnabled`, `maxFileSizeBytes` and
|
|
18
|
+
// `maxAliveTimeSeconds` answer what this instance last wrote — the C ABI has no
|
|
19
|
+
// getter for those four, and neither the Kotlin nor the Swift answers one.
|
|
20
|
+
|
|
21
|
+
import NativeXlog from './NativeXlog';
|
|
22
|
+
|
|
23
|
+
/** `TLogLevel`; `none` is `MARS_LEVEL_NONE`, which the filter understands but
|
|
24
|
+
* the C enum does not carry. */
|
|
25
|
+
export const LogLevel = {
|
|
26
|
+
verbose: 0,
|
|
27
|
+
debug: 1,
|
|
28
|
+
info: 2,
|
|
29
|
+
warning: 3,
|
|
30
|
+
error: 4,
|
|
31
|
+
fatal: 5,
|
|
32
|
+
none: 6,
|
|
33
|
+
} as const;
|
|
34
|
+
|
|
35
|
+
export type LogLevel = (typeof LogLevel)[keyof typeof LogLevel];
|
|
36
|
+
|
|
37
|
+
/** `TAppenderMode`: whether a record reaches the file before the call returns. */
|
|
38
|
+
export const AppenderMode = {
|
|
39
|
+
/** The record goes into the memory-mapped cache and a writer thread takes it
|
|
40
|
+
* to the file — what the C++ opens with, and what `flush` drains. */
|
|
41
|
+
async: 0,
|
|
42
|
+
/** The record is in the file before the call returns, at a write and a lock
|
|
43
|
+
* per line. */
|
|
44
|
+
sync: 1,
|
|
45
|
+
} as const;
|
|
46
|
+
|
|
47
|
+
export type AppenderMode = (typeof AppenderMode)[keyof typeof AppenderMode];
|
|
48
|
+
|
|
49
|
+
/** `TCompressMode`: how a log file is compressed once it is closed. */
|
|
50
|
+
export const CompressMode = {
|
|
51
|
+
/** zlib, what the C++ opens with and what every reader of a mars log file
|
|
52
|
+
* understands. */
|
|
53
|
+
zlib: 0,
|
|
54
|
+
/** Zstandard: smaller at the same speed, and unreadable by a tool that only
|
|
55
|
+
* knows zlib. */
|
|
56
|
+
zstd: 1,
|
|
57
|
+
} as const;
|
|
58
|
+
|
|
59
|
+
export type CompressMode = (typeof CompressMode)[keyof typeof CompressMode];
|
|
60
|
+
|
|
61
|
+
/** `XLogConfig` of the C++ project, in the spelling the Kotlin of the port
|
|
62
|
+
* gives it: what an `Xlog` is opened with.
|
|
63
|
+
*
|
|
64
|
+
* Every field has the default the C++'s own `XLogConfig` carries, so the one an
|
|
65
|
+
* app has to give is `logDir` — the appender answers
|
|
66
|
+
* `MARS_XLOG_ERR_EMPTY_LOG_DIR` without it. */
|
|
67
|
+
export interface XlogConfig {
|
|
68
|
+
/** Where the log files are written; created if it is missing, and the one
|
|
69
|
+
* field with no default. */
|
|
70
|
+
logDir: string;
|
|
71
|
+
/** What every log file starts with (`marsrs_20260927.xlog`), and the name the
|
|
72
|
+
* appender is known by — an app that writes through two of them gives them
|
|
73
|
+
* two. `xlog` when left out. */
|
|
74
|
+
namePrefix?: string;
|
|
75
|
+
/** The level the appender is opened at; `info` when left out. */
|
|
76
|
+
level?: LogLevel;
|
|
77
|
+
/** Whether a write reaches the file before it returns. */
|
|
78
|
+
mode?: AppenderMode;
|
|
79
|
+
/** Empty means the log is written unencrypted. */
|
|
80
|
+
pubKey?: string;
|
|
81
|
+
/** How a closed log file is compressed. */
|
|
82
|
+
compressMode?: CompressMode;
|
|
83
|
+
/** `0` keeps the appender's own default (6). */
|
|
84
|
+
compressLevel?: number;
|
|
85
|
+
/** Left out, the mmap cache lives in the log directory. */
|
|
86
|
+
cacheDir?: string;
|
|
87
|
+
/** `0` keeps every file. */
|
|
88
|
+
cacheDays?: number;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** `XlogConfig.namePrefix` of the Kotlin and of the Swift: what an appender is
|
|
92
|
+
* opened with when a caller gives none. */
|
|
93
|
+
const DEFAULT_NAME_PREFIX = 'xlog';
|
|
94
|
+
|
|
95
|
+
/** The appender of every `namePrefix` `Xlog.open` has opened and `close` has
|
|
96
|
+
* not closed, by the prefix: what makes two `Xlog.open`s of one prefix one
|
|
97
|
+
* appender, which the native side already is — one module, one appender per
|
|
98
|
+
* prefix, and every call carrying the prefix it is about.
|
|
99
|
+
*
|
|
100
|
+
* The second `open` answers the appender the first made, so the two names an
|
|
101
|
+
* app holds are one `Xlog`: `close` on either is `close` on both, and nothing
|
|
102
|
+
* writes through an appender that is gone — which is what it would do, and in
|
|
103
|
+
* silence, if the second call were a second instance the first one's `close`
|
|
104
|
+
* did not know about. */
|
|
105
|
+
const openAppenders = new Map<string, Xlog>();
|
|
106
|
+
|
|
107
|
+
/** The appender of one `namePrefix`: the `Xlog` of the Swift and the Kotlin of
|
|
108
|
+
* the port. `Xlog.open` makes it, and `close` releases the appender it made. */
|
|
109
|
+
export class Xlog {
|
|
110
|
+
/** The name the appender is known by, and what every one of its files starts
|
|
111
|
+
* with: the `namePrefix` of the Kotlin and of the Swift. */
|
|
112
|
+
readonly namePrefix: string;
|
|
113
|
+
|
|
114
|
+
private currentLevel: LogLevel;
|
|
115
|
+
|
|
116
|
+
private currentMode: AppenderMode;
|
|
117
|
+
|
|
118
|
+
private currentConsoleLogEnabled = false;
|
|
119
|
+
|
|
120
|
+
private currentMaxFileSize = 0;
|
|
121
|
+
|
|
122
|
+
private currentMaxAliveTime = 0;
|
|
123
|
+
|
|
124
|
+
/** Whether `close` has been called on this instance. */
|
|
125
|
+
private open = true;
|
|
126
|
+
|
|
127
|
+
private constructor(config: XlogConfig) {
|
|
128
|
+
this.namePrefix = config.namePrefix ?? DEFAULT_NAME_PREFIX;
|
|
129
|
+
this.currentLevel = config.level ?? LogLevel.info;
|
|
130
|
+
this.currentMode = config.mode ?? AppenderMode.async;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** `mars_xlog_new_instance`: opens the appender of `config` and answers the
|
|
134
|
+
* `Xlog` that writes through it.
|
|
135
|
+
*
|
|
136
|
+
* The appender of a `namePrefix` this has already opened is answered as it
|
|
137
|
+
* is, and not opened again: the native side is one appender per prefix, and a
|
|
138
|
+
* second one over the first would be a handle nothing releases. So two calls
|
|
139
|
+
* of one prefix are one `Xlog`, and `close` on it is `close` on both.
|
|
140
|
+
*
|
|
141
|
+
* Throws when the appender would not take the configuration — an empty
|
|
142
|
+
* `logDir` or `namePrefix`, or a directory it cannot write to. */
|
|
143
|
+
static open(config: XlogConfig): Xlog {
|
|
144
|
+
const namePrefix = config.namePrefix ?? DEFAULT_NAME_PREFIX;
|
|
145
|
+
const alreadyOpen = openAppenders.get(namePrefix);
|
|
146
|
+
if (alreadyOpen) {
|
|
147
|
+
return alreadyOpen;
|
|
148
|
+
}
|
|
149
|
+
const xlog = new Xlog(config);
|
|
150
|
+
if (!NativeXlog.open(config)) {
|
|
151
|
+
throw new Error(
|
|
152
|
+
`marsrs-react-native-xlog: the appender of '${xlog.namePrefix}' refused ${config.logDir}`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
openAppenders.set(namePrefix, xlog);
|
|
156
|
+
return xlog;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Whether the appender is still open: `false` after `close`. */
|
|
160
|
+
get isOpen(): boolean {
|
|
161
|
+
return this.open;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** The level of this appender: what the appender answers, and not what this
|
|
165
|
+
* instance holds — `mars_xlog_get_level`, which is the one of the five
|
|
166
|
+
* settings the C ABI has a getter for. */
|
|
167
|
+
get level(): LogLevel {
|
|
168
|
+
if (!this.open) {
|
|
169
|
+
return this.currentLevel;
|
|
170
|
+
}
|
|
171
|
+
const level = NativeXlog.getLevel(this.namePrefix) as LogLevel;
|
|
172
|
+
this.currentLevel = level;
|
|
173
|
+
return level;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
set level(level: LogLevel) {
|
|
177
|
+
if (!this.open) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
NativeXlog.setLevel(this.namePrefix, level);
|
|
181
|
+
this.currentLevel = level;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** The mode of this appender: what `open` or the last write to it gave it,
|
|
185
|
+
* because the C ABI is asked for a mode and never answers one. */
|
|
186
|
+
get mode(): AppenderMode {
|
|
187
|
+
return this.currentMode;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
set mode(mode: AppenderMode) {
|
|
191
|
+
if (!this.open) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
NativeXlog.setMode(this.namePrefix, mode);
|
|
195
|
+
this.currentMode = mode;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Whether the console prints the log too: what the last write to it gave it,
|
|
199
|
+
* `false` when there was none. */
|
|
200
|
+
get consoleLogEnabled(): boolean {
|
|
201
|
+
return this.currentConsoleLogEnabled;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
set consoleLogEnabled(enabled: boolean) {
|
|
205
|
+
if (!this.open) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
NativeXlog.setConsoleLogEnabled(this.namePrefix, enabled);
|
|
209
|
+
this.currentConsoleLogEnabled = enabled;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** The size a log file is split at, in bytes; `0` never splits. */
|
|
213
|
+
get maxFileSizeBytes(): number {
|
|
214
|
+
return this.currentMaxFileSize;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
set maxFileSizeBytes(bytes: number) {
|
|
218
|
+
if (!this.open) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
NativeXlog.setMaxFileSize(this.namePrefix, bytes);
|
|
222
|
+
this.currentMaxFileSize = bytes;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** How long a log file is written to before the appender opens the next one,
|
|
226
|
+
* in seconds; `0` is the C++'s own ten days. */
|
|
227
|
+
get maxAliveTimeSeconds(): number {
|
|
228
|
+
return this.currentMaxAliveTime;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
set maxAliveTimeSeconds(seconds: number) {
|
|
232
|
+
if (!this.open) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
NativeXlog.setMaxAliveTime(this.namePrefix, seconds);
|
|
236
|
+
this.currentMaxAliveTime = seconds;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Whether a record at `level` is written: the appender's own answer, and the
|
|
240
|
+
* one the Swift's `isLoggable` and the Kotlin's give. An app asks it before it
|
|
241
|
+
* builds a message that is expensive to build. */
|
|
242
|
+
isLoggable(level: LogLevel): boolean {
|
|
243
|
+
if (!this.open) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
return NativeXlog.isLoggable(this.namePrefix, level);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** `mars_xlog_write_instance`: writes `message` at `level`, tagged `tag`.
|
|
250
|
+
*
|
|
251
|
+
* The file, the function and the line of the record are left empty on the
|
|
252
|
+
* platform side — there is no JS frame to name, and the C++ writes an empty
|
|
253
|
+
* one too. */
|
|
254
|
+
log(level: LogLevel, tag: string, message: string): void {
|
|
255
|
+
if (!this.open) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
NativeXlog.log(this.namePrefix, level, tag, message);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** `log` at `LogLevel.verbose`. */
|
|
262
|
+
v(tag: string, message: string): void {
|
|
263
|
+
this.log(LogLevel.verbose, tag, message);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** `log` at `LogLevel.debug`. */
|
|
267
|
+
d(tag: string, message: string): void {
|
|
268
|
+
this.log(LogLevel.debug, tag, message);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** `log` at `LogLevel.info`. */
|
|
272
|
+
i(tag: string, message: string): void {
|
|
273
|
+
this.log(LogLevel.info, tag, message);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** `log` at `LogLevel.warning`. */
|
|
277
|
+
w(tag: string, message: string): void {
|
|
278
|
+
this.log(LogLevel.warning, tag, message);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** `log` at `LogLevel.error`. */
|
|
282
|
+
e(tag: string, message: string): void {
|
|
283
|
+
this.log(LogLevel.error, tag, message);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** `log` at `LogLevel.fatal`. */
|
|
287
|
+
f(tag: string, message: string): void {
|
|
288
|
+
this.log(LogLevel.fatal, tag, message);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** `mars_xlog_flush_instance`: takes what is in the cache to the file. `sync`
|
|
292
|
+
* waits for the write, which is what an app wants before it reads the file,
|
|
293
|
+
* uploads it, or lets the process go. */
|
|
294
|
+
flush(sync = false): void {
|
|
295
|
+
if (!this.open) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
NativeXlog.flush(this.namePrefix, sync);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/** `mars_xlog_release_instance`: closes the appender `Xlog.open` made.
|
|
302
|
+
* Nothing is closed twice: an `Xlog` that is already closed answers `false`
|
|
303
|
+
* from `isOpen` and drops what it is asked to write — and an appender two
|
|
304
|
+
* names hold is closed for both, because `Xlog.open` gave them one `Xlog`. */
|
|
305
|
+
close(): void {
|
|
306
|
+
if (!this.open) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
this.open = false;
|
|
310
|
+
openAppenders.delete(this.namePrefix);
|
|
311
|
+
NativeXlog.close(this.namePrefix);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export default Xlog;
|