Powrót do strony głównej

Logger: projektowanie dla diagnostyki w produkcji

Artykuł omawia kluczowe aspekty projektu loggera dla produkcji: wiązanie z zadaniami poprzez kanały, deduplikacja, podsystemy i dynamiczne zarządzanie. Podane są przykłady kodu dla logme i porównanie ze spdlog, Quill. Skupienie na diagnostyce bez uszczerbku wydajności.

Efektywny projekt loggera dla systemów wysokonapiętych
Advertisement 728x90

Projektowanie loggera: skupienie na diagnostyce i kontroli w produkcji

W systemach produkcyjnych loggera ocenia się nie po szybkości API, ale po zdolności do szybkiego diagnozowania incydentów bez dodatkowego obciążenia. Logowanie niesie ze sobą overhead, dlatego w normalnym trybie je minimalizujemy. Jednak w przypadku problemów brak szczegółów lub ich chaotyczność uniemożliwia analizę. Zadaniem loggera jest zapewnienie diagnostyki bez uszczerbku dla wydajności.

Problem pojawia się, gdy struktura loga odzwierciedla kod, a nie procesy biznesowe. W systemach wielowątkowych komunikaty z różnych zadań mieszają się, utrudniając śledzenie konkretnego żądania.

Przywiązanie kontekstu do zadania

Rozwiązanie — kojarzyć logi z zadaniem, a nie z miejscem wywołania. Przekazywanie kontekstu przez parametry zanieczyszcza sygnatury. Lepiej przywiązać go do strumienia wykonania.

Google AdInline article slot

W logme to jest realizowane przez thread channel:

auto requestCh = Logme::Instance->CreateChannel(Logme::ID("req-42"));
LogmeThreadChannel(requestCh);
HandleRequest();

Wewnętrzny kod pisze w kanał automatycznie:

void ParseHeaders() {
    LogmeW("invalid header");
}

Log jest teraz grupowany według operacji. W spdlog i Quill kontekst jest organizowany wokół loggera, co jest prostsze, ale mniej odpowiednie dla zadań.

Google AdInline article slot

Inny problem to szum z powtarzających się wiadomości. Błędy retry wypełniają log identycznymi liniami. Ograniczenie na poziomie zdarzenia rozwiązuje to:

LogmeW(LOGME_ONCE4THIS, "disk is full");

Wiadomość jest wyświetlana raz dla instancji klasy, zachowując czytelność.

Elastyczne rutowanie wiadomości

Globalna konfiguracja jest niewystarczająca. W HTTP/2 trzeba rozróżniać logi połączeń i żądań. Kanał żądania jest powiązany z kanałem protokołu, ale szczegóły żądania nie powinny zaśmiecać ogólnego loga.

Google AdInline article slot
Logme::Override ovr;
ovr.Add.DisableLink = true;
LogmeI(ovr, "request-specific detail");

To izoluje wiadomość w lokalnym kanale. Ogólne wydarzenia są widoczne wszędzie, prywatne — tylko lokalnie.

Podsystemy dla precyzyjnej szczegółowości

Jeden wymiar (kanał) to za mało. W zadaniu są podsystemy: cache, sieć, parsowanie. Logujemy wybiórczo:

LOGME_SUBSYSTEM(cacheSid, "cache");
LOGME_SUBSYSTEM(parserSid, "parser");

LogmeD(cacheSid, "cache miss");
LogmeD(parserSid, "header parsed");

W runtime włącza się tylko potrzebny podsystem, na przykład cache. Pozostałe milczą, log pozostaje kompaktowy.

Integracja z bibliotekami

Biblioteki stron trzecich mają własne logowanie. Przechwytujemy i integrujemy:

void FfmpegLogCallback(void* ptr, int level, const char* fmt, va_list args) {
    char buffer[2048];
    vsnprintf(buffer, sizeof(buffer), fmt, args);

    Logme::Override ovr;
    ovr.Add.DisableLink = true;

    LogmeW(ovr, "[ffmpeg] %s", buffer);
}

Wiadomości podlegają tym samym regułom: kanały, podsystemy, de-duplikacja.

Wsparcie formatów ułatwia migrację:

LogmeI("value=%i", value);
fLogmeI("value={}", value);
LogmeI() << "value=" << value;

logme łączy fmt i stream-podejście, w przeciwieństwie do spdlog (fmt) i Quill (asynchroniczny fmt).

Zarządzanie w produkcji bez restartu

W ciągłych systemach logi minimalizuje się w normalnym trybie. Przy incydencie:

  • Podejrzenie cache — włącza się podsystem cache.
  • Izoluje zadanie przez channel.
  • Ogranicza powtórzenia.

Zarządzanie dynamiczne przez logmectl: poziomy, podsystemy, bez restartu.

Po naprawie wyłącza się szczegółowość. Bez tego — globalne włączenie logów prowadzi do przeciążenia.

spdlog skupia się na szybkości, Quill — na asynchroniczności, logme — na kontroli strumienia wiadomości przy zachowaniu wydajności.

Co jest ważne:

  • Logger powinien grupować według zadań przez kanały, a nie przez loggera.
  • De-duplikacja na poziomie zdarzeń zapobiega szumowi.
  • Podsystemy pozwalają na punktową szczegółowość w runtime.
  • Integracja bibliotek unifikuje wyjście.
  • Dynamiczne zarządzanie bez restartu jest kluczowe dla produkcji.

— Editorial Team

Advertisement 728x90

Czytaj dalej