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.
Files changed (31) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +12 -8
  3. package/android/build.gradle.kts +1 -1
  4. package/android/src/main/kotlin/io/github/orangeboychen/marsrs/xlog/reactnative/XlogModule.kt +248 -43
  5. package/ios/Frameworks/marsrs-xlog.xcframework/Info.plist +83 -0
  6. package/ios/Frameworks/marsrs-xlog.xcframework/ios-arm64/Headers/mars_xlog.h +242 -0
  7. package/ios/Frameworks/marsrs-xlog.xcframework/ios-arm64/Headers/module.modulemap +4 -0
  8. package/ios/Frameworks/marsrs-xlog.xcframework/ios-arm64/libmars_ffi.a +0 -0
  9. package/ios/Frameworks/marsrs-xlog.xcframework/ios-arm64_x86_64-simulator/Headers/mars_xlog.h +242 -0
  10. package/ios/Frameworks/marsrs-xlog.xcframework/ios-arm64_x86_64-simulator/Headers/module.modulemap +4 -0
  11. package/ios/Frameworks/marsrs-xlog.xcframework/ios-arm64_x86_64-simulator/libmars_ffi.a +0 -0
  12. package/ios/Frameworks/marsrs-xlog.xcframework/watchos-arm64-simulator/Headers/mars_xlog.h +242 -0
  13. package/ios/Frameworks/marsrs-xlog.xcframework/watchos-arm64-simulator/Headers/module.modulemap +4 -0
  14. package/ios/Frameworks/marsrs-xlog.xcframework/watchos-arm64-simulator/libmars_ffi.a +0 -0
  15. package/ios/Frameworks/marsrs-xlog.xcframework/watchos-arm64_arm64_32/Headers/mars_xlog.h +242 -0
  16. package/ios/Frameworks/marsrs-xlog.xcframework/watchos-arm64_arm64_32/Headers/module.modulemap +4 -0
  17. package/ios/Frameworks/marsrs-xlog.xcframework/watchos-arm64_arm64_32/libmars_ffi.a +0 -0
  18. package/ios/Xlog.mm +2 -2
  19. package/ios/Xlog.swift +263 -37
  20. package/ios/include/mars_xlog.h +242 -0
  21. package/package.json +1 -1
  22. package/src/NativeXlog.ts +28 -3
  23. package/src/index.ts +143 -20
  24. package/android/.gradle/9.2.0/checksums/checksums.lock +0 -0
  25. package/android/.gradle/9.2.0/fileChanges/last-build.bin +0 -0
  26. package/android/.gradle/9.2.0/fileHashes/fileHashes.bin +0 -0
  27. package/android/.gradle/9.2.0/fileHashes/fileHashes.lock +0 -0
  28. package/android/.gradle/9.2.0/gc.properties +0 -0
  29. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  30. package/android/.gradle/buildOutputCleanup/cache.properties +0 -2
  31. package/android/.gradle/vcs-1/gc.properties +0 -0
