Предтеча программистких логов - бортовой журнал.
Предтеча программистких логов - бортовой журнал.

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

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

Библиотека Log4ts вдохновлена идеями Log4J и обеспечивает логирование в программах, написанных на  TypeScript.
Далее в этой статье я расскажу о том, как её установить, использовать и конфигурировать.

А в конце я опишу коротенько другие мои библиотеки и функции, которые тоже не должны были бы существовать.

А нужна ли она вообще?

Современные IDE позволяют производить отладку (debug)  JavaScript- и TypeScript-приложений напрямую. Кроме того, в JavaScript и TypeScript доступен console с функциями error, warn, log, debug и т.д. Разве этого недостаточно? - возможно спросит читатель. 

Иногда - нет. Отладка очень искажает логику асинхронных приложений, каковыми и являются большинство приложений на  JavaScript и TypeScript. А использование console может быть достаточно при наличии большого статического контекста или в относительно простых приложениях. Но в приложениях со сложной логикой, а тем более в корпоративных приложениях, работающих как в браузере, так и на сервере, этого оказывается недостаточно.

Почему? - Наивное использование функций console в таких приложениях ставит разработчика перед дилеммой. Если разработчик использовал их широко в своем коде, он в конечном итоге получит столько строк в своей консоли, что будет чрезвычайно трудно с ними разобраться. Если использование функций консоли минимизировано, их может оказаться недостаточно для нахождения некоторых проблем. Особенно, если речь идёт об удалённой поддержке пользователей.

Решение этой проблемы давно найдено в других языках программирования, например, в Java, где есть Log4J. И решение очевидно: вы должны иметь возможность управлять выводом функций логирования во время исполнения программы (runtime).
Не найдя среди открытых библиотек такой, которая удовлетворяла бы этому требованию так, как я этого хотел, я и решил разработать свою.  

Обратите внимание!

Важно отметить: библиотека предназначена прежде всего для работы в браузере. В серверных приложениях, в тяжеловесных приложениях на Node.JS, лучше использовать другие библиотеки логирования.

Библиотека может быть полезна как разработчикам, так и работникам отдела поддержки.

Типичный Use Case при разработке. Разработчик ищет ошибку и у него “под подозрением” находится какой-либо класс или пакет. Он настраивает логирование так, чтобы выводились сообщения только из этих источников.  

Типичный Use Case в production: Пользователь сообщает о проблеме с приложением. Сотрудник отдела поддержки решает, что проблема в клиентской части (которая работает в браузере). Он просит пользователя включить логирование, открыть вкладку консолли в браузере и повторить операцию, при которой происходит ошибка, скопировать вывод из консоли и послать в поддержку. И опять - инструктируя пользователя о настройках логирования, он может посоветовать ему включить логирование только “подозрительного” класса или пакета, сделав тем самым выдачу логирования обозримой. 

Как это может работать, можно посмотреть в нашем демо-приложении. Откройте в браузере окно консоли, а в приложении зайдите настройки и в разделе “Выдача журнала” выберите режим логирования. Потом поработайте с поиском чисел.

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

Как её установить?

Если вы не новичок в веб-программировании, вы конечно знаете, что NPM (Node Package Manager) — это менеджер пакетов для JavaScript, который используется для установки, обновления и управления пакетами и библиотеками, а package.json — это файл конфигурации в веб- и простых Node.js проектах. Этот файл используется для управления зависимостями и автоматизации задач в проекте.

