Parsing a game's log file: a Warframe tracker in Electron
How I built WFRIM, an Electron app that reads Warframe's log file to track relics and trades: parsing, timestamps, deduplication, languages and auto-updates.
By Shaheer Q. · · 5 min read
Warframe has no public API for your own trades or relic runs, but the game writes a detailed log file, EE.log, while you play. WFRIM (Warframe Relic Inventory Manager) is a desktop app I built in 2022 that watches that file, reconstructs your trades and relic runs from it, and keeps your relic inventory and daily and monthly statistics up to date without you typing anything in. It is Electron, React and TypeScript, and it is open source on GitHub.
The interesting part is not the UI. It is turning an undocumented, multilingual, append-only debug log into reliable history.
Why the log file
There were three ways to get the data:
- Manual entry. Accurate only for as long as you remember to do it after every run and every trade.
- Reading the screen with OCR. Fragile across resolutions, languages and UI scales.
- The log file. The game writes UI dialogs, trade results and mission rewards to
%LOCALAPPDATA%/Warframe/EE.logas they happen.
The log is passive: WFRIM only reads a file the game already writes. No memory reading, no injected code. The downside is that nothing about its format is promised, and any patch can change a line.
Watching the file
Electron's main process polls the file and debounces reads:
// simplified
fs.watchFile(EE_LOG_PATH, { interval: 3000 }, () => {
clearTimeout(readTimer);
readTimer = setTimeout(readLog, 30_000);
});
fs.watchFile polls the file's size and modified time every three seconds rather than relying on OS file events. The 30-second debounce matters because the game writes constantly while you play, and re-parsing the whole file on every change would compete with the game for CPU. Thirty seconds late is still instant for a statistics screen.
Finding a trade in the noise
A trade in the log is not one line. It is a sequence: a confirmation dialog with the items on both sides, then, some lines later, a result. WFRIM finds the dialog, then scans forward for the outcome:
// simplified
if (line.includes('Dialog::CreateOkCancel(description=Are you sure you want to accept this trade')) {
const seq = 's_' + line.split(' ')[0]; // timestamp offset: a stable id for this event
if (alreadyRecorded(seq)) continue;
let success: boolean | undefined;
for (let i = index + 1; i <= index + 200; i++) {
const next = lines[i];
if (next === undefined) break; // result not written yet; try on the next read
if (next.includes('The trade failed')) { success = false; break; }
if (next.includes('The trade was successful!')) { success = true; break; }
}
if (success) recordTrade(seq, parseItems(line));
}
Three details make this work:
- A bounded window. The result follows the dialog closely, so the scan stops after 200 lines instead of reading to the end of the file for every trade.
- "Not written yet" is not "failed". If the file ends before a result appears, the trade is still in progress. WFRIM leaves it for the next read instead of recording a failure.
- The offered and received items are parsed from the dialog text on either side of "and will receive from".
Relic runs follow the same pattern: an "Are you sure you want to equip" dialog confirmed within 20 lines records which relic you used, and the reward-choice line at the end of the mission marks the run complete and takes one relic off your inventory.
Timestamps from a relative log
Each log line starts with seconds since the game launched, not a clock time. Early in the file, a Current time: line gives the session's real start. WFRIM reads that once, then adds each line's offset to it to get a real timestamp for every event. The session's start time also names the JSON file that session's events are saved to.
Re-reading safely
The file is re-read in full on every change, so every event would be seen many times. The fix is a stable key per event: the line's own time offset, prefixed by event type (s_ for a trade). Before recording anything, WFRIM checks whether that key is already in the session's file. Re-reading the log is then idempotent: run it once or a hundred times and the history is the same.
It is the same idea as idempotent writes in DynamoDB, applied to a text file: derive the key from the event, not from when you happened to see it.
Other languages
The log is written in the game's language. A French or Chinese client writes different dialog text, so string matching breaks. WFRIM keeps translation tables for Chinese, Traditional Chinese and French that rewrite the known phrases into English before parsing. Any other language shows a message asking the player to get in touch, rather than silently recording nothing.
Backfilling history from a data export
The log only covers sessions while WFRIM was installed. Warframe lets players request a data export of their account, which includes a full trade history. WFRIM can import it, and then marks any log-derived trade older than the export's last trade as superseded, so the same trade is never counted twice from two sources.
The Electron side
- Main process does everything that touches the disk or the network: the log reader, the local JSON store in
%LOCALAPPDATA%/WFRIM/, and the API calls. - Renderer is a React and MUI app with Chart.js for the statistics.
- A preload script exposes a narrow API through
contextBridge, so the renderer never gets Node.js access. - A ready handshake. The main process can finish parsing before the window has loaded. It re-sends its data every second until the renderer signals it is ready, so the first screen is never empty.
- Auto-updates use
electron-updateragainst GitHub Releases. Publishing a release with the Windows installer is all it takes for existing installs to update.
Connecting it to the Discord community
WFRIM can sync your relic inventory to the API behind allsquads.com, my Warframe matchmaking site. When you join a relic squad on Discord, the bot shows how many of that relic each member owns, so a squad can see at a glance who can run it how many times. It also pulls current prices and which relics are vaulted from the same API. And a hotkey pastes your saved trade message into chat, only when the Warframe window is focused.
What I learned
- Parse defensively and fail loudly. An undocumented log changes without notice. Every pattern WFRIM depends on is a single string match, and when one stops matching, the app records nothing. A health check that warns "no trades seen in a session that had a trade dialog" would surface a broken pattern the day a patch changes it.
- Test the statistics, not just the parser. Re-reading the code for this post turned up two daily averages swapped in the statistics function: the tracked-relic figure was showing the vaulted-relic data and the other way round. The parser was right; the number on screen was not. A unit test on that function would have caught it in a minute.
- Ship the updater first. WFRIM's main development was in 2022, and a desktop install can keep running long after you stop thinking about it. The auto-updater is the only way a fix reaches people who installed once and forgot about it, so it was worth having from the first release.
Frequently asked questions
Can you read game data without an official API?
Often, from files the game already writes. Many games keep a plain-text log for debugging. Reading it is passive: no memory reading, no injected code, no screen scraping. The catch is that the format is undocumented and can change with any patch.
How do you avoid counting the same log line twice?
Give every event a stable key derived from the log itself, such as its timestamp or line offset in that session, and skip any event whose key you have already stored. Re-reading the whole file then becomes safe.
How do Electron apps update themselves?
electron-updater checks a release feed, such as GitHub Releases, downloads the new installer in the background and installs it on restart. You publish a release with the built installer and update metadata, and existing installs pick it up.