package/src/NativeXlog.ts CHANGED
@@ -37,8 +37,32 @@ export interface Spec extends TurboModule {
37
37
  * holds. */
38
38
  getLevel(namePrefix: string): number;
39
39
 
40
- /** `mars_xlog_flush_instance`. */
41
- flush(namePrefix: string, sync: boolean): void;
40
+ /** `mars_xlog_request_flush_instance`: tells the writer thread it may take what is in
41
+ * the cache to the file, and returns at once — nothing waits, and nothing is
42
+ * in the file because this returned. */
43
+ requestFlush(namePrefix: string): void;
44
+
45
+ /** `mars_xlog_flush_now_instance`: the drain is the calling thread's, so the cache
46
+ * is in the log file, and the file's buffer is the OS's, when this returns. */
47
+ flushNow(namePrefix: string): void;
48
+
49
+ /** `flushNow` on a thread of the module's own, and a `Promise` settled when
50
+ * the drain is over: the one of the three that leaves the JS thread alone.
51
+ * A `Promise<void>` and not a `Promise` of a value — there is nothing to
52
+ * answer, only a drain to be over. */
53
+ flush(namePrefix: string): Promise<void>;
54
+
55
+ /** `mars_xlog_current_log_path_instance`: the directory this appender writes
56
+ * its files into, or `undefined` once it is closed. */
57
+ currentLogPath(namePrefix: string): string | undefined;
58
+
59
+ /** `mars_xlog_getfilepath_from_timespan_instance`: the day's files that are
60
+ * there — `0` is today, `1` is yesterday. */
61
+ logFiles(namePrefix: string, daysAgo: number): string[];
62
+
63
+ /** `mars_xlog_make_logfile_name_instance`: the day's names, whether or not the
64
+ * files are there yet. */
65
+ logFileNames(namePrefix: string, daysAgo: number): string[];
42
66
 
43
67
  /** `mars_xlog_set_level_instance`. */
44
68
  setLevel(namePrefix: string, level: number): void;
@@ -56,7 +80,8 @@ export interface Spec extends TurboModule {
56
80
  /** `mars_xlog_set_max_alive_duration_instance`. */
57
81
  setMaxAliveTime(namePrefix: string, seconds: number): void;
58
82
 
59
- /** `mars_xlog_release_instance`: releases the appender `open` made. */
83
+ /** Releases the appender `open` made: by handle on iOS, by prefix on
84
+ * Android. */
60
85
  close(namePrefix: string): void;
61
86
  }
62
87
 
package/src/index.ts CHANGED
@@ -37,7 +37,8 @@ export type LogLevel = (typeof LogLevel)[keyof typeof LogLevel];
37
37
  /** `TAppenderMode`: whether a record reaches the file before the call returns. */
38
38
  export const AppenderMode = {
39
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. */
40
+ * to the file — what the C++ opens with, and what `flushNow()` and
41
+ * `await flush()` drain. */
41
42
  async: 0,
42
43
  /** The record is in the file before the call returns, at a write and a lock
43
44
  * per line. */
@@ -62,15 +63,16 @@ export type CompressMode = (typeof CompressMode)[keyof typeof CompressMode];
62
63
  * gives it: what an `Xlog` is opened with.
63
64
  *
64
65
  * 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. */
66
+ * app has to give is `logDir` — the C ABI opens no appender
67
+ * without it. */
67
68
  export interface XlogConfig {
68
69
  /** Where the log files are written; created if it is missing, and the one
69
70
  * field with no default. */
70
71
  logDir: string;
71
72
  /** What every log file starts with (`marsrs_20260927.xlog`), and the name the
72
73
  * appender is known by — an app that writes through two of them gives them
73
- * two. `xlog` when left out. */
74
+ * two. `xlog` when left out, when handed over empty, and when what was
75
+ * handed over is nothing but whitespace. */
74
76
  namePrefix?: string;
75
77
  /** The level the appender is opened at; `info` when left out. */
76
78
  level?: LogLevel;
@@ -89,9 +91,29 @@ export interface XlogConfig {
89
91
  }
90
92
 
91
93
  /** `XlogConfig.namePrefix` of the Kotlin and of the Swift: what an appender is
92
- * opened with when a caller gives none. */
94
+ * opened with when a caller gives none — and when it gives a blank one, which
95
+ * the two halves do not agree about: the Kotlin answers its own default for a
96
+ * prefix of nothing but whitespace, and the Swift opens that prefix as it is.
97
+ * Naming the default here is what keeps the `Xlog` an app holds and the
98
+ * appender the halves hold the same one, whichever half it is — a prefix of
99
+ * spaces that one half substituted and the other did not is an appender this
100
+ * `Xlog` cannot ask about afterwards. */
93
101
  const DEFAULT_NAME_PREFIX = 'xlog';
94
102
 
103
+ /** The shortest lifetime of a file the appender takes, in seconds: one below it
104
+ * — `0` among them — is refused, and the appender keeps the lifetime it had.
105
+ * `MIN_LOG_ALIVE_TIME` of the Rust, which is the one place the number is
106
+ * written down. */
107
+ const MIN_ALIVE_TIME_SECONDS = 86400;
108
+
109
+ /** The prefix an appender opened with `config` is known by: `namePrefix`, or
110
+ * [DEFAULT_NAME_PREFIX] when it is missing or blank. What is handed to the two
111
+ * halves is this and not the string the caller gave, so the two of them agree
112
+ * on one name for one appender. */
113
+ function namePrefixOf(config: XlogConfig): string {
114
+ return config.namePrefix?.trim() || DEFAULT_NAME_PREFIX;
115
+ }
116
+
95
117
  /** The appender of every `namePrefix` `Xlog.open` has opened and `close` has
96
118
  * not closed, by the prefix: what makes two `Xlog.open`s of one prefix one
97
119
  * appender, which the native side already is — one module, one appender per
@@ -125,7 +147,7 @@ export class Xlog {
125
147
  private open = true;
126
148
 
127
149
  private constructor(config: XlogConfig) {
128
- this.namePrefix = config.namePrefix ?? DEFAULT_NAME_PREFIX;
150
+ this.namePrefix = namePrefixOf(config);
129
151
  this.currentLevel = config.level ?? LogLevel.info;
130
152
  this.currentMode = config.mode ?? AppenderMode.async;
131
153
  }
@@ -138,16 +160,25 @@ export class Xlog {
138
160
  * second one over the first would be a handle nothing releases. So two calls
139
161
  * of one prefix are one `Xlog`, and `close` on it is `close` on both.
140
162
  *
163
+ * The native side is asked either way, and not only the first time: one
164
+ * appender per prefix is one for the whole app, so one the app closed
165
+ * through its own Kotlin or Swift is gone from under this map, and asking
166
+ * again is what opens it again.
167
+ *
141
168
  * Throws when the appender would not take the configuration — an empty
142
- * `logDir` or `namePrefix`, or a directory it cannot write to. */
169
+ * `logDir`, or a directory it cannot write to. */
143
170
  static open(config: XlogConfig): Xlog {
144
- const namePrefix = config.namePrefix ?? DEFAULT_NAME_PREFIX;
171
+ const namePrefix = namePrefixOf(config);
145
172
  const alreadyOpen = openAppenders.get(namePrefix);
146
- if (alreadyOpen) {
173
+ // The appender of a prefix is one for the whole process, so one another
174
+ // part of the app closed is gone from under this map: the open is asked
175
+ // again instead of trusted, and what it answers is the appender that is
176
+ // there now — the same one when nothing closed it.
177
+ if (alreadyOpen && NativeXlog.open({ ...config, namePrefix })) {
147
178
  return alreadyOpen;
148
179
  }
149
180
  const xlog = new Xlog(config);
150
- if (!NativeXlog.open(config)) {
181
+ if (!NativeXlog.open({ ...config, namePrefix })) {
151
182
  throw new Error(
152
183
  `marsrs-react-native-xlog: the appender of '${xlog.namePrefix}' refused ${config.logDir}`
153
184
  );
@@ -219,11 +250,21 @@ export class Xlog {
219
250
  return;
220
251
  }
221
252
  NativeXlog.setMaxFileSize(this.namePrefix, bytes);
222
- this.currentMaxFileSize = bytes;
253
+ // Mirrored once it is a size the native side took, and not before: that
254
+ // side reads a `number` as an `unsigned long long`, and a negative one or
255
+ // a `NaN` is not one it hands over. `0` is a size and not an absence —
256
+ // "never split" — so it is mirrored like any other.
257
+ if (Number.isFinite(bytes) && bytes >= 0) {
258
+ this.currentMaxFileSize = bytes;
259
+ }
223
260
  }
224
261
 
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. */
262
+ /** How many seconds a log file of this appender is kept before the sweep
263
+ * deletes it — and not how long one is written to, which is what the day's
264
+ * turn and [maxFileSizeBytes] decide: `0` is the lifetime an appender
265
+ * opened with none keeps, the C++'s own ten days, and one below a day is a
266
+ * lifetime the appender refuses, so what this answers is the one it has and
267
+ * not the last one it was asked for. */
227
268
  get maxAliveTimeSeconds(): number {
228
269
  return this.currentMaxAliveTime;
229
270
  }
@@ -233,7 +274,12 @@ export class Xlog {
233
274
  return;
234
275
  }
235
276
  NativeXlog.setMaxAliveTime(this.namePrefix, seconds);
236
- this.currentMaxAliveTime = seconds;
277
+ // Mirrored once it is a lifetime the appender took, and not before: a day
278
+ // is the shortest one it takes, and `0` is below it, so a mirror that took
279
+ // the number would answer a lifetime no appender is writing under.
280
+ if (seconds >= MIN_ALIVE_TIME_SECONDS) {
281
+ this.currentMaxAliveTime = seconds;
282
+ }
237
283
  }
238
284
 
239
285
  /** Whether a record at `level` is written: the appender's own answer, and the
@@ -288,17 +334,94 @@ export class Xlog {
288
334
  this.log(LogLevel.fatal, tag, message);
289
335
  }
290
336
 
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 {
337
+ /** `mars_xlog_request_flush_instance`: tells the writer thread it may take what is in
338
+ * the cache to the file, and returns at once. Nothing waits, and nothing is
339
+ * in the file because this returned — a record still in the cache is in a file
340
+ * the kernel holds, so one whose process dies keeps it. What it is for is a
341
+ * drain an app wants soon and does not want to wait for; before the file is
342
+ * read, uploaded or left behind, it is `flushNow()` or `await flush()`
343
+ * that an app wants and not this. */
344
+ requestFlush(): void {
345
+ if (!this.open) {
346
+ return;
347
+ }
348
+ NativeXlog.requestFlush(this.namePrefix);
349
+ }
350
+
351
+ /** `mars_xlog_flush_now_instance`: the drain is the calling thread's, so the
352
+ * records are in the log file — handed to the OS, and not left in the file's
353
+ * own `FILE*` — when it returns, and what that costs is the time the drain
354
+ * takes, on the thread that asked for it. It answers no `Promise` and takes
355
+ * no `await`, which is the point of it: the line after the call is a line
356
+ * that runs after the drain, and there is nothing to get wrong in between. */
357
+ flushNow(): void {
358
+ if (!this.open) {
359
+ return;
360
+ }
361
+ NativeXlog.flushNow(this.namePrefix);
362
+ }
363
+
364
+ /** `flushNow()` off the JS thread: the same drain, on a thread of the
365
+ * module's own, and a `Promise` that is settled when it is over. The records
366
+ * are on disk when it resolves, and what waited for them is that thread and
367
+ * not the one the app runs JS on — the call to make before reading or
368
+ * uploading a log file whose drain is longer than the app can sit through. */
369
+ async flush(): Promise<void> {
295
370
  if (!this.open) {
296
371
  return;
297
372
  }
298
- NativeXlog.flush(this.namePrefix, sync);
373
+ await NativeXlog.flush(this.namePrefix);
299
374
  }
300
375
 
301
- /** `mars_xlog_release_instance`: closes the appender `Xlog.open` made.
376
+ /** `mars_xlog_current_log_path_instance`: the directory this appender writes
377
+ * its files into, or `undefined` once it is closed.
378
+ *
379
+ * A directory and not a file, because that is what the C++'s
380
+ * `GetCurrentLogPath` answers, and there is no "not yet" state: an open
381
+ * appender has a directory from the moment it is opened. What an app that
382
+ * uploads a whole day asks for is [`logFiles`]. */
383
+ get currentLogPath(): string | undefined {
384
+ if (!this.open) {
385
+ return undefined;
386
+ }
387
+ return NativeXlog.currentLogPath(this.namePrefix);
388
+ }
389
+
390
+ /** `mars_xlog_getfilepath_from_timespan_instance`: the log files of `daysAgo`
391
+ * days ago that are *there* — what an app that uploads yesterday's opens. `[]`
392
+ * when the directory holds none of that day's. `0` is today, `1` is yesterday,
393
+ * and so on.
394
+ *
395
+ * This is a day of files and not the directory they are in: what
396
+ * `currentLogPath` answers is that, and this is this appender's own prefix and
397
+ * directory. */
398
+ logFiles(daysAgo: number): string[] {
399
+ if (!this.open) {
400
+ return [];
401
+ }
402
+ return NativeXlog.logFiles(this.namePrefix, daysAgo);
403
+ }
404
+
405
+ /** `mars_xlog_make_logfile_name_instance`: the paths of the log files of
406
+ * `daysAgo` days ago whether or not they are *there yet* — the name an app
407
+ * that is about to write, or that is naming a file to someone else, asks for.
408
+ *
409
+ * A day's answer can be two where [`logFiles`] answers one, when a cache dir
410
+ * is given and the file is there. */
411
+ logFileNames(daysAgo: number): string[] {
412
+ if (!this.open) {
413
+ return [];
414
+ }
415
+ return NativeXlog.logFileNames(this.namePrefix, daysAgo);
416
+ }
417
+
418
+ /** Closes the appender `Xlog.open` made. `mars_xlog_release_instance_of` on
419
+ * iOS, which is given the handle beside the prefix — a release by prefix
420
+ * alone closes whichever appender the prefix answers at that moment — and
421
+ * `mars_xlog_release_instance` on Android, which releases the prefix and is
422
+ * asked once per `Xlog`, by whichever takes its handle out of the table
423
+ * first.
424
+ *
302
425
  * Nothing is closed twice: an `Xlog` that is already closed answers `false`
303
426
  * from `isOpen` and drops what it is asked to write — and an appender two
304
427
  * names hold is closed for both, because `Xlog.open` gave them one `Xlog`. */
File without changes
@@ -1,2 +0,0 @@
1
- #Mon Sep 28 14:47:25 CST 2026
2
- gradle.version=9.2.0
File without changes