Логи транзакцій — це єдине джерело правди про те, що саме відбулося всередині виконання вашої програми на Solana. На відміну від EVM-мереж, де ви отримуєте структурований трейс, Solana видає текстовий потік рядків. Ця інструкція покаже, як цей потік читати системно, а не вгадувати помилки навмання.

Навіщо читати логи

Діагностика помилок виконання

Коли транзакція повертає статус failed, RPC-відповідь містить лише загальний код помилки (наприклад, InstructionError або CustomError) та індекс інструкції, що впала. Без логів ви не дізнаєтесь, яка саме перевірка всередині програми спрацювала. Логи дають точний рядок коду або умову, яка призвела до відхилення.

Розуміння шляху виконання програми

Навіть успішна транзакція може працювати неправильно — записати не ті дані, передати не той рахунок, викликати не ту гілку логіки. Логи показують послідовність інструкцій, CPI-викликів (Cross-Program Invocation — виклик однією програмою іншої) та значення, які програма обробляла. Це особливо важливо на Devnet, де ви тестуєте логіку до розгортання в mainnet.

Читання логів через CLI

solana logs команда та параметри

Для роботи з логами потрібен встановлений Solana CLI (перевірте версію командою solana --version). Усі приклади нижче використовують кластер Devnet.

Існує два основних способи отримати логи:

1. Логи конкретної транзакції — найчастіший варіант під час розробки. Використовуйте підпис транзакції (signature), який повертає RPC після відправки:

solana confirm -v <SIGNATURE> --url devnet

Параметр -v (verbose) вмикає виведення логів. Без нього ви побачите лише статус підтвердження.

2. Потокове читання логів кластера — корисно, коли ви хочете спостерігати за викликами своєї програми в реальному часі:

solana logs --url devnet

Увага: на Devnet ця команда видає величезний обсяг даних, оскільки показує логи всіх транзакцій кластера. Без фільтрів це практично непридатно для аналізу.

Фільтрування за програмою та instruction

Щоб потокові логи стали корисними, фільтруйте їх за ID вашої програми:

solana logs --url devnet --program-id <YOUR_PROGRAM_ID>

Це показуватиме лише ті транзакції, які прямо або опосередковано викликали вказану програму. Для ще точнішої діагностики комбінуйте з grep:

solana logs --url devnet --program-id <YOUR_PROGRAM_ID> | grep "Program log"

Так ви побачите лише користувацькі повідомлення з msg!, відкинувши системні рядки про invoke та compute units.

Розуміння формату логів

Program log: системні повідомлення

Префікс Program log: використовується для двох типів повідомлень: системних (від runtime) та користувацьких (від вашого коду). Розпізнати їх просто — системні повідомлення мають стандартизований формат, а користувацькі містять текст, який ви передали в макрос msg!.

Типові системні повідомлення:

  • Program <PUBKEY> invoke [N] — програма зазначеним публічним ключем почала виконання на глибині вкладеності N
  • Program <PUBKEY> consumed N of M compute units — скільки compute units витрачено з виділеного бюджету
  • Program <PUBKEY> success — програма завершилася успішно
  • Program <PUBKEY> failed: <ERROR> — програма завершилася з помилкою

Program data: користувацькі логи (msg!)

У сучасних версіях Solana runtime макрос msg! виводить текст безпосередньо через Program log:. Рядок виглядає так:

Program log: User balance: 1500

Префікс Program data: у логах транзакції з'являється в іншому контексті — він показує hex-представлення даних інструкції, а не вивід msg!. Не плутайте ці два формати. Якщо ви бачите Program data:, це сирові байти інструкції, які можна декодувати окремо за допомогою інструментів розбору транзакцій або вручну.

Практична порада: під час розробки завжди маркуйте свої логи унікальними префіксами, щоб їх було легко знайти в потоці:

msg!("[INIT] Initializing vault state");
msg!("[TRANSFER] Amount: {}, from: {}", amount, from);

Логи CPI-викликів

Як відстежити ланцюг CPI у логах

Cross-Program Invocation (CPI) — це механізм, за якого одна програма на Solana викликає іншу. У логах кожен CPI-виклик збільшує лічильник глибини в квадратних дужках. Це дозволяє відновити повне дерево викликів.