Поэтому для того, чтобы начать использовать Log4ts в вашем проекте, вам просто необходимо в ваш package.json добавить одну строчку:

 {

…

"dependencies": {

…	

	"@vsirotin/log4ts": “^3.0.0",

…

Если ваш NPM-проект правильно сконфигурирован, при следующей компиляции и построении вашего проекта библиотека будет скачана в ваш проект.

Как её использовать?

Конечно, логгер нужно перед использованием создать. В нашем случае мы должны сделать это, используя интерфейс ILogger:

logger = LoggerFactory.getLogger('MyClass');

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

DOMEN.PROJECT.CLASS(COMPONENT).

например:

logger = LoggerFactory.getLogger(‘com.example.my-project.MyClass');

Как настроить в процессе разработки проекта?

В отличие от аналогичных вызовов консоли, после создания наш логгер по умолчанию будет выводить только вызовы из error и warn, игнорируя остальные, например, log или debug.

Это решение кажется мне более прагматичным, если в вашем проекте много классов с встроенным логированием.

Но иногда, особенно на этапе разработки, вам нужно логировать не только информацию об ошибках и предупреждениях, но и другую информацию. Какую другую информацию? - Log4ts предоставляет следующие уровни логирования в зависимости от их серьезности:

0 или отрицательное число - выводится вся информация (ошибка, предупреждение, лог и отладка),

1 - только ошибка, предупреждение и лог,

2 - только ошибка и предупреждение (этот режим включен по умолчанию),

3 - только ошибка,

4 и более - все вызовы игнорируются.

Чтобы изменить уровень логирования на лету, нужно вызвать функцию класса setLogLevel(...). Например, чтобы включить вывод всех вызовов логирования:

 logger.setLogLevel(0);

Однако вам не обязательно помнить, какое значение параметра чему соответствует.
Для этого существуют удобные функции:

  • setErrorLevel - выводит только ошибки.

  • setDefaultLevel - выводит ошибки и предупреждения.

  • setAllLevels - выводит все сообщения.

  • setNoLogging - отключает все сообщения.

Эти функции управляют поведением отдельного логгера.
Статические функции фабрики LoggerFactory с похожими названиями (setErrorLevelByAllLoggers, setDefaultLevelByAllLoggers, setAllLevelsByAllLoggers, setNoLoggingByAllLoggers)  выполняют те же настройки, но сразу для всех созданных к этому моменту логгеров.

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

Например, приведенный ниже пример позволяет отключить логирование для всех логгеров с 'com.example.my-project.M' в ID:

LoggerFactory.setLogLevel('com.example.my-project.M', 4);

Звездочки в строке поиска могут быть в начале или в конце шаблона поиска.

Таблица ниже показывает различные виды искомых под-путей (перечисленных в первой строке таблицы) для трех идентификаторов, перечисленных в левой колонке таблицы.

sub-path

*b/c

a*

*b*

e/b/c

x/y/z

*

a/b/c

+

+

+

-

-

+

a/d/c

-

+

-

-

-

+

e/b/c

+

-

+

+

-

+

В некоторых особых случаях, таких как отладка проекта, где используется несколько подходов к логированию, вы можете захотеть уничтожить все логгеры из Log4ts до конца сеанса. Это можно сделать, вызвав

LoggerFactory.clearAllLoggers();

Как настроить во время применения (runtime, production)?

Чтобы управлять логгерами во время исполнения (например, для целей поддержки конечных пользователей), вам нужно иметь возможность вызывать функции LoggerFactory из какого-то вашего UI компонентов.

Вот демо-приложение, которое показывает пример такого UI компонента.

Практические советы

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

DOMAIN.PROJECT.CLASS(COMPONENT).

В текущей реализации отображение адреса точки логирования неинформативно, так как вывод в конечном итоге выполняется путем вызова функций console. 

Поэтому я рекомендую явно локализировать точку логирования, указывая имя логируемой функции.

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

Вот пример того, как создать логгер, учитывая соглашение об именах, указывая логируемую функцию и различая логгеры в пределах одной функции:

export class FileProcessor {

  private logger: ILogger = LoggerFactory.getLogger('eu.sirotin.lfp.FileProcessor');

  constructor(listPath?: string) {

    this.logger.log('constructor: FileProcessor created. List path:', listPath);

  }

    public processFile(filePath: string, outputDir: string): void {
    if (!this.analyzeLanguageFileSyntax(filePath)) {
      this.logger.error('In processFile. ERROR 100: Syntax analysis failed.');
      return;
    }
    ...

    if (this.listPath && !this.compareLanguageCodes()) {
      this.logger.error('In processFile. ERROR 200: Language code comparison failed.');
      return;
    }
...
  }

}

Так что, пользуйтесь на здоровье!

А я пока выполню своё обещание, данное в начале статьи.

Другие мои библиотеки и функции, которых не должно быть

Ошибки в работе с физическими единицами (когда метры складывают с футами или секунды с килограммами) приводили к, наверное, самым дорогим ошибкам в истории программирования. Тем более удивительно, что в составе популярных языках программирования нет средств работы с единицами системы СИ. 

Поскольку мне это однажды самому понадобилось, я разработал мультиплатформенную библиотеку  KotUniL.    

Как её использовать в Kotlin я описал тут:     1, 2, 3.

Её также (правда, без элегантности, присущей Kotlin) на Java и на JavaScript.

Я так и не смог убедить котлинцев включить эту библиотеку в стандарт языка или разработать что-то своё (ещё лучше).

В Java 8 в своё время добавили много хороших возможностей, но среди них не было класса Optional, который мне пришлось реализовать самому.

Точно также в Java 8 не хватало мульти-арных  функций размерности больше двух. Так что пришлось реализовывать их самому.

В Java мне с самого начала не хватало возможности красиво выводить на печать матрицы. В результате я написал класс MatrixFormatter

И уж совсем приватно

Мой сайт - https://www.sirotin.eu/

Кроме того, я пишу открытую электронную книгу “Мемуары кочевого программистаю Байки, были, думы”. Её текущий вариант можно найти здесь.

Я убеждён, что программирование - это материализаци я идей. Об этом я первый раз написал здесь. А вот уже несколько лет мы с группой единомышленников ведём группу в Телеграмме под названием “Материализация идей”. Если вам это интересно - подключайтесь.


Иллюстрация: Предтеча программистских логов - бортовой журнал. Совместное творение автора и ИИ.

Комментарии (30)


  1. barmaglot92
    29.01.2025 18:04

    Почему нет пометки что это перевод, автор явно не разработчик

    И как можно вот так написать было recetDefaults

    ведь это не опечатка


    1. visirok Автор
      29.01.2025 18:04

      Спасибо, что заметили очепятку в сигнатуре. Глаз замылился. Исправлю и залью новую версию.

      Буду признателен за другие замечания, если увидите.

      А почему Вы решили, что автор не разработчик?


      1. barmaglot92
        29.01.2025 18:04

        "При следующей компиляции и построении вашего проекта библиотека будет скачана в ваш проект."

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


        1. visirok Автор
          29.01.2025 18:04

          То что автор статьи и автор пакета одно лицо, я доказывать Вам не буду. Наверное, вы не дочитали статью до конца и не посмотрели репозиторий проекта.

          более того оно не правильное

          А что по сути неверно в этом предложении?


          1. barmaglot92
            29.01.2025 18:04

            Каком построении блин, сборки бандлером видимо имеется ввиду, ну ладно, давно ли у нас пакет при npm install скачивается после сборки проекта, как он вообще скомплиируется тсом и соберётся если скачивается только после этого.

            Ну я собственно дальше не читал уже да, зачем?

            Код смотреть мне зачем? Уже не хочется, такую фигню можно самому написать а после таких фраз не охото этот пакет юзать, вдруг он у меня скачается после сборки тем более


            1. barmaglot92
              29.01.2025 18:04

              Хорошо посмотрел немного

              log(...args: any[]) {

              if (this.logLevel <= 1) console.log(this.generateOutput(args));

              }

              console.log невозможно будет заменить на другой аутпут, он здесь захажкожен а должена инжектится сущность с определенным интерфейсом, dependency inversion принцип не используется, это не хорошо.

              Не должен класс логгера знать о console.log браузера это не его зона ответственности

              Более того это сайд эффект усложняющий тестирование и делающий функцию грязной

              То есть если я захочу писать лог в файл например в ноде, ваша библиотека увы не подойдёт. Либо захочу писать в дом ноду в браузере

              А можно было бы подкидывать например функцию с определенным интерфейсом извне и управлять потоком логов как угодно, а так же потенциально делать коробочные варианты, например как в winston

              И это я увидел сразу, через секунд 10

              Не нужно грубить мне в ответ, может быть я резко выражаюсь, зато по делу, это ведь лучше чем ничего? Если вас задело прошу прощения.

              PS за классы респект


              1. visirok Автор
                29.01.2025 18:04

                То есть если я захочу писать лог в файл например в ноде, ваша библиотека увы не подойдёт.

                Скорее всего да. Или вы будите разбираться сами с перенаправлением в файл на уровне stdout.

                Она не для этого. Она для высокофункциональных браузерных приложений, что и написано во первых строках.

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


                1. barmaglot92
                  29.01.2025 18:04

                  Тащить ниче не надо, консоль лог это вы как раз затащили лишнего, вашей либе он не нужен она другими вещами занимается, то что та среда где вы запускаете вашу либу имеет встроенный консоль лог это не значит что это не зависимость

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

                  Тот же file logger не обязан быть частью данной библиотеки, он может быть в другом пакете


                1. PrinceKorwin
                  29.01.2025 18:04

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

                  ...

                  и обеспечивает логирование в программах, написанных на  TypeScript.

                  ...

                  Она не для этого. Она для высокофункциональных браузерных приложений

                  Эм. На TS пишут только под браузер? а "любой новый язык" тоже только под браузеры?


                  1. visirok Автор
                    29.01.2025 18:04

                    Ваш комментарий я расцениваю как повод уточнить в документации к новой версии фокус, на что библиотека ориентирована и на что нет.

                     а "любой новый язык" тоже только под браузеры

                    С чего Вы это взяли? Я пишу о том, что в любом языке программирования (на чтобы он не был нацелен) должно быть встроенное логирование.


    1. visirok Автор
      29.01.2025 18:04

      ведь это не опечатка

      Ещё раз спасибо. Уже исправлено в тексте и в библиотеке.


  1. APXEOLOG
    29.01.2025 18:04

    Интересно, чем автора не устроил log4js, стабильная известная библиотека с теми же фичами и 4 миллионами инсталлов в неделю


    1. visirok Автор
      29.01.2025 18:04

      Авторы пакета сами пишут (цитата):

      Although it's got a similar name to the Java library log4j, thinking that it will behave the same way will only bring you sorrow and confusion.

      (Хотя ее название похоже на название библиотеки Java log4j, думать, что она будет вести себя так же, принесет вам лишь огорчения и замешательство.)


      1. cupraer
        29.01.2025 18:04

        Ну дык ваша-то еще гораздо дальше от прототипа.


        1. visirok Автор
          29.01.2025 18:04

          Тут Вы правы. Надо подчеркнуть легковесность библиотеки. То, что моя библиотека инспирирована Log4J не значит, что она повторяет её функциональность. В Log4J вбиты десятилетия труда программистов.


          1. cupraer
            29.01.2025 18:04

            Дело не в этом.

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

            Просто обернуть вывод в консоль — красивой оберткой — никакого смысла не имеет. Вот ребята из OpenTelemetry как-то пытаются двигаться в правильном направлении, хотя для JS этот путь очень тернист.


            1. visirok Автор
              29.01.2025 18:04

              Просто обернуть вывод в консоль — красивой оберткой — никакого смысла не имеет.

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

              Что я ясно вижу, о чем уже писал в другом комментарии - надо еще активнее подчеркнуть, что это библиотека для работы а браузере, а не в тяжелых серверных приложениях на Node.Js. В простых серверных предложениях - возможно.


              1. Kingas
                29.01.2025 18:04

                Так сбор телеметрии на клиенте это тоже нормально. Хотя вроде как чаще решается другими библиотеками ... Но тем не менее как простой вариант может быть и снимок лога.

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


                1. visirok Автор
                  29.01.2025 18:04

                  Почему это полезно,

                  Никто не оспаривает полезность сбора и анализа серверных логов. Это - огромная тема, большое количество открытых и платных инструментов.
                  Но эта библиотека предназначена совсем для другого.
                  Типичный Use Case в production: Пользователь сообщает о проблеме с приложением. Сотрудник отдела поддержки решает, что проблема в клиентской части (которая работает в браузере). Он просит пользователя включить логирование, открыть вкладку консолли в браузере и повторить операцию, при которой происходит ошибка, скопировать вывод из консоли и послать в поддержку.


                  1. PrinceKorwin
                    29.01.2025 18:04

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

                    Очень сложно. А если это мобильный браузер? Странное решение.

                    Почему нельзя сделать отправку логов на сервер автоматически для конкретно одной учетной записи (пользователя)?

                    Пользователь жалуется - ему в учетке ставят флаг "отладка". Дальше логи автоматически только с этой учетки будут накапливаться на сервере.


                    1. visirok Автор
                      29.01.2025 18:04

                      отправку логов на сервер автоматически

                      Сделать это можно. На это даже тикет в GitHub заведён. Но это не сделано, потому что я сначала делаю то, что нужно моим проектам, и если это может быть интересно и другим, предлагаю миру. А моим проектам этого пока не нужно.

                      Реализованный вариант лучше предложенного в следующих ситуациях, когда

                      • приложение высоко-автономное и, возможно, глючит как раз при отсутствии связи с сервером,

                      • поддержка осуществляется из разных пунктов, в том числе и средствами сообщества пользователей,

                      • в стране пользователя существуют строгие ограничения на передачу и хранение приватных данных (а если пользователь их сам отослал, сам за это и ответчик),

                      • пользователи хотели бы иметь понятный контроль над данными, которые они отсылаю куда-либо,

                      • провайдер услуги (использующий в своём коде эту библиотеку) не хочет тратиться на поддержку сервисного сервера.


  1. simplepersonru
    29.01.2025 18:04

    In processFile. ERROR 100:

    смутило что эта часть лога пишется руками. как тогда парсить тонны логов, если этот кусок пишет человек? а если в одном месте он напишет ERROR, а в другом месте случайно ЕRROR. не видите разницу? а символ E кирилл цей во втором случае :)

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

    [{{dd.mm.yyyy hh:mm:ss}} _ {{context}}]
    {{logLevel}}: {{message}}


    1. visirok Автор
      29.01.2025 18:04

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

      Именно для этого я советую сознательно различать точки логирования.


  1. corvair
    29.01.2025 18:04

    Суда на картинке явно идут курсом на столкновение.