C # условная регистрация / трассировка - PullRequest
8 голосов
/ 17 февраля 2009

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

В C ++ вы можете использовать препроцессор для определения макросов, которые будут препятствовать выполнению кода вообще так:

#define VLOG(level,expr) if (level >= g_log.verbosity) { g_log.output << expr; }

Используется так:

VLOG(5,"Expensive function call returns " << ExpensiveFunctionCall());

Как ты это делаешь в C #?

Я прочитал документы Microsoft, объясняющие возможности трассировки и отладки здесь , и они утверждают, что использование #undef DEBUG и #undef TRACE удаляет весь код трассировки и отладки из созданного исполняемого файла, но делает это действительно удалить весь вызов? Значение, если я напишу

System.Diagnostics.Trace.WriteLineIf(g_log.verbosity>=5,ExpensiveFunctionCall());

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

В любом случае, даже если он удаляет его, он уступает макросу C ++, потому что я не могу сделать этот большой уродливый вызов похожим на мой простой вызов VLOG () в C ++ и все же избежать оценки параметров, могу ли я? Также я не могу избежать издержек, определяя детализацию ниже во время выполнения, как я могу в C ++, верно?

Ответы [ 9 ]

9 голосов
/ 17 февраля 2009

Чтобы ответить на один из ваших вопросов, все вызовы методов, которые должны оцениваться для вызова Trace.WriteLine (или его братьев и сестер / родственников), не вызываются, если Trace.WriteLine скомпилирован. Так что продолжайте и вставляйте ваши дорогие вызовы методов непосредственно в качестве параметров в вызов Trace, и он будет удален во время компиляции, если вы не определите символ TRACE.

Теперь к вашему другому вопросу, касающемуся изменения вашего многословия во время выполнения. Хитрость заключается в том, что Trace.WriteLine и аналогичные методы принимают в качестве аргументов форматирования строки 'params object [] args'. Только когда строка на самом деле испускается (когда многословие задано достаточно высоким), метод вызывает ToString для этих объектов, чтобы получить строку из них. Таким образом, я часто играю трюк, чтобы передать в эти методы объекты, а не полностью собранные строки, и оставить создание строк в ToString объекта, который я передаю. Таким образом, налог на производительность времени выполнения платится только тогда, когда фактически происходит регистрация, и это дает вам свободу изменять детализацию без перекомпиляции приложения.

2 голосов
/ 16 июля 2010

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

Если вы используете TraceSource (что, я полагаю, следует делать, а не вызывать Trace напрямую, потому что это дает вам более детальный контроль над трассировкой на уровне компонентов во время выполнения), вы можете сделать что-то вроде этого:

if (Component1TraceSource.ShouldTrace(TraceEventType.Verbose))
     OutputExpensiveTraceInformation()

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

Преимущество этого подхода состоит в том, что JITer компилируется по функциям в зависимости от необходимости, если «if» оценивается как false, функция не только не будет вызвана - она ​​даже не будет JITed. Недостатком является то, что (а) вы разделили знания об уровне трассировки между этим вызовом и функцией OutputExaciousTraceInformation (поэтому, если вы, например, измените TraceEventType на TraceEventType.Information, например, он не будет работать, поскольку даже вызывайте его, если в этом примере не включен TraceSource для трассировки подробного уровня) и (б) это больше кода для записи.

В этом случае может показаться, что C-подобный препроцессор поможет (поскольку он может убедиться, например, в том, что параметр ShouldTrace и возможный вызов TraceEvent одинаковы), но я понимаю, почему C # не включает это.

Эндрю предлагает выделить дорогостоящие операции в методах .ToString объектов, передаваемых в TraceEvent, также хорошо; в этом случае вы можете, например, разработать объект, который просто используется для трассировки, в который вы передаете объекты, для которых вы хотите построить дорогостоящее строковое представление, и изолировать этот код в методе ToString объекта трассировки, а не делать это в список параметров для вызова TraceEvent (что приведет к его выполнению, даже если TraceLevel не включен во время выполнения).

