Логи програми на 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-систем рекомендується побудувати конвеєр:
- Збір. Скрипт-колектор періодично опитує RPC або підписується на WebSocket-підключення (метод
logsSubscribe), фільтруючи за вашим Program ID. - Парсинг. Кожен лог-рядок класифікується: це
msg!,AnchorError,invoke/success/error, чиProgram data. - Збагачення. AnchorError коди мапляться на людячі описи з вашого IDL. Кастомні помилки (6000+) — з поля
errorsIDL. - Агрегація. Зібрані події відправляються у вашу систему моніторингу (Grafana Loki, Elasticsearch, Datadog) із мітками: Program ID, ім'я інструкції, тип помилки, сигнатура транзакції.
При такому підході ви отримуєте можливість не лише реагувати на окремі падіння, а й бачити патерни: які помилки трапляються найчастіше, які інструкції нестабільні, чи є кореляція з певними слотами або навантаженням на мережу.
Обмеження та межі застосування
- Логи не зберігаються на ланцюгу назавжди. RPC-ноди зазвичай зберігають логи за останні кілька сотень слотів. Для довгострокового зберігання ви повинні зберігати їх самостійно в момент виникнення.
- Метод
getTransactionможе повернутиnullдля старих транзакцій, якщо нода вже очистила кеш. У такому разі допоможе лише архівна нода (archive node) або сторонній індексатор. - Логи не містять інформації про compute units, спожиті інструкцією, — для цього потрібен окремий запит до метаданих транзакції.
- Не покладайтеся на логи як на єдиний механізм моніторингу. Критичні метрики (кількість викликів, частота помилок за типами) повинні збиратися окремо через індексацію транзакцій або on-chain метрики.
Наступний логічний крок після налагодження логування — автоматизована перевірка транзакцій перед відправкою. Як це зробити за допомогою simulation, описано в матеріалі Як використовувати simulation для перевірки транзакцій.