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

Формат логів Anchor-програм

Кожна транзакція на Solana генерує логи для кожної програми, яка брала участь у виконанні. Логи вашої програми ідентифікуються за її Program ID і мають кілька типів рядків із різним префіксом.

Базова структура логів виклику Anchor-програми виглядає так:

Program 7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU invoke [1]
Program log: Instruction: Initialize
Program 11111111111111111111111111111111 invoke [2]
Program 11111111111111111111111111111111 success
Program 7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU success

Розберемо кожен тип рядка:

  • Program {ID} invoke [N] — програма з цим ID отримала управління. Число в дужках — глибина стеку виклику. Якщо N=1, це прямий виклик від користувача. N=2 означає, що програму викликала інша програма через CPI.
  • Program log: Instruction: {Name} — Anchor автоматично логує ім'я інструкції, яку було розпізнано за дескриптором. Якщо цього рядка немає, програма не змогла десеріалізувати дані інструкції.
  • Program log: {текст} — вивід макроса msg! або внутрішні логи Anchor.
  • Program data: {hex} — вивід функції sol_log_data, зазвичай містить серіалізовані дані у hex-форматі.
  • Program {ID} success — програма завершила виконання без помилок.
  • Program {ID} error: {hex_code} — програма повернула помилку через solana_program::program_error::ProgramError. Цей формат використовується для нативних помилок, а не для AnchorError.

Ключове правило аналізу: фільтруйте логи за Program ID вашої програми та звертайте увагу на глибину стеку. Якщо ваша програма викликає іншу через CPI, логи викликаної програми будуть між вашим invoke та success (або error).

Розшифрування AnchorError кодів

Коли Anchor-програма повертає помилку, ви побачите блок із чіткою структурою:

Program log: AnchorError occurred. Error Code: ConstraintSeeds. Error Number: 2008. Error Message: A seeds constraint was violated.

Цей рядок містить три ключові поля:

  • Error Code — іменована константа з вихідного коду програми або з бібліотеки Anchor.
  • Error Number — числовий код. Для вбудованих помилок Anchor це значення від 3000 до 3099. Для кастомних помилок вашої програми — від 6000 і вище (точне значення залежить від кількості визначених помилок).
  • Error Message — людячий опис помилки.

Найчастіші вбудовані помилки Anchor та їхні числові коди:

Error Code Error Number Коли виникає
InstructionMissing 3001 Дані інструкції порожні або не містять валідного дескриптора
InvalidInstructionData 3002 Дані інструкції не відповідають очікуваній структурі (неправильний розмір, невалідні поля)
InvalidAccountData 3003 Дані на рахунку не відповідають очікуваній структурі акаунта
AccountDiscriminatorMismatch 3004 Перші 8 байт даних рахунку не збігаються з очікуваним дескриптором типу
ConstraintMut 3005 Рахунок позначений як mut, але не є записуваним у цій транзакції
ConstraintOwner 3006 Власник рахунку не збігається з очікуваним Program ID
ConstraintSigner 3007 Рахунок позначений як signer, але не підписав транзакцію
ConstraintSeeds 3008 PDA, згенерований із вказаних насінин, не збігається з переданим адресою рахунку
ConstraintSpace 3009 Розмір рахунку менший за необхідний для розміщення даних
ConstraintTokenMint 3010 Mint-рахунок токена не збігається з очікуваним
ConstraintTokenOwner 3011 Власник токен-рахунку не збігається з очікуваним адресою
ConstraintAssociated 3012 Токен-рахунок не є асоційованим рахунком (Associated Token Account) для вказаного власника та mint
ConstraintAddress 3013 Адреса рахунку не збігається з жорстко заданою в constraint = address
ConstraintRaw 3014 Вираз у constraint = ... повернув false

Для кастомних помилок вашої програми Error Number починається з 6000. Нумерація йде послідовно в порядку оголошення у блоці #[error_code]. Якщо ви бачите Error Number 6002 — це третя за рахунком помилка у вашому enum (починаючи з 6000). Щоб точно зіставити число з іменем, перевірте вихідний код програми або згенерований IDL, де кастомні помилки перелічені в полі errors.

Типова помилка при аналізі: плутанина між Program {ID} error: 0x... та AnchorError occurred. Перший формат — це нативна помилка Solana Runtime (наприклад, insufficient funds, account already in use). Другий — це помилка на рівні бізнес-логіки Anchor. Вони мають різні формати і різні способи розшифрування.

Кастомні логи через msg! макрос

Макрос msg! — це основний інструмент діагностики всередині Anchor-програми. Він викликає sol_log і виводить рядок із префіксом Program log:.

Базове використання:

msg!("Vault authority: {}", ctx.accounts.vault.authority.key());
msg!("Current balance: {}", ctx.accounts.vault.lamports);
msg!("Instruction started at slot: {}", Clock::get()?.slot);

