Как стать автором
Обновить

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

В Groovy AOP нету? По идее гораздо гибче и можно не только логгирование победить.

Привет! Спасибо за комментарий.


Spring AOP точно есть, ApsectJ тоже (насколько я знаю, но не пробовал сам).


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


Так что AST наиболее беспроблемный вариант. А вот насчёт гибкости — разве в общем случае AOP более гибкий, чем AST API?


Как бы то ни было, мы позаботились об этом в "Карбюраторе" (абстрактный AST API, используемый в "Чёрном ящике") — он гибко настраивается (даже есть экстернализированная конфигурация), и предоставляет доступ ко всем нужным мета-данным этапа компиляции на этапе исполнения.


Но интересно узнать Ваше мнение насчёт этого.

В общем случае, AST API (не знаю насколько он гибкий для Groovy) конечно предоставляет полный контроль над чем угодно.

Но я писал конкретно про «Чёрный ящик» — у вас предопределённые правила и часто может получиться что они имеют либо слишком широкий охват либо наоборот. Используя AOP вы сами контролируете что и как добавлять учитывая специфику своего проекта.

Ну и плюс для AspectJ реальзованно множество аспектов, которые вы можете переиспользовать у себя.

Согласен, конечно. Это лишь вопрос трудозатрат — использовать готовое решение, или настраивать в конкретных проектах.
На всякий случай повторюсь, "Чёрный ящик" не требует от программиста, использующего его, каких-либо трудозатрат, кроме добавления аннотации @BlackBox к методам (или даже к классам целиком). Такая себе прозрачность использования получается — и никому не придётся разбираться как реализовано это через AOP (не все в нём хорошо разбираются).


Но ещё раз спасибо — Вы подтолкнули подумать — можно ли сделать Java реализацию "Чёрного ящика" с помощью AOP (как библиотеку). Пока рассматривался вариант аннотации и CGLIB.

Я не совсем понял, что вы пишете в логи, но вы не хотите писать их в более структурированном и наглядном виде? Например, в формате tree:


error
    time \2019-02-21 15:54:11:486
    class \io.infinite.pigeon.threads.SenderThread
    method \SELF_TEST_RETRY_OUTPUT_RETRY_SENDER_1_RETRY
    guid \a64aceed-8e4c-4d4b-8030-0c23bc3b3f0d
    type \java.lang.NullPointerException
    stack
        \at io.infinite.pigeon.threads.SenderThread.sendMessage(SenderThread.groovy:98)
        \at io.infinite.pigeon.threads.SenderThread.run(SenderThread.groovy:44)
    calls
        \sendMessage(60,5,100,6)
        \run(40,5,50,6)

Вместо этого нечитаемого месива:


2019-02-21 15:54:11:486|error|SELF_TEST_RETRY_OUTPUT_RETRY_SENDER_1_RETRY|io.infinite.pigeon.threads.SenderThread|EXCEPTION (first occurrence):
2019-02-21 15:54:11:492|error|SELF_TEST_RETRY_OUTPUT_RETRY_SENDER_1_RETRY|io.infinite.pigeon.threads.SenderThread|a64aceed-8e4c-4d4b-8030-0c23bc3b3f0d
2019-02-21 15:54:11:493|error|SELF_TEST_RETRY_OUTPUT_RETRY_SENDER_1_RETRY|io.infinite.pigeon.threads.SenderThread|java.lang.NullPointerException
    at io.infinite.pigeon.threads.SenderThread.sendMessage(SenderThread.groovy:98)
    at io.infinite.pigeon.threads.SenderThread.run(SenderThread.groovy:44)

2019-02-21 15:54:11:493|error|SELF_TEST_RETRY_OUTPUT_RETRY_SENDER_1_RETRY|io.infinite.pigeon.threads.SenderThread|METHOD EXCEPTION: sendMessage.io.infinite.pigeon.threads.SenderThread(60,5,100,6)
2019-02-21 15:54:11:493|error|SELF_TEST_RETRY_OUTPUT_RETRY_SENDER_1_RETRY|io.infinite.pigeon.threads.SenderThread|EXCEPTION (additional occurrence):
2019-02-21 15:54:11:494|error|SELF_TEST_RETRY_OUTPUT_RETRY_SENDER_1_RETRY|io.infinite.pigeon.threads.SenderThread|a64aceed-8e4c-4d4b-8030-0c23bc3b3f0d
2019-02-21 15:54:11:494|error|SELF_TEST_RETRY_OUTPUT_RETRY_SENDER_1_RETRY|io.infinite.pigeon.threads.SenderThread|METHOD EXCEPTION: run.io.infinite.pigeon.threads.SenderThread(40,5,50,6)

Привет!


