English · Русский
Запись трассы
Настоящий файл debug.info целиком, снятый с прогона из README. Программа — двадцать строк на Python, три функции, два вызова верхнего уровня: один удачный, второй с делением на ноль.
Файл целиком
{"p":"in","t":"2026-08-28T23:39:45.166","id":"23192bfc-8625-453d-bd48-1af02ceec638","ci":-1,"th":"2864987.129949101195776","fn":"report","a":"['get 12', 'put 30', 'get 18']","k":""}
{"p":"in","t":"2026-08-28T23:39:45.166","id":"9f93c6ea-f76f-4768-ab35-cb41731e05f1","ci":-1,"th":"2864987.129949101195776","fn":"parse_line","a":"'get 12'","k":""}
{"p":"out","id":"9f93c6ea-f76f-4768-ab35-cb41731e05f1","fn":"parse_line","r":"('get', 12)","d":2e-06}
{"p":"in","t":"2026-08-28T23:39:45.166","id":"4fc1254d-6b4f-451f-8e5f-ddb4b1597ca9","ci":-1,"th":"2864987.129949101195776","fn":"parse_line","a":"'put 30'","k":""}
{"p":"out","id":"4fc1254d-6b4f-451f-8e5f-ddb4b1597ca9","fn":"parse_line","r":"('put', 30)","d":2e-06}
{"p":"in","t":"2026-08-28T23:39:45.166","id":"73eba9cc-20ae-4bd4-92d8-4e50e6dc2376","ci":-1,"th":"2864987.129949101195776","fn":"parse_line","a":"'get 18'","k":""}
{"p":"out","id":"73eba9cc-20ae-4bd4-92d8-4e50e6dc2376","fn":"parse_line","r":"('get', 18)","d":1e-06}
{"p":"in","t":"2026-08-28T23:39:45.166","id":"e668ee33-6d35-4bb8-9f96-cb44f488c93f","ci":-1,"th":"2864987.129949101195776","fn":"average","a":"[12, 30, 18]","k":""}
{"p":"out","id":"e668ee33-6d35-4bb8-9f96-cb44f488c93f","fn":"average","r":"20.0","d":2e-06}
{"p":"out","id":"23192bfc-8625-453d-bd48-1af02ceec638","fn":"report","r":"20.0","d":0.000346}
{"p":"in","t":"2026-08-28T23:39:45.166","id":"cb4e33f4-d0b2-4099-bcf2-044e4a88c1fa","ci":-1,"th":"2864987.129949101195776","fn":"report","a":"[]","k":""}
{"p":"in","t":"2026-08-28T23:39:45.166","id":"ef71eb89-6727-4cdf-a3b7-2ea54cff81e3","ci":-1,"th":"2864987.129949101195776","fn":"average","a":"[]","k":""}
{"p":"out","id":"ef71eb89-6727-4cdf-a3b7-2ea54cff81e3","fn":"average","x":"ZeroDivisionError: division by zero","d":3e-06}
{"p":"out","id":"cb4e33f4-d0b2-4099-bcf2-044e4a88c1fa","fn":"report","x":"ZeroDivisionError: division by zero","d":6.8e-05}14 строк, 1887 байт, 7 вызовов — около 270 байт на вызов.
Обратите внимание на порядок: строка входа report стоит первой, а её строка выхода — десятой, после всего, что report успел позвать. Вложенность видна по тому, какие вызовы попали между входом и выходом.
Куда пишется
- Один файл, только дописываемый, UTF-8, по одному объекту JSON в строке.
- Путь берётся из переменной среды
OUROBOROS_DEBUG_INFO. Не задана — файл./debug.infoв рабочем каталоге процесса. ouroboros executeподставляетOUROBOROS_DEBUG_INFOравным<draft>/debug.infoперед запуском — поэтому обмазанный код на любом языке пишет в один и тот же файл.- Запись идёт не через стандартный вывод. Помощник дописывает записи прямо в файл, поэтому вывод самой программы остаётся чистым.
- Каждая запись пишется одним добавлением и укладывается в размер, при котором строки не могут перемешаться: два процесса или два потока не порвут строку друг другу.
Ключи
| ключ | на какой строке | что значит |
|---|---|---|
p | на обеих | вход ("in") или завершение ("out") |
t | вход | время входа: местное ISO-8601 с точностью до миллисекунды |
id | на обеих | UUIDv4, свой у каждого вызова; он и связывает вход с выходом |
ci | вход | номер ядра процессора; -1 = «неизвестно» |
th | вход | метка потока; у Python — <процесс>.<номер потока> |
fn | на обеих | полное имя функции — нарочно на обеих строках |
a | вход | доводы по позиции, снятые при входе, до тела |
k | вход | именованные доводы как имя=значение; где их не бывает — пустая строка |
r | выход | результат при обычном возврате. Не бывает вместе с x |
x | выход | <Тип>: <сообщение> при ошибке. Не бывает вместе с r. У C исключений нет, и в его записях бывает только r |
d | выход | длительность в секундах, числом; с монотонных часов. У завершения с исключением d тоже есть |
Ключи нарочно короткие: строк много, и лишние байты в каждой дорого обходятся на объёмных записях.
fn на обеих строках — нарочно. Если запись оборвалась и строка входа потерялась, оторванная строка выхода всё равно говорит, что вернулось.
Доводы снимаются при входе, до тела. Проверено: функция, которая дописывает в переданный ей список, всё равно записала [1, 2] — то, с чем её позвали, — а не то, во что она список превратила.
Результат снимается до того, как уйдёт наружу. Помощник забирает его и записывает прежде, чем отдать вызывающему.
Зависший вызов виден прямо
Строка входа, у которой нет парной строки выхода с тем же id, означает, что вызов вошёл и не вернулся — зависание, падение или жёсткий выход.
Искать непарные id руками не нужно: читалка это уже сделала, и они лежат в поле in_flight каждого ответа. Это ровно тот ответ, которого не даёт ни один способ записи «одной строкой по завершении»: вызов, который не завершился, в такой записи просто отсутствует и неотличим от вызова, которого не было.
Длительность при этом тоже не считается вычитанием: она стоит готовой в поле d.
Записи, которые не про вызовы
ouroboros execute добавляет по одной служебной записи на запущенную команду — правильную строку JSON, у которой p не in и не out:
{"p":"exec","cmd":["python3","stats.py"],"rc":0,"out":"20.0\n","err":""}Так debug.info остаётся однородным JSONL и единственным местом, где написано, что запускалось и чем кончилось.
Разбор пропускает любую правильную строку JSON, у которой p не in и не out. Испорченными (malformed) считаются только строки, которые не разбираются как JSON, или порванные — например от сбойного захвата с последовательного порта при работе в ядре системы.
Предел длины значения
Длинные значения ограничивает короткое изображение значения: у Python это reprlib с maxstring = maxother = 200, а списки, словари и наборы обрезаются до первых десяти элементов (ouroboros/runtime.py:53). Остальные языки держатся тех же пределов.
Значение не обрезается «как-нибудь» и не восстанавливается по остатку. Соседний случай — <Foo object at 0x…>: это записано, но это тождество объекта, а не его значение.
Одна схема, разные диалекты
Схема у восьми языков одна. Диалекты — как язык изображает значение, как пишет полное имя, как печатает число — нарочно оставлены родными.
Что это значит на практике — на странице Языки.
Чего в записи нет по устройству
Веток, в которые не заходили. Записи описывают прогон, а не программу.
Намерения. Функция вернула -1 — код ошибки или настоящий ответ?
Смысла в порядке вызовов. Есть время входа, номер вызова, поток и длительность. Что вызов был после другого и потому дал такой ответ, не записывается.
Настоящей цены вызова. Поле d меряет обмазанный прогон.
Полностью — Границы.