Що важливо знати про msg! у production-контексті:

  • Обмеження розміру. Кожен виклик msg! додає дані до лог-буфера транзакції. Загальний розмір логів транзакції обмежений (зазвичай до кількох кілобайтів). Якщо перевищити ліміт, транзакція впаде з помилкою TransactionError::LogTooLong. Не логуйте великі масиви даних або серіалізовані структури цілком.
  • Вплив на gas. Логи не впливають на compute units безпосередньо, але кожен виклик sol_log споживає трохи процесорного часу. У межах одного виклику це мінімально, але масове логування в циклі може стати проблемою.
  • Форматування. msg! підтримує синтаксис format!, тому можна виводити значення змінних. Але не передавайте туди складні типи без реалізації Display — це призведе до помилки компіляції.
  • Порядок виконання. msg! виконується синхронно в точці виклику. Якщо ви поставили msg! перед перевіркою, а програма впала на перевірці — ви побачите лог. Якщо після — не побачите. Це базовий, але надійний спосіб локалізувати місце падіння.

Практичний патерн для діагностики constraint-помилок:

msg!("Checking PDA seeds: {:?}", ctx.seeds);
msg!("Expected PDA: {}", ctx.accounts.expected_pda.key());
// Після цього рядка Anchor перевірить ConstraintSeeds
// Якщо впаде — ви вже знаєте, які насінини використовувалися

Для виведення бінарних даних (наприклад, для діагностики серіалізації) використовуйте sol_log_data безпосередньо:

sol_log_data(&[b"data-prefix", &account_data]);

Це виведе рядок Program data: 646174612d707265666978..., який доведеться декодувати з hex вручну або скриптом.

Інструменти для парсингу та фільтрації логів

У production ви рідко читаєте логи сирими. Ось перевірені інструменти та підходи для різних сценаріїв.

Отримання логів транзакції

Через CLI:

solana confirm -v {signature} --url {rpc_endpoint}

Прапорець -v (verbose) виводить повні логи. Без нього ви отримаєте лише статус. Для машинного парсингу додайте --output json:

solana confirm -v {signature} --output json --url {rpc_endpoint}

Через RPC-виклик методу getTransaction із параметром commitment: "confirmed" та опцією maxSupportedTransactionVersion: 0. Логи будуть у полі transaction.meta.logMessages.

Фільтрація за Program ID

Найчастіша завдання — відокремити логи вашої програми від логів системних програм (System Program, Token Program, Associated Token Program). Через grep:

solana confirm -v {signature} | grep "Program 7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU"

Для JSON-виводу через jq:

solana confirm -v {signature} --output json | jq '.transaction.meta.logMessages[] | select(startswith("Program 7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU"))'

Парсинг AnchorError з JSON

Щоб автоматично витягнути Error Code та Error Number з логів:

solana confirm -v {signature} --output json | jq -r '.transaction.meta.logMessages[] | select(test("AnchorError occurred"))' | grep -oP 'Error Code: \K[^.]+'

Це поверне лише ім'я Error Code, яке можна мапити на документацію або на ваш внутрішній каталог помилок.

Логи в реальному часі

Для локального solana-test-validator логи виводяться безпосередньо в stdout. Для фільтрації під час розробки:

solana-test-validator ... 2>&1 | grep --line-buffered "AnchorError\|Program log: Instruction"

Прапорець --line-buffered критично важливий — без нього grep буферизує вивід і ви не побачите логи в реальному часі.

Для production-валідатора логи пишуться у файл, який вказано в конфігурації (параметр LOG у agave-validator). Формат файлу — по одному рядку на подію, що дозволяє використовувати стандартні інструменти: tail -f, less, awk.

Структуроване логування у вашій інфраструктурі

Для production-систем рекомендується побудувати конвеєр:

  1. Збір. Скрипт-колектор періодично опитує RPC або підписується на WebSocket-підключення (метод logsSubscribe), фільтруючи за вашим Program ID.
  2. Парсинг. Кожен лог-рядок класифікується: це msg!, AnchorError, invoke/success/error, чи Program data.
  3. Збагачення. AnchorError коди мапляться на людячі описи з вашого IDL. Кастомні помилки (6000+) — з поля errors IDL.
  4. Агрегація. Зібрані події відправляються у вашу систему моніторингу (Grafana Loki, Elasticsearch, Datadog) із мітками: Program ID, ім'я інструкції, тип помилки, сигнатура транзакції.

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

Обмеження та межі застосування

  • Логи не зберігаються на ланцюгу назавжди. RPC-ноди зазвичай зберігають логи за останні кілька сотень слотів. Для довгострокового зберігання ви повинні зберігати їх самостійно в момент виникнення.
  • Метод getTransaction може повернути null для старих транзакцій, якщо нода вже очистила кеш. У такому разі допоможе лише архівна нода (archive node) або сторонній індексатор.
  • Логи не містять інформації про compute units, спожиті інструкцією, — для цього потрібен окремий запит до метаданих транзакції.
  • Не покладайтеся на логи як на єдиний механізм моніторингу. Критичні метрики (кількість викликів, частота помилок за типами) повинні збиратися окремо через індексацію транзакцій або on-chain метрики.

Наступний логічний крок після налагодження логування — автоматизована перевірка транзакцій перед відправкою. Як це зробити за допомогою simulation, описано в матеріалі Як використовувати simulation для перевірки транзакцій.

Джерела