How to Parse Solana Logs: Complete Guide

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.

Quick Setup-Don't Skip This

Okay, fire up your terminal. Make a folder:

  1. mkdir solana logs parser
  2. cd solana logs parser
  3. npm init -y
  4. npm install @solana/web3.js@1.87.6 (that's the stable one I use)
  5. touch log parser.js

Now 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 yours

Why devnet first? Fees are free, ~0 SOL per tx. Mainnet? ~0.000005 SOL per read call, but scales fast if you're hammering it.

Grab Signatures-Your Entry Point

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.

Now the Fun Part: Fetch Full Transactions

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.

Log Truncation Sucks-Here's Why

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.

Parse Those Logs Like a Pro

Raw logs are noisy. Filter 'em. Common patterns:

  • Program <PUBKEY> invoke - Instruction call
  • Program log: <your message> - Custom logs via msg!("hello") in Rust
  • Program data: <base64> - sollogdata for events (binary, needs decode)
  • Error lines if tx failed

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.

Live Listening-Don't Miss a Beat

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 Events-If You're Using It

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

javascript const program = anchor.workspace.YourProgram; const listener = program.addEventListener('MyEvent', (event, slot) => { console.log(Event! Value: ${event.value}, Msg: ${event.message}, Slot: ${slot}); });

Attach to rpc() calls. Cleans up with removeEventListener. Way better than manual base64 parsing. In my experience, saves hours.

Common Screw Ups and Fixes

ProblemWhy?Fix
No logs showingTx not involving your addr/programCheck err in sigs, use broader search
Truncated logs10KB limit hitSelf CPI or events only
Rate limitedFree RPC spamQuickNode/Helius, add delays: await new Promise(r => setTimeout(r, 200))
Versioned tx failsMissing maxSupportedTransactionVersion: 0Add it always
Live listener silentCommitment mismatchUse 'confirmed' or match your tx commitment

That table? Saved my ass debugging a frontrunning script last week.

Go Deeper: Instructions and Balances

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.

Scale It: Pagination Beast Mode

Full history? Loop with before:

javascript async function getAllTxs(address, max = 10000) { let allSigs = []; let lastSig = null; while (allSigs.length < max) { const opts = { limit: 1000 }; if (lastSig) opts.before = lastSig; const batch = await solanaConnection.getSignaturesForAddress( new solanaWeb3.PublicKey(address), opts ); if (batch.length === 0) break; allSigs.push(..batch); lastSig = batch[batch.length - 1].signature; await new Promise(r => setTimeout(r, 100)); // Chill } return allSigs.slice(0, max); }

Calls ~10 reqs/sec safe. For 10k txs? Minutes. Dump to JSON: fs.writeFileSync('history.json', JSON.stringify(allSigs)).

Program Specific Filtering

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.

Your Turn-Build Something

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.

(