Структура виглядає так:

  1. Program A invoke [1] — ваша програма стартує
  2. Program log: Starting transfer... — ваш лог
  3. Program B invoke [2] — ваша програма викликає програму B через CPI
  4. Program log: Instruction: Transfer — лог програми B
  5. Program B success — програма B завершилася
  6. Program log: Transfer completed — повернення до вашої програми
  7. Program A success — ваша програма завершилася

Число в дужках — це глибина вкладеності. Якщо програма B через CPI викликає програму C, ви побачите [3]. Якщо на будь-якому рівні стається помилка, весь ланцюг відкочується, і ви побачите failed на рівні програми, де сталася помилка, після чого батьківська програма також отримає failed з причиною, що вказує на невдалий CPI-виклик.

Приклад: логи токен-транзакції

Нижче — приклад логів транзакції, яка переказує SPL-токени через вашу програму, яка в свою чергу викликає Token Program через CPI:

Program <YOUR_PROGRAM> invoke [1]
Program log: [TRANSFER] Initiating token transfer
Program <TOKEN_PROGRAM> invoke [2]
Program log: Instruction: Transfer
Program <TOKEN_PROGRAM> consumed 3462 of 200000 compute units
Program <TOKEN_PROGRAM> success
Program log: [TRANSFER] Token transfer succeeded
Program <YOUR_PROGRAM> consumed 12840 of 200000 compute units
Program <YOUR_PROGRAM> success

Зверніть увагу: логи вашої програми ([1]) та логи Token Program ([2]) чергуються, але глибина дозволяє чітко розмежувати, хто що вивів. Якби Token Program повернув помилку, ви б побачили Program <TOKEN_PROGRAM> failed: ... на рівні [2], і після цього — Program <YOUR_PROGRAM> failed на рівні [1].

Типові помилки

Порожні логи: чому програма мовчить

Ви відправили транзакцію, отримали failed, але логів немає або є лише рядки invoke і failed без жодного Program log:. Причини бувають такі:

  • Програма не містить макросів msg! — якщо ви не додали логування в код, runtime нічого не виведе. Це найчастіша причина для програм, написаних без Anchor, де логи додаються автоматично для багатьох операцій.
  • Помилка на етапі валідації акаунтів — якщо runtime відхиляє транзакцію до виклику вашої програми (наприклад, невірний підпис, недостатній баланс для оренди, невірний власник акаунта), ваш код взагалі не виконається, і ваших логів не буде. Ці помилки розглядаються в окремому розділі про типові помилки транзакцій.
  • Невірний program_id — ви дивитесь логи не тієї програми. Переконайтеся, що ID у логах збігається з тим, який ви деплоїли на Devnet.

Спосіб перевірки: порівняйте підпис транзакції з тим, що показує solana confirm -v. Якщо ви бачите invoke вашої програми, але немає жодного Program log: — проблема в відсутності msg! у коді. Якщо ви не бачите навіть invoke — проблема на рівні валідації транзакції ще до потрапляння у вашу програму.

Обрізані логи через ліміт розміру

Solana накладає ліміт на загальний розмір логів однієї транзакції. Якщо ваша програма генерує забагато виводу (наприклад, логуєте великий масив даних у циклі), логи будуть обрізані, і ви втратите частину діагностичної інформації.

Ліміт стосується суто логів — він не впливає на виконання програми, але ускладнює діагностику. Щоб цього уникнути:

  • Не логуйте цілі масиви або структури даних. Логуйте лише ключові значення: індекси, суми, статуси, ID акаунтів.
  • Використовуйте умовне логування: msg! лише у гілках, де ви очікуєте проблему, а не на кожному кроці.
  • Для глибокої діагностики на Devnet можна тимчасово зменшити обсяг даних, що обробляються, щоб зберегти детальні логи для критичної ділянки коду.

Цей підхід відрізняється від production-стратегії, де логи взагалі мінімізують через витрати compute units. На Devnet ви маєте повну свободу для діагностики, але в межах ліміту розміру логів.

Джерела