Изначально использовали иерархический формат — XML (с XSD на соответствующим классовой модели Groovy AST):
image


  • Исключение
    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <rootAstNode xsi:type="MethodNode" methodName="foo" className="io.infinite.blackbox.SandBox"
             startDateTime="2018-10-22T16:17:40.843+04:00" lineNumber="29" columnNumber="5" lastLineNumber="35"
             lastColumnNumber="6" xmlns="https://i-t.io/blackbox/groovy/2_x_x/BlackBox.xsd"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <astNodeList/>
    <argumentList>
        <argument argumentClassName="java.lang.String" argumentName="bar">
            <argumentValue>foobar</argumentValue>
        </argument>
    </argumentList>
    <exception exceptionDateTime="2018-10-22T16:17:40.913+04:00">
        <exceptionStackTrace>java.lang.Exception: Bar can not be foobar
            at io.infinite.blackbox.SandBox$_foo_closure1$_closure3.doCall(SandBox.groovy:32)
            at io.infinite.blackbox.SandBox$_foo_closure1$_closure3.doCall(SandBox.groovy)
            at io.infinite.blackbox.BlackBoxEngine.expressionEvaluation(BlackBoxEngine.groovy:70)
            at io.infinite.blackbox.BlackBoxEngine$expressionEvaluation$3.call(Unknown Source)
            at io.infinite.blackbox.SandBox$_foo_closure1.doCall(SandBox.groovy:32)
            at io.infinite.blackbox.SandBox$_foo_closure1.doCall(SandBox.groovy)
            at io.infinite.blackbox.BlackBoxEngine.executeMethod(BlackBoxEngine.groovy:175)
            at io.infinite.blackbox.BlackBoxEngine$executeMethod$1.call(Unknown Source)
            at io.infinite.blackbox.SandBox.foo(SandBox.groovy)
            at io.infinite.blackbox.SandBox.run(SandBox.groovy:40)
        </exceptionStackTrace>
    </exception>
    </rootAstNode>
  • Нормальное выполнение
    <rootAstNode startDateTime="2018-10-22T15:59:49.120+04:00" xmlns="https://i-t.io/logging/groovy/2_x_x/BlackBox.xsd"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <astNodeList>
        <astNode xsi:type="MethodNode" methodName="foo" className="io.infinite.blackbox.SandBox"
                 startDateTime="2018-10-22T15:59:49.156+04:00" lineNumber="29" columnNumber="5" lastLineNumber="32"
                 lastColumnNumber="6">
            <argumentList>
                <argument argumentClassName="java.lang.String" argumentName="bar">
                    <argumentValue>z</argumentValue>
                </argument>
            </argumentList>
            <astNodeList>
                <astNode xsi:type="Statement" statementClassName="ReturnStatement"
                         startDateTime="2018-10-22T15:59:49.217+04:00" sourceNodeName="BlockStatement:statements"
                         lineNumber="31" columnNumber="9" lastLineNumber="31" lastColumnNumber="19">
                    <restoredScriptCode>
                    <astNodeList>
                    </astNodeList>
                </astNode>
                <astNode xsi:type="Expression" expressionClassName="VariableExpression"
                         startDateTime="2018-10-22T15:59:49.226+04:00" sourceNodeName="ReturnStatement:expression"
                         lineNumber="31" columnNumber="16" lastLineNumber="31" lastColumnNumber="19">
                    <restoredScriptCode>bar</restoredScriptCode>
                    <astNodeList>
                    </astNodeList>
                    <expressionValue className="java.lang.String">
                        <value>z</value>
                    </expressionValue>
                </astNode>
            </astNodeList>
            <methodResult className="java.lang.String">
                <value>z</value>
            </methodResult>
        </astNode>
    </astNodeList>
    </rootAstNode>

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


Как показывает практика, в нём не так важна красота — чаще всего знаешь, что именно искать — и ищешь с помощью ctrl-f. А вот минимализм — помогает.


Так что текущая реализация — базовая. Имеет смысл вариации делать именно как отдельные реализации; Базовый API — карбюратор. Если есть желание — можете поэкспериментировать с ним.


А если мыслить масштабно — то это вообще дело не программы, а Kibana/Splunk/Grafana. В этом случае вообще не так критичен формат вывода из программы. Главное чтобы он был единообразен.


Это если в общем виде — какие строки и сообщения логировать.


Если говорить про частные случаи форматирования отдельных сообщений (например method exception — как вы написали выше) — это должно решаться на уровне настроек логгера.
Мы рекомендуем использовать логгер Бобину.
Вот как бы выглядили настройки Бобины для Вашего примера:


Bobbin.yml:


destinations:
  - name: io.infinite.bobbin.config.ConsoleDestinationConfig
    levels: [error]
    formatThrowable: ("error\n    time $dateTime\n    class $className\n    type ${throwable.class.name}\n stack ${exceptionUtils.stacktrace(throwable)}")

Это если без реализации Tree. А так, надо бы реализовать на Java его, отличный формат! Спасибо за наводку.

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


Лучше всего попробуйте на практике Чёрный ящик.

извиняюсь за 3й ответ Вам подряд. Тему затронули обширную сразу.
Так вот, забыл сказать самое важное: если Вы хотели бы использовать Чёрный ящик у себя в проектах, но именно с форматом Tree — пожалуйста дайте знать. Мы подготовим реализацию BlackBoxTree. Это не займёт много времени.


Подход именно такой: под каждый формат — отдельная реализация.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации