This project is in active development. APIs may change without notice.
manasight-parser is the log parsing engine behind Manasight, an MTG Arena companion app.
MTG Arena log file parser — a Rust library crate that reads Arena's Player.log and emits typed game events. It runs natively — tailing a live log through an async event bus — or compiles to WebAssembly to parse a whole log in the browser or in Node.
cargo add manasight-parserOr in Cargo.toml:
[dependencies]
manasight-parser = "0.5"Requires Rust 1.93.0 or later.
Player.log → File Tailer → Entry Buffer → Router → Parsers → Event Bus
log— file discovery, polling tailer, entry accumulation, timestampsrouter— dispatches raw entries to the correct category parserparsers— one sub-module per event categoryevents— public event type enums/structs (the parser's output contract)event_bus—tokio::broadcastchannel for fan-out to subscribersstream— public entry point (MtgaEventStream)sanitize— privacy scrubber for redacting PII from raw log textutil— pipeline utilities (gzip compression, content hashing)wasm—wasm-bindgenexport of the synchronous parser (enabled by thewasmfeature)
The async pipeline above powers the live desktop overlay. The synchronous parse_whole_log entry point — and its wasm export — reuse the same Router → Parsers core directly, bypassing the tailer and event bus (no tokio, no filesystem).
manasight-parser has two entry points: a streaming tailer for live logs, and a synchronous whole-log parser (the basis for the WASM build).
MtgaEventStream (the default tailer feature) tails a live log and fans out events to subscribers as they arrive:
use std::path::Path;
use manasight_parser::MtgaEventStream;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let (stream, mut subscriber) = MtgaEventStream::start(Path::new("Player.log")).await?;
while let Some(event) = subscriber.recv().await {
println!("got event: {event:?}");
}
Ok(())
}parse_whole_log parses an entire Player.log string in one synchronous call — no async runtime, no filesystem. It is always available (independent of the tailer feature) and is the exact entry point the WASM build exposes to JavaScript:
use manasight_parser::parse_whole_log;
fn main() -> std::io::Result<()> {
let text = std::fs::read_to_string("Player.log")?;
let events = parse_whole_log(&text);
println!("parsed {} events", events.len());
Ok(())
}| Feature | Default | Description |
|---|---|---|
brace_depth_flush |
✓ | Flush multi-line entries on JSON brace-balance instead of waiting for the next header. Disable as a rollback mechanism if a regression is detected. |
tailer |
✓ | File tailer, log discovery, async event stream (MtgaEventStream), and event bus. Pulls in tokio and known-folders. Disable to build the pure-sync WASM-compatible subset. |
wasm |
wasm-bindgen export of parseWholeLog for use in a browser or Node Parse Worker. Implies brace_depth_flush; does not pull in tailer (no tokio/known-folders). |
The wasm feature wraps the synchronous parse_whole_log in a wasm-bindgen export, producing a loadable .wasm artifact plus JS/TypeScript bindings via wasm-pack. The same parser core then runs in a browser or in Node — no tokio, no filesystem, no async runtime.
# Install wasm-pack (once) — pin the version for reproducible builds
cargo install wasm-pack --locked --version 0.15.0
# Build — outputs pkg/ with .wasm + JS + .d.ts
wasm-pack build --target web --no-default-features --features wasm
# For a Node.js consumer:
wasm-pack build --target nodejs --no-default-features --features wasm
# For a bundler (webpack / Vite):
wasm-pack build --target bundler --no-default-features --features wasmConsume the generated bindings from JavaScript / TypeScript:
import init, { parseWholeLog } from "./pkg/manasight_parser.js";
await init(); // instantiate the .wasm module (web / bundler targets)
const text = await readPlayerLog(); // the full Player.log contents, as a string
let events;
try {
events = parseWholeLog(text); // GameEvent[] — see "Event Types" below
} catch (err) {
// parseWholeLog throws only if serialisation to JS fails (extremely unlikely)
console.error("parse failed:", err);
}Return type: at runtime parseWholeLog returns a GameEvent[] — a live JS object graph (via serde-wasm-bindgen), not a JSON string, so there's no second JSON.parse. Each element is an externally-tagged enum, e.g. { "GameState": { … } } (see Event Types). Because the wrapper hands back a JsValue, the generated .d.ts types the return as any and the call can throw — TypeScript consumers will likely want to layer their own types and a try/catch.
Distribution: npm publishing / packaging is intentionally out of scope for this library. The consuming application (e.g. a Parse Worker) vendors or packages the built pkg/ artifact.
The sanitize module strips PII and credentials from raw Player.log text before it leaves the user's machine. It redacts auth tokens, bearer tokens, account IDs, display names, session identifiers, OS user paths, and hardware fingerprints.
use manasight_parser::sanitize::scrub_raw_log;
let raw = std::fs::read_to_string("Player.log").unwrap();
let clean = scrub_raw_log(&raw);
// clean contains no tokens, account IDs, or user pathsPipeline utilities for compression and content-addressable storage:
use manasight_parser::util::{compress_log, content_hash};
let compressed = compress_log(&clean).unwrap();
let hash = content_hash(&compressed); // 64-char hex SHA-256The scrub binary reads stdin and writes sanitized output to stdout:
cargo run --bin scrub < Player.log > Player-sanitized.log| Event | Description | Class |
|---|---|---|
GameState |
GRE-to-client messages (zones, game objects, turns) | Interactive |
ClientAction |
Client-to-GRE messages (mulligan, select, deck submit) | Interactive |
MatchState |
Match room state changes (start, end, player seats) | Interactive |
DraftBot |
Bot draft picks (Quick Draft) | Durable |
DraftHuman |
Human draft picks (Premier/Traditional Draft) | Durable |
DraftComplete |
Draft completion signal | Durable |
EventLifecycle |
Event join, claim prize, enter pairing | Durable |
Session |
Login, account identity, logout | Durable |
Rank |
Constructed and limited rank snapshots | Durable |
DeckCollection |
Deck collection snapshots with correlated decklists | Durable |
Inventory |
Currency, wildcards, boosters, vault progress | Durable |
DeckSubmission |
Submitted deck Format + DeckId from EventSetDeck requests |
Durable |
GameResult |
Game result / batch trigger | Post-game |
- Interactive (Class 1): local-only processing, ≤100ms latency target
- Durable (Class 2): persisted to disk queue, ≤1s latency target
- Post-game (Class 3): triggers assembly and upload of game batch
MSRV is 1.93.0.
Contributions are welcome! See CONTRIBUTING.md for guidelines on reporting bugs, submitting pull requests, and setting up a development environment.
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
This project is not affiliated with, endorsed by, or associated with Wizards of the Coast, Hasbro, or Magic: The Gathering Arena. All trademarks are the property of their respective owners.