Надеюсь, это поможет.

2 голосов
/ 17 февраля 2009

Решение, которое сработало для меня, - использование класса singleton . Он может раскрыть ваши функции ведения журнала, и вы сможете эффективно контролировать его поведение. Давайте назовем класс «AppLogger». Ее пример

public class AppLogger
{
   public void WriteLine(String format, params object[] args)
    {
        if ( LoggingEnabled )
        {
            Console.WriteLine( format, args );
        }
    }
}

Обратите внимание, что синглтон не включен в приведенный выше пример. Есть множество хороших примеров из труб. Теперь самое интересное, как поддерживать многопоточность. Я сделал это так: (сокращенно, хахахаха)

public static void WriteLine( String format, params object[] args )
{
    if ( TheInstance != null )
    {
        TheInstance.TheCreatingThreadDispatcher.BeginInvoke(  Instance.WriteLine_Signal, format, args );
    }
}

Таким образом, любой поток может записывать в журнал, а сообщения обрабатываются в исходном создаваемом потоке. Или вы можете создать специальный поток только для обработки выходных данных журнала.

2 голосов
/ 17 февраля 2009

Условный атрибут - ваш лучший друг. Вызов будет полностью удален (как если бы сайты вызовов были # if'd), когда #define не установлен.

РЕДАКТИРОВАТЬ: кто-то поместил это в комментарии (спасибо!), Но стоит отметить в основном теле ответа:

Все методы класса Trace оформлены с помощью Conditional ("TRACE"). Просто видел это с помощью отражателя.

Что означает, что Trace.Blah (... дорогой ...) полностью исчезнет, ​​если TRACE не определено.

1 голос
/ 18 февраля 2009

Два из этих ответов (Эндрю Арнотта и Брайана) действительно ответили на часть моего вопроса. ConditionalAttribute, который применяется к методам класса Trace и Debug, вызывает удаление всех вызовов методов, если TRACE или DEBUG # undef'd, включая дорогостоящую оценку параметров. Спасибо!

Во второй части, можете ли вы полностью удалить все вызовы во время выполнения, а не во время компиляции, я нашел ответ в log4net fac . Согласно им, если вы установите свойство только для чтения во время запуска, среда выполнения скомпилирует все вызовы, которые не прошли тест! Это не позволяет вам изменить его после запуска, но это нормально, это лучше, чем удалять их во время компиляции.

1 голос
/ 17 февраля 2009

вы пробовали сложные API для регистрации, такие как log4net (http://logging.apache.org/log4net/index.html)?

0 голосов
/ 17 февраля 2009

Это вызовет дорогой вызов, потому что у него могут быть желательные побочные эффекты.

Что вы можете сделать, так это украсить свой дорогой метод атрибутом [Conditional ("TRACE")] или [Conditional ("DEBUG")]. Метод не будет скомпилирован в конечный исполняемый файл, если константа DEBUG или TRACE не определена, и не вызовет какие-либо вызовы для выполнения дорогостоящего метода.

0 голосов
/ 17 февраля 2009

Я не уверен, но вы сами можете найти ответ.

Сделайте это действительно дорогой функцией (например, Thread.Sleep(10000)) и время звонка. Если это занимает очень много времени, то он все равно вызывает вашу функцию.

(Вы можете обернуть вызов Trace.WriteLineIf() с помощью #if TRACE и #endif и протестировать его снова для сравнения базы.)

0 голосов
/ 17 февраля 2009

За ваш комментарий

«потому что я не могу сделать этот большой уродливый вызов похожим на мой простой вызов VLOG () в C ++» - вы можете добавить оператор using, как показано ниже.

</p> <pre> using System.Diagnostics; .... Trace.WriteLineIf(.....) </pre> <p>

Как я понимаю, он удалит строки, содержащие Trace, если вы не определите символ Trace.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...