Logy — логгер с человеческим лицом

    Некоторое время назад мне пришла в голову идея сделать логирование в Java более дружелюбным, простым и в тоже время достаточно гибким в настройке. Такие требования справедливы пожалуй, в средних и малых проекта, где можно обойтись без громоздкого log4j. Буквально за неделю, идея переросла в простенькую Java библиотеку с ни менее простым названием — logy.

    Использование:
    import static logy.Logy.*;
    
    public class Test {
      public void test() {
        String s[] = {"a", "b"};
        warn("Can't find", quote(upper("c")), "in", group(quote(upper(scalar(s)))));
      }
    }
    

    Вывод:
    29.06.2012 1:19:25 Test.test [WARN] :: Can't find "C" in ["A", "B"]

    Как по мне, выглядит очень читабельно, благодаря синтаксическому сахару, DSL-like API и динамическому определению параметров логирования в момент вызова (читай без дополнительных полей public static final Logger logger = ... в классе).

    О названии


    Слово “logy” — переводится c английского как “тупой”. С одной стороны, название воспринимается как уменьшительно-ласкательное от “log”, с другой — намекает на узколобость (в хорошем смысле) и простоту библиотеки.

    Особенности/возможности


    • занимает 17 кб в скомпилированном JAR файле
    • без зависимостей
    • DSL-like API: quote, group, upper, export, …
    • динамическое определение параметров логированя (без явной инициализации логгера)
    • логирование в файл/stdout/stderr
    • поддержка маски (“*”) в конфигурационных файлах
    • конфигурация в диапазоне “глобально”...“метод”

    API


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

    import static logy.Logy.*;
    

    5 уровней сообщений, в порядке приоритета: “debug”, “fine”, “info”, “warn”, “error”, представлены одноименными методами.

    Пример:
    error("Files", quote("file1", "file2"), "not found!").
    

    Вывод:
    Files "file1" "file2" not found!

    Экспорт результатов преобразований в строку с помощью метода “export”.

    Пример:
    String s = export("The", quote(upper("message")), "can’t be delivered!");
    System.out.println(s);
    

    Вывод:
    The "MESSAGE" can't be delivered!

    Оборачивание параметров в кавычки методом “quote”.

    Пример:
    int arr[] = {1, 2, 3, 4};
    info("Quotted values:", quote(scalar(arr), "a", "b"));
    

    Вывод:
    Quotted values: "1" "2" "3" "4" "a" "b"

    Группировка результатов с помощью метода “group”.

    String s[]= {"a", "b", "c"};
    info("Grouped values:", group(scalar(s), 1, "d"));
    

    Вывод:
    Grouped values: [a, b, c, 1, d]

    Изменение регистра параметров методами “upper” и “lower”.

    Пример:
    String s[]= {"a", "b", "c"};
    info("Uppered values:", upper(scalar(s)));
    info("Lowered values:", lower("A", "B", "C"));
    

    Вывод:
    Uppered values: A B C
    Lowered values: a b c

    Уточнение варианта использования параметра с помощью методов “scalar” и “array”.

    Пример:
    int arr[] = {1, 2, 3, 4};
    info("Quotted array:", quote(array(arr)));
    info("Quotted values:", quote(scalar(arr)));
    

    Вывод:
    Quotted array: "[1, 2, 3, 4]"
    Quotted values: "1" "2" "3" "4"

    Конфигурация


    Конфигурационные файлы поддерживают следующие определения:
    • комментарии, начинающиеся с “#”
    • тройки вида “VARIABLE@SCOPE=VALUE”

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

    format@=%date% %time %class% [%level%] %%%
    

    Где доступны следующие переменные контекста:
    • %scope% — полный путь до метода, из которого вызван логгер
    • %class% — полный путь до класса, из метода которого вызван логгер
    • %method% — название метода, из которого вызван логгер
    • %date% — дата, в момент вызова логгера, в формате текущей локали
    • %time% — время в момент вызова логгера, в формате текущей локали
    • %level% — уровень сообщения логгера
    • %%% — сообщение логгера

    Основной особенностью при задании области использования переменной является использование маски “*” в пути.

    Рассмотрим простой пример. Пусть требуется логировать все сообщения в файл из классов-тестов и логировать в консоль из остальных классов только ошибки. Для этого создадим файл “properties.logy” в корне проекта, со следующим содержанием:

    # глобальные настройки
    level@=error
    logger@=stream:err
    
    # для классов заканчивающихся на “Test” и методов начинающихся с “test”
    level@*Test.test*=debug
    logger@*Test.test*=file:test.log
    


    Планы


    Я хочу, чтобы logy всегда оставался минималистичным инструментом, решающим узкий набор задач. Поэтому, в проект наверняка не будут масштабно вливаться новые фичи, например: поддержка логирования в БД, в сеть, и т.д. Целевая аудитория проекта — малые и средние проекты, где такие возможности скорее всего не понадобятся.

    Единственное, что действительно не хватает logy сейчас, это а) поддержка нескольких логгеров для одной области использования (сейчас, к сожалению только один); б) корректная работа в многопоточной среде (по правде сказать, не проверял, но подозреваю, что будут проблемы, особенно при логировании в файл).

    Эти изменения я планирую сделать в ближайшее время для версии 0.2.0.

    PS


    Конечно, я рассказал не обо всех возможностях библиотеки. Более подробную документацию (в процессе написания) можно будет найти на GitHub странице проекта.

    Буду крайне рад фидбекам, форкам, пул-реквестам и баг репортам.

    Что касается аналогов. Я не зря оставил этот вопрос напоследок. Честно признаться, я не особо старался найти похожую функционалом и возможностями библиотеку. Наверное потому, что хотел написать что-то полностью свое, или, мне просто надоело искать для себя отмазки вида “да уже придумали такое, пойду поем” перед каждой новой идей в моей голове, жаждущей реализации. Кроме того известно, что придумать что-то действительно новое почти невозможно, возможно — сделать это лучше чем другие.
    Поделиться публикацией

    Комментарии 42

      –1
      А ничего, да, уютненько! Пожалуй возьму побаловаться.
      Спасибо!
        +1
        Пожалуйста! :) Отправляйте баг-репорты, уверен я протестировал не все.
        0
        А с андроидовским LogCat оно работаеть умеет? Если нет — реквестирую сборку библиотеки для андроид-программистов.
          0
          Нет нет, это только для Java SE/EE. На счет Андроида надо подумать. Я никогда не писал под него. Можете форкнуть и написать врапперы :)
            0
            Там, насколько я вижу, все используемые вами классы вполне себе будут. Единственное — вместо вывода в файл или stream надо будет докрутить вариант вывода в logcat — можно по аналогии с «stream:err», например, сделать «logcat:something».
              0
              Дада. Можно будет просто создать новый класс расширяющий logy.logger.Logger
          +3
          Эм, я, конечно, все понимаю, но, по-моему, import static logy.Logy.* как-то совсем жестоко замусоривает symbol space. Получить такие слова, как:

          • join
          • group
          • fetch
          • quote
          • export

          и огрести очень возможные пересечения с локальными символами — по-моему — не самая хорошая идея.

          Ну и да, как-то совсем странно видеть публикацию библиотеки совсем без javadoc.
            0
            javadoc пишется и будет в ближайшее время на ровне с документацией.

            По поводу слов. Я думал над этим, но соблазн использовать красивые и понятные названия методов был настолько большим, что я не смог устоять :) Честно говоря не могу сказать на сколько часто я использовал данные названия для переменных/методов. «quote» точно ни разу не писал.
            0
            Автор, просто интересно, вы слышали про slf4j? Если да, то не очень понятны предпосылки создания вашей библиотеки. Какие у нее преимущества?
              0
              Слышал. Последний абзац поста как раз отвечает на вас вопрос.
                +1
                В вашем подходе с DSL (который дает весьма сомнительные плюшки, кстати) есть один большой минус: куча вызовов instanceof и созданий массивов даже в тех случаях, когда сообщение не будет выводиться в лог
                  –1
                  Нене. Ну про плюшки это вы зря. Каждому свое, мне например нравится писать так, как было показано в примерах. Насчет масивов и instanceof тут да — ничего не поделаешь, хотя можно кеширование какое-нибудь прикрутить. Ну это если тормозить будет. Пока ни кто не жаловался — значит летает все :)
                    0
                    Пока никто и не пользовался, поэтому жалоб и нет :)
                    Насчет массивов можно решить по другому:

                    logger.debug("Can't find {quote,upper} in {quote,upper}", "c", s);
                    
                      –1
                      Хм. nice try :) Такой подход где-то используется? Или только что придумали?
                        0
                        Ну это импровизация на тему расширения возможностей синтаксиса slf4j :)
                      0
                      У вас плюшек то и нет никаких, только изменение регистра и закавычивание.
                        –1
                        Ну как это и есть плюшки.
                0
                я просто оставлю эту ссылку:
                тыц
                чтобы было понятно, почему в slf4j сделано так, как сделано.
                0
                Можно по подробнее о «динамическое определение параметров логированя (без явной инициализации логгера)». Есть подозрения что будут проблемы с производительностью.
                  0
                  Дада. Предыдущий комментарий как раз об этом.

                  Я знаю, что получать стек трейсы удовольствие не из дешевых, но я сознательно сделал именно так. Целевой функцией у меня было — удобство использования. К тому-же преждевременная оптимизация блаблабла… Если действительно будут с этим проблемы, я обещаю подумать над другим способом получения параметров.
                    0
                    Странно вы рассуждаете. Т.е. вы сначала предоставляете продукт а уже потом думаете о его производительности?
                    Удобство использования должно включать в себя оптимальную работу. Допустим я начал использовать вашу библиотеку а потом понимаю что он ужасно тупит при использовании. Что мне предложите делать тогда?
                      +1
                      Эм… зафайлить баг и привести цифорки о том на сколько оно тормозит.
                        0
                        Этим разве не вы должны заниматься перед представлением вашей библиотеки? Таким образом тестировать ваш продукт будут другие разработчики?
                          0
                          Я проводил функциональное тестирование. Performance тестов у меня нет. Я и не планировал их делать. Я не хотел сделать «быстро», я хотел «просто сделать». А потом, если возникнут проблемы у меня или у кого-то еще уже можно было бы заняться оптимизацией. Кажется так делают большинство людей.
                            0
                            Если я правильно понял использовать эту библиотеку я должен на собственный страх и риск? «большинство людей» какое большинство? Логирование приложения одна из самых сложных задач в плане производительности и оптимизации, а вы даже не планировали performance тестов. Так «большинство хороших разработчиков» не делают.
                              +1
                              зануда
                                0
                                Есть немного )
                  +1
                  Одна из основных проблем в системах логирования это скорость. Необходимо, чтобы логирование как можно меньше замедляло программу.
                  У Вас информация о классе и методе берется программно из стека вызовов, что обычно очень неоптимально, и всякие библиотеки типа log4j не рекомендуют вообще пользоваться такой возможностью в критичных к скорости программах.
                  Ну и по мелочи. Ваши вспомогательные функции принимаютс Object… а значит на каждый вызов оборачивают свои аргументы в массив, что тоже не прибавляет скорости.
                    0
                    github.com/vkostyukov/logy/blob/master/src/main/java/logy/logger/FileLogger.java#L33

                    Открывать и закрывать файл на каждую запись в него? Не думаю, что это хорошая идея…
                      0
                      Дада. Сначала был постоянный дескриптор, я просто решил упростить себе жизнь. Когда будут допиливать поддержку многопоточной среды, обязательно взгляну на это.
                        0
                        Нормальная. Когда пользователи захотят 1 лог-файл для 4 серверов, придется добавлять дополнительно блокировку при открытии файла.
                        +1
                        group(quote(upper(scalar(s)))) — не могу согласиться что это очень читаемо ))))
                          0
                          Возможно пример не очень удачный, можно было просто написать «s» — как массив.
                        • НЛО прилетело и опубликовало эту надпись здесь
                            +2
                            Была где-то статья про историю логгеров в Java. Если кратко, каждый хотел сделать лучше и потому получилось много библиотек.

                            У нас тоже есть библиотека для более дружелюбного к программисту логгирования. Но, в отличие от вам, мы не стали переписывать весь логгер, вместо этого написали один класс с удобными функциями (в т.ч. и варианты с varargs), который затем вызывает ванильные методы log4j.

                            Советую и вам подумать в этом направлении…
                            –1
                            Конфигурировать как? Очень похоже на наколенную студенческую поделку с изобретением велосипеда. Если кому и надо будет такое, то это делается за 2 минуты путем враппинга slf4j/log4j/logback или что у вас там используется
                            0
                            Да уж наверное. И даже не поленилсчя посмотреть код на гитхабе, который оставляет желать лучшего
                              0
                              log4j? прошлый век, все давно используют slf4j + logback

                              String s[] = {"a", "b"};
                              log.warn("Can't find '{}' in {}", "c", s);
                              

                              выведет
                              22:28:19.443 [main] WARN  Test1 - Can't find 'c' in [a, b]
                              


                              Приводить к UPPER CASE или еще как-то менять исходные данные противоречит самой сути логгирования, ведь логи ведутся не для красоты, а чтобы потом было проще понять, в чем проблема. А вдруг проблема именно в регистре? А в логах все строки будут в одном и том же регистре.

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

                              Самое читаемое