Parsing Solana logs? It's not some magic black box. You just grab transaction signatures, pull the details, and dig into the logs like you're reading a messy chat history. Why bother? Because that's where the real action hides-your program's outputs, errors, events, all that good stuff. I do this daily to track my bots or debug deploys. Sound familiar? Let's jump in.
We'll build a simple Node script first. No fluff. By the end, you'll have a tool that spits out transaction history with logs for any address. And yeah, we'll hit events too, 'cause raw logs are half the story.
Okay, fire up your terminal. Make a folder:
mkdir solana logs parsercd solana logs parsernpm init -ynpm install @solana/web3.js@1.87.6 (that's the stable one I use)touch log parser.jsNow open log parser.js. Paste this starter:
javascript const solanaWeb3 = require('@solana/web3.js'); const endpoint = 'https://api.devnet.solana.com'; // Swap for mainnet or your QuickNode URL const solanaConnection = new solanaWeb3.Connection(endpoint, 'confirmed'); const searchAddress = 'vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg'; // Example addr-change yoursWhy devnet first? Fees are free, ~0 SOL per tx. Mainnet? ~0.000005 SOL per read call, but scales fast if you're hammering it.
First job: find transactions for an address. Wallets, programs, mints-doesn't matter. Use getSignaturesForAddress. It's async, returns up to 1000 sigs by default.
In your file, add this function:
javascript async function getTxSigs(address, limit = 10) { const pubkey = new solanaWeb3.PublicKey(address); const sigs = await solanaConnection.getSignaturesForAddress(pubkey, { limit }); return sigs; }Test it quick. At the bottom:
javascript (async () => { const sigs = await getTxSigs(searchAddress, 3); console.log('Sigs:', sigs.map(s => s.signature)); })();Run node log parser.js. Boom-signatures. Each has blockTime (unix timestamp), confirmationStatus ('confirmed' or 'finalized'), and err if it failed.
Pro tip: before and after params let you paginate. Like { before: 'lastSigHere', limit: 1000 }. I chain these for wallet history dumps.
Signatures alone? Useless. Pull the meat with getParsedTransaction or getParsedTransactions for batches. Gives you ParsedTransactionWithMeta-slot, tx details, logs, balances, inner instructions.
Extend your function:
javascript async function getTxDetails(sigs) { const sigList = sigs.map(s => s.signature); return await solanaConnection.getParsedTransactions(sigList, { maxSupportedTransactionVersion: 0 // Handles legacy + versioned txs }); }Update the test:
javascript (async () => { const sigs = await getTxSigs(searchAddress, 3); const details = await getTxDetails(sigs); console.log(details?.meta?.logMessages || 'No logs'); })();Run it. You'll see an array of strings-those are your logs. Stuff like "Program 11111111111111111111111111111111 invoke ", fee mentions, or your program's custom sol_log outputs.
Logs cap at ~10KB per tx. Misses stuff if your program's busy. Fix? Emit events early or use self CPI: call your own program again with data as instruction payload. Logs the bytes without truncation risk. In my experience, that's cleaner than fighting the limit.
Raw logs are noisy. Filter 'em. Common patterns:
Program <PUBKEY> invoke - Instruction callProgram log: <your message> - Custom logs via msg!("hello") in RustProgram data: <base64> - sollogdata for events (binary, needs decode)Let's code a parser. Add this:
javascript function parseLogs(logs, programId) { if (!logs) return []; return logs .filter(line => line.includes(programId.toString()) || line.startsWith('Program log:')) .map(line => { if (line.startsWith('Program data:')) { // Base64 decode events (Anchor style) const base64Data = line.split(': '); return { type: 'event', data: Buffer.from(base64Data, 'base64').toString() }; } return { type: 'log', message: line }; }); }Hook it in:
javascript (async () => { const sigs = await getTxSigs(searchAddress, 3); const details = await getTxDetails(sigs); sigs.forEach((sig, i) => { const meta = details[i]?.meta; if (meta) { console.log(Sig: ${sig.signature}); console.log('Parsed:', parseLogs(meta.logMessages, new solanaWeb3.PublicKey('YourProgramIdHere'))); console.log(''); } }); })();Now you're filtering by program. Pass your program's pubkey. Matches? Prints clean events/logs.
History's cool, but real time? Use WebSockets. connection.onLogs watches all logs or filters by address/program.
Add:
javascript solanaConnection.onLogs( new solanaWeb3.PublicKey(searchAddress), (logs, context) => { console.log('Live log:', logs.signature, logs.logs); // Filter here like before }, 'confirmed' );Run it. Trigger a tx on that address (via phantom or whatever). Watch logs stream. Why does this matter? Bots, alerts, MEV spotting. I usually pipe this to a DB.
Gotchas: RPCs drop logs sometimes-high volume chains. Paid nodes like QuickNode (~$9/mo) are more reliable. Free tier? Fine for dev.
Anchor makes events easy. In Rust:
rust #[event] pub struct MyEvent { pub value: u64, pub message: String, } emit!(MyEvent { value: 42, message: "yo".to_string() });Client side (add @coral xyz/anchor):
Attach to rpc() calls. Cleans up with removeEventListener. Way better than manual base64 parsing. In my experience, saves hours.
| Problem | Why? | Fix |
|---|---|---|
| No logs showing | Tx not involving your addr/program | Check err in sigs, use broader search |
| Truncated logs | 10KB limit hit | Self CPI or events only |
| Rate limited | Free RPC spam | QuickNode/Helius, add delays: await new Promise(r => setTimeout(r, 200)) |
| Versioned tx fails | Missing maxSupportedTransactionVersion: 0 | Add it always |
| Live listener silent | Commitment mismatch | Use 'confirmed' or match your tx commitment |
That table? Saved my ass debugging a frontrunning script last week.
Logs are gold, but pair with instructions. From earlier details:
javascript const instructions = details[i].transaction.message.instructions; instructions.forEach((instr, n) => { console.log(Instr ${n+1}: Program ${instr.programId?.toString() || 'System'}); });Pre/post balances in meta.preBalances/postBalances. Calc fees: (pre - post) / LAMPORTSPERSOL (~1e9 lamports per SOL).
Want token transfers? Check meta.preTokenBalances. Parse mints, amounts. That's for another script, but start with parsed infos.
Full history? Loop with before:
Calls ~10 reqs/sec safe. For 10k txs? Minutes. Dump to JSON: fs.writeFileSync('history.json', JSON.stringify(allSigs)).
Hunting a program's txs? Swap searchAddress to program ID. Logs will scream its invokes. Cross with account changes in meta.postTokenBalances for mint/burns.
Now tweak it. Track a wallet's swaps? Filter logs for "Jupiter" or Raydium program IDs. Alert on big transfers? Parse balances.
Honestly, once you run this on your own txs, it clicks. Hit snags? Console the full meta-it's a treasure map. What's next for you?
Full script so far? lines. Handles 90% cases. Expand from here.
(