ermouth: (ang)
ermouth ([personal profile] ermouth) wrote2015-06-27 12:11 pm
Entry tags:

Логгер для CloudWall

Написал за ночь сегодня логгер для следующеего релиза CloudWall, сейчас потестил – оч шустро вышло. Написал заметочку в корпоративной Стене и решил сюда кросспостнуть.

Под катом инженерные соображения про логгер для браузерной системы.

Заимплементил логгер. Это довольно любопытный зверёк, поэтому подробнее опишу. Смотреть код можно тут (сорри, френды, вам пока нельзя), искать функцию _logstart.

Требования к логгеру


  1. Логгер должен стартовать до всей остальной системы

  2. Должен писать всё, что пишется в системную консоль (именно она отображается на консоль браузера в debug-mode)

  3. Должен быть синхронным и быстрым

  4. Он не должен падать ни от какого набора аргументов, в тч от jQuery-объектов – и записывать все аргументы, которые возможно записать

  5. Должен контролировать длину сериализованных аргументов, чтобы не засорять лог длинными записями

  6. Не должен занимать много ключей в ls

  7. Должен дописываться append-ом (поэтому чистый JSON не подходит; с ним чтобы добавить запись, надо сначала распарсить все предыдущие записи, потом вставить элемент, а потом обратно сериализовать – это O(n^2) сложность, а нам надо O(1)) (UPD. Никак тут не обойтись без минимум O(n))

  8. Должен различать сообщения из разных табов одной системы

  9. Должен быть читаем человеком

Я над этим неделю думал и читал всякое, и вот что получилось.

Есть два ключа в localStorage – _cw_log_curr и _cw_log_prev. Первый рабочий, его длина ограничена 50Кб, второй – архивный, не больше 1Мб. Параметры задаются в конфиге.

Как только рабочий лог становится длинным, он приписывается к архивному целиком и очищается. Архивный, если стал длинным, подрезается с головы до 1Мб.

Структура

Сам по себе лог – длинная строка, набор JSON-строк, конкатенированных через \n. Разрывов строк в JSON-ах не бывает (это энфорсится), поэтому такой подход рационален вполне.

То-есть для добавления новой записи мы читаем текущий лог как длинную строку, это быстро. Ничего не парсим, а просто приписывем к этой строке \n и сериализованную в JSON новую запись лога. То, что получилось, пишем обратно в ls.

Каждый раз читать лог нужно потому, что в него может писать соседняя вкладка, например.

Если лог нужно разобрать по записям, мы читаем строку лога, делаем .split("\n") и получаем массив JSON-строк записей. Каждую запись нужно потом распарсить отдельно. Заодно такой подход защищает нас от кривых записей – они не повлияют на остальные, даже если не разберутся.

Для различения сессий я ввёл сессионный ключ, он генерится новый при каждой загрузке cw.js – то-есть записи из соседних вкладок будут иметь разные маркеры.

Сериализация записи

Тут тоже нужен трюк. cw.log(arg1, arg2, ...argN) может быть вызван с несколькими аргументами, причём некоторые после сериализации могут оказаться слишком длинными.

Поэтому сделан кастомный сериализатор, который обрезает слишком длинные строки, получившиеся после стрингифая каждого аргумента. Ещё он принимает на вход jQuery-объекты и сохраняет их как {jQuery:["SPAN#id.class1.class2","DIV.class3"]}.

Зацикленные объекты тоже не отклоняются вслепую – по ним пишется список ключей первого уровня.

Как посмотреть

Чтобы прочитать рабочий лог, можно в консоли выполнить cw.log(N) – вернёт массив из N последних неразобранных записей.

cw.log(N).map(JSON.parse) – вернёт массив из распарсенных.

Скорость – примерно 100-200мкс на сохранение килобайтной записи с обрезкой длинных узлов (~5Кб) посредине, это при отправке 1000 записей подряд. И можно ещё быстрее кратно сделать, если пооптимизировать на серализации и в плане подстройки по GC, чтобы он пореже мир останавливал.

