Rust implementation of Tencent/mars, the mobile library WeChat runs on. It is the same three pieces the C++ project is:
| piece | what it is |
|---|---|
| xlog | the logging pipeline — a logger that writes off the thread that called it |
| STN | the task model: a request with a channel, a timeout and a retry behind it |
| SDT | the network diagnosis: the probes that say which end a connection broke at |
One Rust core writes all three, and a package per platform hands that core to an
app that is not Rust. What the logger writes is the .xlog the C++
implementation writes — same framing, same compression, same encryption — so the
decoders that came with upstream, and any tooling built on them, read these files
without a conversion step.
Documentation — English · 简体中文 — install, configure, write, and read the files back. The site is one tab per piece: the logger, the task pipeline and the network diagnosis. Not every package carries the last two — use it says which do.
| your app is | what you take | where it comes from |
|---|---|---|
| Rust | marsrs / marsrs-xlog |
the tag of this repository — crates.io publication is pending |
| iOS 12+, watchOS 10+, Swift or Objective-C | the MarsRSXlog product or pod |
SwiftPM or CocoaPods, from this repository |
| Android, Kotlin or Java | xlog / marsrs |
JitPack |
| Kotlin Multiplatform | xlog-kmp / marsrs-kmp |
GitHub Packages, or the release's marsrs-kmp-maven.zip |
| Flutter | marsrs_xlog / marsrs |
pub.dev, or the release's marsrs-flutter-xlog-<version>.tar.gz |
| React Native 0.74+ | marsrs-react-native-xlog / marsrs-react-native |
npm, or the release's marsrs-react-native-xlog-<version>.tgz |
| anything with a C or C++ FFI | the marsrs-<version>-<host> archive |
the release: Linux, macOS and Windows hosts |
| HarmonyOS | marsrs-harmonyos-xlog, or the three .so of marsrs-harmony-<version>.tar.gz |
the release — ohpm is not switched on yet |
The Kotlin Multiplatform package is the widest of them: the same calls in shared
code write the same file on Android, iOS, watchOS, tvOS, macOS, Linux and
Windows, each platform compiling the half that reaches this core — JNI on
Android, cinterop over the C ABI everywhere else. HarmonyOS has both ways in:
marsrs-harmonyos-xlog is the package, a HAR an app installs and writes ArkTS
through, and marsrs-harmony-<version>.tar.gz is the three .so and the header
for an app that would rather write NAPI of its own.
Every release also ships the xlog CLI, which writes and reads those files from
a shell and makes the key pair that decides who can — as
marsrs-xlog-cli-<version>-<host> on the release, or off the tag with
cargo install --git https://github.com/orangeboyChen/mars-rs --tag v0.1.0-alpha.3 marsrs-xlog
— and then xlog keygen.
Every one of those platforms has a demo in demo/ — one
directory each, and the same seven records written in all eight — one at each
level, and one behind a level check — so that what
differs between them is what the platform makes of it and not what the app
does.
Two ways in, and the difference is how much of the port you take: xlog alone,
or the whole port — xlog, STN and SDT. An app that only logs takes the first;
the pair is the same one the crates are. Two of the packages carry
the logger under both names without the other two pieces — Flutter's marsrs and
marsrs-react-native have no STN or SDT surface yet, so on those two the choice
is one of name and not of contents. Everywhere else the wider name is the wider
port: marsrs on Android, marsrs-kmp in shared Kotlin and the MarsRS product
on Apple are where an app reaches the task pipeline and the diagnosis, and the C
archive ships every header beside the libraries: mars_xlog.h and the
mars_xlog.hpp beside it, and mars_sdt.h and mars_stn.h.
# The crates are not on crates.io yet — publication is pending — so a Rust app
# takes them off the tag: `cargo add marsrs-xlog` on its own resolves nothing.
# xlog alone — the logger and nothing else:
cargo add marsrs-xlog --git https://github.com/orangeboyChen/mars-rs --tag v0.1.0-alpha.3
# the whole port: + STN, SDT
cargo add marsrs --git https://github.com/orangeboyChen/mars-rs --tag v0.1.0-alpha.3// Package.swift
.package(url: "https://github.com/orangeboyChen/mars-rs", from: "0.1.0-alpha.3")
.product(name: "MarsRSXlog", package: "mars-rs") // or "MarsRS", for both halves# Podfile — Swift or Objective-C
pod 'MarsRSXlog', :podspec => 'https://raw.githubusercontent.com/orangeboyChen/mars-rs/v0.1.0-alpha.3/MarsRSXlog.podspec'The pods are not on a spec repo, so the podspec is fetched out of the tag it names — the releases page is where the newest one is.
// settings.gradle.kts: maven { url = uri("https://jitpack.io") }
implementation("io.github.orangeboychen.marsrs:xlog:0.1.0-alpha.3") // xlog alone
implementation("io.github.orangeboychen.marsrs:marsrs:0.1.0-alpha.3") // + STN, SDT
// build.gradle.kts of a Kotlin Multiplatform shared module
implementation("io.github.orangeboychen.marsrs:xlog-kmp:0.1.0-alpha.3")
implementation("io.github.orangeboychen.marsrs:marsrs-kmp:0.1.0-alpha.3")# HarmonyOS — ArkTS; xlog only, for now. ohpm is not switched on yet, so a
# release is where the package comes from:
ohpm install marsrs-harmonyos-xlog-<version>.harThen the same three steps on every platform: open an appender once when the app starts, write through it, and flush before you read or upload its files.
use marsrs::xlog::{LogLevel, XLogConfig, Xlog};
let mut config = XLogConfig::default();
config.logdir = std::path::PathBuf::from("/tmp/mars-log");
config.nameprefix = "marsrs".to_owned();
let xlog = Xlog::open(config, LogLevel::Info)?;
xlog.i("startup", "hello from mars");
xlog.flush_now(); // the records are on disk when this returnsval xlog = Xlog.open(
XlogConfig(
logDir = File(context.filesDir, "xlog/log").path,
namePrefix = "marsrs",
level = LogLevel.INFO,
)
)
xlog.i("startup", "cold start in $elapsedMillis ms")
xlog.flushNow() // before the app reads or uploads the filesThe file is <logDir>/<namePrefix>_YYYYMMDD.xlog — marsrs_20260927.xlog above.
The default mode is async, so a record can sit in the cache for a moment: flush
before the file is read or uploaded.
There are three drains, and two of them on every platform: what Dart has no face for is the blocking one, and what HarmonyOS has no face for is the awaited one.
| Rust | Kotlin, Swift, TypeScript | what the caller gets back |
|---|---|---|
xlog.flush_now() |
xlog.flushNow() |
the records are on disk when it returns |
xlog.flush().await |
await xlog.flush() |
the same drain, off the calling thread |
xlog.request_flush() |
xlog.requestFlush() |
nothing: it asks and returns at once |
request_flush is what a timer calls — what is still in the cache sits in a
file the kernel holds, so a drain that has not happened yet has lost nothing.
The two that answer are the ones an app wants before it reads or uploads.
Every option, and its name on each platform, is on the configuration page; what has to happen when the app goes away is on log files.
MIT, like the upstream project — see LICENSE.