Автоматизация логирования входов в функции

    У нас в компании с незапамятных времен существует гласно-негласное правило о логировании входа в каждую функцию. И ладно бы это ограничивалось простой строчкой Logger.LogEntering() в их начале (хотя, наверное, тоже надоело бы), так еще и наш «замечательный» доморощенный логгер получать названия функций из которых он вызван не умеет, и как следствие, эта единственная строчка разрасталась до эпического Logger.Log(«Classname.FunctionName — Entering») or something like that.

    Неудивительно, что под воздействием недавних топиков о Mono.Cecil и родилась задача автоматизации процесса.



    Для того, чтобы продукт представлял не только внутрикорпоративную ценность, но и был общественно полезным, решено было поддержкой «внутреннего» логгера не ограничиваться, а также поддержать сторонние log4net и nlog (благо, и для внутренних нужд мы плавно переходим на log4net).

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

    Вступление затянулось, и пора переходить от слов к делу.
    Реализация — это msbuild-task, который запускается на этапе пост-компиляции и производит инъекцию вызовов Logger.Debug(«Entering») в начало каждой функции. Вернее, не каждой, а только помеченные атрибутом [Log]. Или во все функции классов, помеченные этим атрибутом. Ну или всё-таки в начало каждой функции модуля, если атрибут [Log] затесался в AssemblyInfo. Если же какие-то функции логировать ну никак не хочется, то их можно пометить атрибутом [NoLog].

    Чтобы эта msbuild-задача выполнялась, в .csproj\.vbproj необходимо добавить её вызов. Например, так:
    
    <Import Project="c:\path\to\Logging.NLog.targets" />
    

    Ну или чуть-чуть изменив, если вы предпочитаете log4net.

    Кроме этого, конечно, необходимо создать инстанс логгера, в который это всё и будет логироваться. LoggingMagic на данный момент подразумевает, что экземпляр логгера доступен через статичное поле какого-либо класса.
    
        //имя класса - Logger - имеет значение. Можно менять, но тогда надо его менять и в .targets-файле
        public class Logger
        {
            public static NLog.Logger Log = NLog.LogManager.GetLogger("NlogConsoleApp");
        }
    


    На этом и всё. Фильтрация результирующего потока логов возлагается целиком и полностью на логирующую библиотеку. LoggingMagic достаточно легко расширяется, имена атрибутов и класса с логгером меняются в параметрах msbuild task'a.

    Серьезным недостатком является отсутствие поддержки методики logger-per-class (которая пропагандируется, к примеру, nlog'ом), логирование возможно либо через статичное поле, либо через статичную функцию. В комментариях было бы любопытно узнать, каким способом логирования пользуетесь вы :) Логирование per-class возможно будет добавлено при настойчивых требованиях сообщества :)

    Итог получился урезанным подобием Log4Postsharp, но без привязки к проприетарным компонентам и без необходимости линковки с чем-либо сторонним и поставки этого при инсталяциях (библиотеки loggingmagic дальше Buildserver'a никуда не уходят).

    Исходные коды всего этого безобразия выложены на codeplex

    P.S. надеюсь, что к дискуссии о нужности-ненужности собственно логирования входов всё не сведется, потому что это чаще всего определяется предметной областью. В нашем конкретном случае это порой единственный способ сбора детальной информации при обнаружении проблем на клиентских инсталляциях.
    Поделиться публикацией
    Комментарии 19
      +1
      Дешево и сердито :)
        0
        Это бы знание, да года три назад…
          +7
          задачка из учебника по АОР
            0
            ну я тоже удивился, что нигде нет именно готовой реализации. Если я-таки свилосипедил, то киньте ссылкой на проект, буду благодарен.
            А говорят об этом везде, да. На уникальность идеи\реализации не претендую :)
              0
              Дело не в проекте, а в том, что такая задача традиционно решается с помощью АОП. А первая задача на АОП, которую чаще всего решают при изучении этой парадигмы — это сквозное логирование.
                +1
                ну да, я поэтому и плюсанул исходный комментарий, что полностью с ним согласен. Это классическая задача АОП.

                И именно поэтому я и не расписываю пути «как я решал эту сложнейшую проблему» :) Просто представил результат, потому что в готовом виде ничего подобного (кроме log4postsharp уже упоминавшегося) не нашел.
            0
            Удобно получилось.
            Интересно, существует ли подобный функционал для Java через аннотации?
              0
              Сам задал вопрос, сам и ответил. :)

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

              Очень часто при использовании Entity Framework, ну и вообще, бывают lazy-параметры. При вызове ToString все начинает подтягиваться с базы, и не факт что оно сработает вообще на всех этапах работы программы. Поэтому задача сводится к тому, чтобы логировать факт входа.
                0
                Вопрос немножко не в тему: А что, в EF пришли к лейзи? В той версии, которую я смотрел (вроде 1.0) все вроде было хардкорно-директово. Они еще помнится под это какую-то объяснительную базу подкладывали, доказывая преимущество над подходом NHibernate, типа лучший контроль, нет лишних обращений и.т.п.
                  0
                  да там изначально было, за исключением может каких древних версий. Да и свет клином не сошелся на EF, в NHibernate, других мэпперах такая же фигня.
                  0
                  логирование параметров происходит только при указании атрибута [Log(true)]. В случаях, когда вызов ToString является проблемой, параметры можно не логировать.

                  Лично я «по умолчанию» логирую-таки без параметров, а с ними — на ключевых стадиях процесса, в ключевых менеджер-классах.
                  0
                  Не понятно, только, зачем ))
                    +1
                    «благо, и для внутренних нужд мы плавно переходим на log4net», сколько печали в этой фразе =)
                      +2
                      а почему PostSharp не подходит?
                        0
                        требует линковки готового продукта со своими библиотеками, что делает нас зависимыми от него.
                        Проприетарность, и возможная платность в будущем.
                        Некоторые негативные отзывы о производительности — но это на личном примере не проверялось.

                        Это вкупе, ну и относительная простота собственной реализации и подтолкнули к Mono.Cecil.
                          0
                          что значит «возможная платность в будущем»? Он вроде как уже платный, если не Community Edition.
                            0
                            коммьюнити я и имел ввиду. Для данных целей её вроде бы достаточно.
                            Но и коммьюнити в один прекрасный момент может стать платной
                              0
                              старые версии платными вдруг не станут.

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

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