Такие дела.

---

Пока копировал псто, подумал ещё, что надо сделать фичу распознавания повторяющихся событий. И, может, это выпилить отдельной стэндэлон-библиотекой и на гитхаб сложить.

Справочно: скорость записи в localStorage небольших фрагментов (десятки килобайт) колоссальна: на моём i7 – сотни мегабайт в секунду в любом браузере. Это многократно превосходит среднюю скорость сериализации в JSON, например.

Коллеги, что я забыл про важные фичи лога?

[identity profile] soloviewoff.livejournal.com 2015-06-27 02:58 pm (UTC)(link)
> Каждый раз читать лог нужно потому, что в него может писать соседняя вкладка, например.

Но можем потерять запись (lost update)?

[identity profile] ermouth.livejournal.com 2015-06-27 05:24 pm (UTC)(link)
Да, можем. При имеющейся плотности событий в наихудшем случае (на одновременном старте двух систем в двух соседних вкладках) мы будем их терять с вероятностью примерно 1e-6.

При этом в лог у нас помещается примерно 20К записей, так что вероятность потери от лог ротэйта выше.

В этой связи я посчитал нецелесообразным городить какие-то дополнительные механизмы, чтобы ещё эту вероятность уменьшить. Они все замедлят лог, а совсем надёжные сделают его вовсе асинхронным, что неприемлемо.

Одновременный старт в двух вкладках событие маловероятное само по себе. Другие близкие к одновременным события (репорт о новых приложениях, например, когда они в базу прилетают) терять не страшно, они для разных вкладок всё равно одинаковые, база то одна.

Обычно всё же пользователь работает в одной вкладке, это сильно смещает tradeoff надёжность-скорость в сторону скорости.


Edited 2015-06-27 17:25 (UTC)

[identity profile] soloviewoff.livejournal.com 2015-06-27 06:14 pm (UTC)(link)
ОК. Еще перенос в архив может дублироваться.

[identity profile] ermouth.livejournal.com 2015-06-27 06:50 pm (UTC)(link)
Да, но это не страшно же само по себе и ещё маловероятнее.

Вообще, главное соображение – в неактивных вкладках типичная плотность событий, которые страшно потерять, уменьшается на примерно два порядка. Скрипты подтормаживаются, приложения пользовательские событий не генерят и тп.

Если б это было не так и не надо было бы уложиться в мегабайт, я б делал по ключу в БД на каждую сессию, по ключу на heartbeat каждой сессии и асинхронный сборщик/архиватор логов умерших сессий на старте новой вкладки.

Но я не хочу вылезать из мегабайта никогда – потому что полная ls quota в браузерах 5-10Мб. Да и 1е-6 в наихудшем сценарии по мне ок.

[identity profile] tonsky.livejournal.com 2015-06-28 03:29 pm (UTC)(link)
Про одновременный старт — если было открыто несколько вкладок с приложениями, потом браузер перезагрузили и он восстанавливает вкладки, они примерно все одинаково начинают открываться. Конечно, не такт в такт, но сама ситуация способствует гонке на старте

[identity profile] ermouth.livejournal.com 2015-06-28 05:08 pm (UTC)(link)
Классный поинт – а я дурак что это упустил. Буду переделывать доделывать, спасибо.

Первой стартует активная вкладка. Она может писать в ls маркер, которые остальные будет проверять на старте, и если разница небольшая, просто подтормаживать старт. Заодно кста можно сделать так, чтобы PIN надо было вводить в таком мценарии однократно. Сейчас нужно во всех вкладках.
Edited 2015-06-28 17:40 (UTC)

[identity profile] morfizm.livejournal.com 2015-06-28 01:18 am (UTC)(link)
Важные фичи лога (согласен с твоими, кроме спорного п.5, добавляю ещё):

1. Как ты правильно заметил, линейное добавление к логу. У тебя не O(1), потому что склеивание строк это уже O(N). На практике, может быть две пары строк (<50kb и <1mb) будут good enough, но в теории, чтобы получить хотя бы амортизированный O(1) на склеивании строк, тебе нужно иметь не пару буферов, а O(log N).

2. Нужна дифференциация по verbosity levels. Твой п.5 слабоват: на практике тебе, как правило, либо нужно целиком всё сериализованное значение, либо никакого вовсе. Нужно иметь возможность включить нужный уровень logging'а при необходимости, пусть во вред performance'у, но который сохранит всё, что нужно.

3. Нужна дифференциация по severity of errors, хотя бы для того, чтобы для high severity делать flush (не накапливать в памяти и дожидаться, пока storage запишет). Если у тебя и так всегда flush, то disregard this :)

4. Нужна возможность каких-то связывающих идентификаторов, чтобы можно было реализовать "сквозной" просмотр логов из разных компонент, касающихся одного workflow. Например, когда ты дебагаешь веб-приложение, тебе хорошо бы видеть в хронологическом порядке логи сервера в перемешку с логами клиента. Чтобы это было возможным, нужны всякие там request ids. Ну и часы правильно синхронизовать и в одной часовой зоне (а лучше в GMT) логать время. Я бы для этого функционала добавил бы фичу закачивать логи на сервер, которую можно включить опционально и "на лету".

5. Logging engine должен быть максимально прост, потому что ты не можешь использовать его же, чтобы логать ошибки внутри logging engine :) Сделай хорошее покрытие тестами.

[identity profile] ermouth.livejournal.com 2015-06-28 02:10 am (UTC)(link)
5. Вдруг ты не видел ещё этот прекрасный твит

Yay, all unit tests passing! pic.twitter.com/ax2uxPsZqv

— Dave Hulbert (@dave1010) June 24, 2015

[identity profile] morfizm.livejournal.com 2015-06-28 02:24 am (UTC)(link)
Отлично :)))

[identity profile] morfizm.livejournal.com 2015-06-28 01:19 am (UTC)(link)
Ещё одно (очевидное, но добавлю для полноты): логгер должен стартовать раньше всех и умирать последним :)

[identity profile] ermouth.livejournal.com 2015-06-28 02:05 am (UTC)(link)
Да, насчёт «умирать последним» ещё есть над чем поработать. Синхронность кста нужна, чтобы логгер мог записать ‘Tab closing’, когда вкладка умирает.

1. Если память копируется, то да, O(N). Но у меня есть стойкое ощущение, что непосредственно при конкатенации строк память не копируется – результирующая строка получается как набор указателей и не копируется до тех пор, пока части остаются неизменными. Хотя я не уверен на 100% и в код V8 тыкнуть не могу.

Но при записи в ls копируется, конечно.

2-3. Я ещё не придумал, как это по-хорошему сделать, но точно сделаю.

[identity profile] morfizm.livejournal.com 2015-06-28 02:27 am (UTC)(link)
1. Я не специалист в веб-девелопменте и могу не знать всё из новинок, что относится к задаче, но буду удивлён, если браузер будет оптимизировать конкатенацию строк из local storage. Ведь для этого нужно хранить строку как сложную структуру (список, дерево) на диске. Интуитивно - очень-очень вряд ли. Но мало ли, может быть :)

[identity profile] ermouth.livejournal.com 2015-06-28 02:46 pm (UTC)(link)
Хехе, ты знаешь, я решил всё же почитать код.

Оно работает так. При старте вкладки весь localStorage кэшируется в оперативной памяти и хранится как конструкция, стандартная для рантайма.

Она только как-то по-другому попадает под GC и неё на входе, видимо, ограничение, она принимает объекты не всяких типов под ключи.

Я предположил, что это явно означает, что строки не копируются и дальше копаться в коде Хромиума не стал, а провёл такой эксперимент:

var x = "x".repeat(1000000), t;
localStorage.setItem("x",x); t=Date.now();
var i=1, a=[];a[0] = localStorage.getItem("x");
for(i=1;i<100;i++) a[i]=a[i-1]+localStorage.getItem("x");
Date.now() - t;
>160

Эта штука выполнилась за 160мс. Снэпшот всего, что было выделено по пути, выглядит так:



8Мб на всю вкладку.

Если бы память копировалась, оно бы выделило минимум гигабайт, а максимум – 5 Гб. За 1/6 секунды такой объём можно закопировать в одном потоке, если иметь производительность связки память-процессор 30Гб/с+, это не про айМак )

Так что тут напрашивается два вывода:

1. Хороший: как минимум при чтении из ls и последующей конкатенации строки не копируются, а создаётся вектор указателей на буфера.

2. Плохой: оно персистится на диск только по закрытию вкладки.

Ну и 3 – видимо, и при записи в ls строки не копируются.

Кажется, в этом случае моё чутьё получше твоего сработало, что вселяет в меня просто нереальный оптимизм )

[identity profile] morfizm.livejournal.com 2015-06-29 05:56 am (UTC)(link)
Я не очень понимаю, что именно ты тестировал/почему так.

Попробуй вместо:
a[i]=a[i-1]+localStorage.getItem("x");

Написать вот такое:
localStorage.setItem("x", x + localStorage.getItem("x"))

Тогда мы действительно будем append'ить строки к localStorage.
В твоём примере append'ятся строки, уже живущие в JavaScript-списке, а не помещённые в localStorage. localStorage вполне может иметь layer кэша и не ходить на диск за каждым повторным вызовом getItem() если setItem() между ними не было.

[identity profile] morfizm.livejournal.com 2015-06-29 06:08 am (UTC)(link)
Если я меняю так как я предложил, оно тут же вылетает по квоте. Видимо 100 MB многовато. Поэтому для тестов меняем 1M на 10K:

1.
var x = "x".repeat(10000), t;
localStorage.setItem("x",x); t=Date.now();
var i=1, a=[];a[0] = localStorage.getItem("x");
for(i=1;i<100;i++) a[i]=a[i-1]+localStorage.getItem("x");
Date.now() - t;

стабильно 3ms.


2.

var x = "x".repeat(10000), t;
localStorage.setItem("x",x); t=Date.now();
var i=1, a=[];a[0] = localStorage.getItem("x");
for(i=1;i<50;i++) localStorage.setItem("x", x + localStorage.getItem("x"));
Date.now() - t;

200-250 ms, среднее около 225ms.


3.


var x = "x".repeat(10000), t;
localStorage.setItem("x",x); t=Date.now();
var i=1, a=[];a[0] = localStorage.getItem("x");
for(i=1;i<100;i++) localStorage.setItem("x", x + localStorage.getItem("x"));
Date.now() - t;

980-1080ms, но среднее тяготеет к нижней границе, около 1000ms.

Вот тебе пришла квадратичность. Объём конечной строки вдвое выше, а времени потребовалось в 4 раза больше.


4.

var x = "x".repeat(10000), t;
localStorage.setItem("x",x); t=Date.now();
var i=1, a=[];a[0] = localStorage.getItem("x");
for(i=1;i<150;i++) localStorage.setItem("x", x + localStorage.getItem("x"));
Date.now() - t;

2470-2880ms, среднее около 2600ms.

Размер конечной строки увеличили ещё в 1.5 раза, время увеличилось в более, чем 2.25 раз, т.е. оно чуть хуже, чем квадратично.

[identity profile] ermouth.livejournal.com 2015-06-29 09:43 am (UTC)(link)
Да, при записи копируется, O(n) ( Нереальный оптимизм как-то подостыл ггг

Получается, что без json-a O(n^2). С json-ом просто ещё минимум втрое (это если бы каждая запись строкой была) медленней.

Вот же говно.

Ну, всё равно переделывать – там Тонски ниже хороший поинт написал. Стало быть, если я ввожу ключ, который фиксирует, какая последняя сессия писала в ls, я могу не читать весь working cache, если писала в предыдущий раз та же самая вкладка.

В этом случае я храню то, что было записано в прошлый раз, локально, аппендю к нему и пишу что получилось. Тогда тоже O(n^2), но всё же быстрее.

Ну и два набора – длинный и короткий – тоже позволяют этому O(n^2) быть не так заметным.
Edited 2015-06-29 09:54 (UTC)

[identity profile] morfizm.livejournal.com 2015-06-29 10:28 am (UTC)(link)
Мне кажется, вкладки надо изолировать, если это хоть как-то возможно. У вкладок есть свой ID? Если да, то можно prepend'ить его к ключам в LS. Плюс какой-то алгоритм garbage collection, чтобы следить за расходуемым местом и удалять старые, или merge'ить их в глобальный репозиторий. Ну, х.з., конечно, может, и так будет стабильно работать, но если что - имей в виду эту идею.

Насчёт O(n^2) - давай я чуток разовью идею, как получить ... хотел сказать O(n), но, на самом деле, похоже, будет O(n log n).

Во-первых, сразу идея - по поводу двух наборов - длинного и короткого - ограничивай короткий не длиной в байтах/символах, а количеством append'ов.

Теперь продолжение: представь себе, что у тебя наборов не два, а много, но каждый строго ограничен количеством append'ов, скажем, не больше 10. Как только в одном наборе набирается 10, он тут же приклеивается к набору старшего разряда (увеличивая его количество append'ов на 1, нужно track'ать отдельно), а свой буфер сбрасывает в пустую строку и 0 append'ов. В этом случае расклад по количеству append'ов будет десятичное число :) где каждой цифре соответствует свой буфер.

Append одного сообщения - эквивалент прибавления 1 к этому числу (с переносами). Нетрудно увидеть, что амортизированная стоимость этого процесса это фиксированное количество сложений, O(1). Даже не логарифм, а константа, т.к. ты будешь делать append в младшем разряде с вероятностью 100%, в следующем с вероятностью 10%, в следующем с вероятностью 1%, и т.п., 1 + 1/10 + 1/100 + .. это очень убывающий ряд.

Квадратичные оверхеды будут только в рамках последнего разряда, они константны. Но с каждым разрядом тебе придётся пусть в 10 раз реже, но копировать в 10 раз более длинную строку, так что стоимость именно копирования будет N + N + N + ... это N log N.

Конечно, десятичная система просто для наглядности. Можно в двоичной. Чтобы держать миллион записей, тебе нужно будет 20 буферов. Можно потестить и выбрать такое основание, которое оптимально по перформансу. Не факт, что это будет 2, и не факт, что 10. Хотя, интуиция мне подсказывает, что правильный ответ ближе к 2, чем к 10.

[identity profile] ermouth.livejournal.com 2015-06-29 11:03 am (UTC)(link)
С разведением по вкладками – я так и собирался сначала делать (где-то тут коммент есть про это), а потом рекомбайнить логи умерших сессий при старте новой. Это стрёмно по нескольким причинам, хотя я уже не уверен что стрёмность перевешивает выгоды.

С количеством аппендов – я тоже так собирался делать. Но тогда нам надо прочитанный лог разобрать на отдельные записи – а это лишняя движуха.

Сейчас я эту лишнюю движуху делаю только в случае, если мне надо отрезать записи с головы (выход за 1Мб).

Писать в отдельный ключ счётчик(и) к-ва записей (чтобы не разбирать) я не хочу – это лишний рид-райт в ls, а транзакций у нас нет. То-есть так я просто повышу себе вероятность потери записей, если одноврменно начнут писать две вкладки.

Насчёт цепочки буферов кратного размера – да, я это понимаю. Просто для размера в 1Мб я посчитал, что двух достаточно.

[identity profile] morfizm.livejournal.com 2015-06-29 11:40 am (UTC)(link)
Счётик отдельно это чисто для наглядности рассуждений. Конечно, надо бороться за атомарность и таки вычитывать строку.

Кроме того, парсить её совершенно не обязательно, на практике вполне годится ограничение по длине (которая будет как прокси для количества записей). Счётчики были только для рассуждения о сложности.

Кстати, carryover это potential failure point. Лучше сначала добавить наверх, потом обнулить. (В случае fail in the middle, получишь дупликат записей, который можно разрезолвить как-нибудь потом, а не data loss.)

[identity profile] ermouth.livejournal.com 2015-06-29 01:46 pm (UTC)(link)
> Лучше сначала добавить наверх, потом обнулить
Да, я так и делаю. Там выше где-то тоже есть в комментах про это.

> по сути, нужна API-level функциональность для синхронизации параллельных потоков

Точнее, как минимум для откладывания старта соседних, но неактивных вкладок. Это я сделаю, оно несложно. Кстати первый подход к снарядку показал, что активная вкладка стартует побыстрее даже на глаз если подтормаживать старт остальных (в общем-то, ожидаемый результат).

Совсем синхронизировать потоки мне незачем. Pouch это делает для БД, куда-то выше я пока не готов лезть.

Про затирание сообщений – да, я готов с этим примириться, выше объяснял почему.

Плюс надо понимать, что вкладка с Клаудволлом – это уже кооперативная многозадачность, в ней много приложений может быть запущено одновременно. Открывать даже вторую незачем обычно.



[identity profile] morfizm.livejournal.com 2015-06-29 07:52 pm (UTC)(link)
Тогда такая идея: в каждой вкладке гони счётчик и логай его (но таким образом, чтобы потом было легко распарсить и вытащить значение счётчика). При загрузке лога, проверяй последовательность. Если есть пробелы - печатай "the log has been corrupted by other tabs and may have missing information, use at your own risk". Если будешь видеть это сообщение слишком часто, попробуй чуть больше уровней кэша (не два), т.к. это увеличит performance и уменьшит вероятность коллизий.

[identity profile] morfizm.livejournal.com 2015-06-29 07:54 pm (UTC)(link)
Логика проверки будет чуть сложнее, т.к. эту проверку надо будет делать независимо для каждого таба. Нужно логать id таба (если такого нет среди стандартных, генерируй по random'у или какой-нибудь unique id) и счётчик как пару, потом подсчитывать для каждого, и сообщение о corruption можно будет делать про испорченные табы, а не про весь лог.

[identity profile] morfizm.livejournal.com 2015-06-29 11:48 am (UTC)(link)
С разведением по вкладками – я так и собирался сначала делать (где-то тут коммент есть про это), а потом рекомбайнить логи умерших сессий при старте новой. Это стрёмно по нескольким причинам, хотя я уже не уверен что стрёмность перевешивает выгоды.

Если вкладки будут стартовать одновременно, тебе, по сути, нужна API-level функциональность для атомарного append'а или для синхронизации параллельных потоков (я правильно понимаю, что JS в разных табах исполняется параллельно?). Иначе могут быть неприятные race conditions.

localStorage.setItem("x", x + localStorage.getItem("x")); это, на самом деле, много операций: getItem, взять значение x, сложить, setItem. Interleave между тредами может быть совершенно произвольный. Например, легко представить себе, что ты будешь терять сообщения, потому что между getItem и setItem первого треда выполнится пара из второго (сообщение второго треда будет затёрто тормознутым setItem'мом первого).

[identity profile] morfizm.livejournal.com 2015-06-29 12:00 pm (UTC)(link)
Кстати,... вот тебе ещё идея с логгером. Тяжеловесная, но забавная. Пишешь сервер log-manager'а как standalone service, работающий на localhost, и запись в лог - это http реквест. Это может показаться супертяжеловесным решением, но на самом деле, вполне возможно, что будет таки быстро (localhost же), и избавит тебя от всех проблем - как с синхронизацией, так и с ограничениями local storage.

[identity profile] morfizm.livejournal.com 2015-06-29 12:02 pm (UTC)(link)
Задно - для режима логгинга с не очень большим трафиком - эта идея тривиальным образом превращается в "посылать логи в real-time на сервер". Например, просишь клиента - добавьте такой-то флаг в url, или нажмите на кнопку на странице "capture logs", и тут же тебе побежали логи, просто вместо localhost'а оно будет слать на твой сервак.

[identity profile] morfizm.livejournal.com 2015-06-29 12:03 pm (UTC)(link)
Ну и заодно у тебя уже готовый инструментарий для разных benchmark'ов и аналитики. Можно на сервер логать page load times и прочие полезные вещи, а потом не спеша анализировать, метрики вычислять :)

[identity profile] ermouth.livejournal.com 2015-06-29 12:08 pm (UTC)(link)
Всё что касается «посылать на сервер», «поднимать на локалхост» сразу отметается не глядя. Эта штука должна работать только в браузере, сразу после открытия вкладки и не имея инет-соединения.

Потому что сам CloudWall так работает, ему ничего не нужно кроме открытой вкладки. Он даже картинки не из сети берёт. Это noBackend и offline-first.

[identity profile] morfizm.livejournal.com 2015-06-29 12:35 pm (UTC)(link)
OK, чутка родственная идея. Можно ли создать независимого worker'а (или невидимый tab?) которого шарить между табами? В нём будет работать singleton log manager'а, и только он будет писать в localStorage.

[identity profile] ermouth.livejournal.com 2015-06-29 01:29 pm (UTC)(link)
Можно (называется SharedWorker), но
а) не во всех браузерах он есть (в IE и Сафари 5- вообще никак, в мобильных плохо работает),
б) в нём будет не поймать момент закрытия других вкладок
в) самое главное – он не видит localStorage.

[identity profile] morfizm.livejournal.com 2015-06-29 06:14 am (UTC)(link)
Не знаю, как посмотреть профайлинг, чтобы получить картинку, похожую на твою, но получается, что, скорее всего, оно как минимум сериализует строки и разрушает векторную структуру, которую JS поддерживает для append'ов внутри JS-объектов. А может быть, ещё и пишет на диск. Чтобы узнать, пишется ли оно на диск, можно посмотреть system level traces (под Windows Sysinternal tools дают), правда, там операционка может обманывать и не писать на диск если это правильным образом открытые temp-файлы.
Edited 2015-06-29 06:15 (UTC)

[identity profile] morfizm.livejournal.com 2015-06-28 01:22 am (UTC)(link)
Ещё немного полезного функционала для логгинг-клиента:

1. Нередко нужно каким-то образом форматировать сообщение лога. API должен быть такой, чтобы отложить форматирование и избежать работы на случай, если данный уровень логгинга выключен. Напр., питоновское: лучше делать log("Price = %0.2f", price), чем log("Price = %0.2f" % price). Во втором случае подстановка и форматирование делаются всегда, а в первом они делаются движком логгера и только при необходимости.

2. Полезные фичи - throttling. Например, логать в этой строке не чаще, чем раз в секунду, или по 1 разу на каждые 100 проходов, или по условию.

[identity profile] morfizm.livejournal.com 2015-06-28 02:31 am (UTC)(link)
"Чтобы прочитать рабочий лог, можно в консоли выполнить"

Резало слух, наконец, смог сформулировать, что именно.
Разделяй задачи logging'а и задачи introspection. Если ты будешь думать об этом как о разных задачах и писать для этого разные компоненты, тебе будет легче.

Задача "посмотреть накопившийся лог" входит в круг задач introspection (узнать состояние системы в данный момент), но это лишь одна из задач. В контексте интроспекции нативных предложений может быть полезно посмотреть stack dumps, посмотреть active RPC calls и т.п. Что-то аналогичное может быть полезно и для веба. Будет легче, если во время дизайна и имплементации таких штук не смешивать всё это с логгингом (логгинг это последовательная запись строк в файл).