Skip to content

manasight/manasight-parser

This project is in active development. APIs may change without notice.

manasight-parser

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.

Installation

cargo add manasight-parser

Or in Cargo.toml:

[dependencies]
manasight-parser = "0.5"

Requires Rust 1.93.0 or later.

Architecture

Player.log → File Tailer → Entry Buffer → Router → Parsers → Event Bus
  • log — file discovery, polling tailer, entry accumulation, timestamps
  • router — dispatches raw entries to the correct category parser
  • parsers — one sub-module per event category
  • events — public event type enums/structs (the parser's output contract)
  • event_bustokio::broadcast channel for fan-out to subscribers
  • stream — public entry point (MtgaEventStream)
  • sanitize — privacy scrubber for redacting PII from raw log text
  • util — pipeline utilities (gzip compression, content hashing)
  • wasmwasm-bindgen export of the synchronous parser (enabled by the wasm feature)

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).

Usage

manasight-parser has two entry points: a streaming tailer for live logs, and a synchronous whole-log parser (the basis for the WASM build).

Streaming (live Player.log)

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(())
}

Whole-log parsing (synchronous)

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(())
}

Cargo Features

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).

WASM / wasm-bindgen build

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 wasm

Consume 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.

Log Sanitization

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 paths

Pipeline 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-256

CLI

The scrub binary reads stdin and writes sanitized output to stdout:

cargo run --bin scrub < Player.log > Player-sanitized.log

Event Types

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

Performance Classes

  • 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

Minimum Supported Rust Version

MSRV is 1.93.0.

Contributing

Contributions are welcome! See CONTRIBUTING.md for guidelines on reporting bugs, submitting pull requests, and setting up a development environment.

License

Licensed under either of

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.

About

MTG Arena log file parser — Rust library crate

Topics

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors