ІнфраструктураБудь-який рівень

Логування

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

Рядок логу — це
Подія, а не проза
Вирішується коли
Пишеться код
Що жене вартість
Обсяг × зберігання

Рядок логу — це подія, а не речення

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

Рядок логу, написаний як речення, може прочитати людина — і майже ніхто більше. Той самий момент, записаний полями (назва події, ідентифікатор користувача, реліз, причина, ідентифікатор трасування, тривалість), можна рахувати, групувати, фільтрувати й зʼєднувати. Саме ця різниця вирішує, чи буде інцидент відповідним на питання: за тижні люди питають, яких користувачів це зачепило, як часто це ставалося і з якого релізу почалося, — і кожне з цих питань потребує поля, а не фрази. Grep знайде рядок, але не порахує за користувачами й не порівняє два релізи, тож рішення про те, що структурувати, ухвалюється тоді, коли пишеться код, а не коли починається інцидент. Лишайте повідомлення читабельним для людини, якщо хочете, — але факти кладіть у поля поруч.

Практична зміна невелика: віддавайте обʼєкт замість відформатованого рядка й кладіть кожен факт, який ви вставили в повідомлення, ще й у власне поле. Не вдалося списати кошти з картки користувача 8213 після 3 спроб стає подією з назвою payment_failed і полями user_id, attempts та reason поруч. Лишіть і читабельне повідомлення, якщо хочете, — нічого не втрачається, — але саме поля роблять запис таким, що рахується. І вживайте одну назву поля в усіх сервісах, бо user_id в одному сервісі й userId в іншому — це дві колонки для рушія запитів і один дуже дратівливий день для людини.

Логи, метрики й трасування відповідають на різні питання

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

СигналНа яке питання відповідаєЧого коштує і де спиняється
Метрики«Чи щось не так і відколи?» Числа, агреговані в часі: темпи, затримки, кількості.Дешеві й сталі на серію, тож тримайте їх довго. Вони нічого не скажуть про один конкретний запит.
Логи«Що саме сталося з цим одним обʼєктом?» Окремі події з непошкодженими деталями.Вартість росте з трафіком, тож зберігання — рішення бюджету. Погані для трендів: рахувати їх дорого.
Трасування«Куди пішов час — через усі сервіси?» Один запит як дерево відрізків із часом.Зазвичай із вибіркою, бо тримати всі траси не по кишені. Слабкі для рідкісного, якщо вибірка не розумна.

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

Поля, що роблять інцидент відповідним на питання

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

  • Ідентифікатор трасування

    Найцінніше поле на цій сторінці. Воно звʼязує кожен рядок логу з кожного сервісу з однією дією користувача. Створіть його на межі, передавайте всюди, логуйте завжди.

  • Назва події

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

  • Сервіс і реліз

    Який код це видав. Без релізу питання «чи спричинило це розгортання» не має відповіді, — а воно перше в більшості інцидентів.

  • Актор і тенант

    З ким це сталося — ідентифікатором, а не імʼям чи поштою. Потрібно, щоб відповісти про масштаб: один клієнт, один регіон чи всі.

  • Результат і причина

    Успіх чи провал і код причини із закритої множини. Саме лише «провал» змушує людину читати повідомлення; код причини можна згрупувати.

  • Тривалість

    Скільки тривала операція, у мілісекундах, числом. Записати нічого не коштує, а на питання «чи було повільно до поломки» відповідає безкоштовно.

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

Арифметика, що стає рахунком

Вартість логування — це одне множення, і зробити його один раз означає уникнути несподіванки. Візьміть запити на секунду, помножте на рядки логу на запит, на байти в рядку і на секунди в добі. Сервіс на 500 запитів за секунду, що пише 8 рядків по 600 байтів, дає близько 200 ГБ на добу — з одного сервісу. Помножте на кількість сервісів, потім на строк зберігання, потім на те, що платформа бере за прийом і за індексацію, — а це зазвичай два окремі рахунки. Більшість команд знайомиться з цим числом уперше в інвойсі.

Керують цим чотири важелі, і тягнути їх варто в такому порядку:

  • Перестаньте логувати те, що ніхто не запитує. Більшість обсягу — це кілька рядків на гарячому шляху, доданих під час налагодження роки тому й ніколи не прибраних.
  • Робіть вибірку успішних і тримайте всі провали. Нікому не потрібен кожен з мільйона однакових успіхів, і всім потрібен кожен провал.
  • Ешелонуйте зберігання: із пошуком — два тижні, дешевий архів — на строк відповідності. Майже кожен запит стосується останніх кількох днів.
  • Перетворіть найоб’ємніші події на метрики й перестаньте зберігати їх поодинці: лічильник відповідає на «скільки» за похибку від тієї вартості.

Як зробити інцидент відповідним від краю до краю

У системі з одного сервісу логи — це файл. У системі з кількох це купа файлів, що описують ту саму дію користувача з пʼяти боків і не зʼєднуються — якщо тільки щось не було пронесено між ними. Це щось — ідентифікатор трасування, створений у першій точці входу запиту у вашу систему й переданий далі в заголовку, а потім причеплений до кожного рядка логу, який цей запит породжує будь-де. Це кілька годин сантехніки — і це різниця між реконструкцією інциденту й припущеннями про нього.

Ще дві звички відділяють сховище логів від придатного до вжитку. Віддавайте однакові назви полів усюди: домовтеся про них один раз, покладіть у спільну бібліотеку й не дозволяйте кожному сервісу вигадувати власні. І користуйтеся однією відкритою конвенцією, а не форматом вендора: OpenTelemetry дає ідентифікатор трасування, ідентифікатор відрізка й набір стандартних назв атрибутів, а отже, збір і платформу можна замінити, не чіпаючи коду застосунку. Друге з часом важить більше, ніж здається зараз, бо платформа логів — той компонент, який команди найчастіше хочуть змінити й найрідше можуть.

Антипатерни, які варто назвати

  • Повідомлення, зібрані з шаблонів: кожен рядок — унікальний рядок, і без регулярного виразу нічого не порахувати.
  • Залогувати виняток і кинути його далі: один збій зʼявляється чотири рази на чотирьох рівнях стека.
  • Персональні дані в полях, що лежать девʼяносто днів на платформі зі ширшим списком доступу, ніж у базі, звідки вони взялися.
  • DEBUG, лишений увімкненим у проді після розслідування, — його виявляє наступного місяця фінансовий відділ.
  • Немає ідентифікатора трасування: інцидент між сервісами реконструюють, порівнюючи мітки часу й сподіваючись, що годинники згодні.
  • Алерти за пошуком у логах: це робота метрики, яку виконує рушій запитів у найгіршу можливу мить.

Коли це застосовувати

Застосовуйте, коли

  • Будь-що, що працює без нагляду: лог — єдина розповідь про те, що сталося, яка переживе процес.
  • Розслідування окремого випадку — це замовлення, цей користувач, цей запит, — де агрегат не допоможе.
  • Запис бізнес-подій, які згодом треба рахувати чи аудіювати, де назва події є одиницею аналізу.
  • Будь-яка система, де запиту торкається більш ніж один сервіс, — за умови, що між ними несеться ідентифікатор трасування.

Уникайте, коли

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

Було корисно?

Поділіться з тим, хто працює над тією ж задачею.