Роббинс Д. - Отладка приложений для Microsoft .NET и Microsoft Windows - 2004
.pdf
|
|
|
ГЛАВА 3 Отладка при кодировании |
81 |
|||
|
|
|
|
|
|
||
Листинг 3-2. |
Пример исчерпывающего утверждения |
|
|
|
|
||
|
|
|
|
|
|||
HANDLE DEBUGINTERFACE_DLLINTERFACE __stdcall |
|
|
|
|
|||
StartDebugging |
( LPCTSTR |
szDebuggee |
, |
|
|
|
|
|
|
LPCTSTR |
szCmdLine |
, |
|
|
|
|
|
LPDWORD |
lpPID |
, |
|
|
|
|
|
CDebugBaseUser * pUserClass |
, |
|
|
|
|
|
|
LPHANDLE |
lpDebugSyncEvents |
) |
|
|
|
{ |
|
|
|
|
|
|
|
// Утверждаем параметры. |
|
|
|
|
|
||
ASSERT |
( FALSE |
== IsBadStringPtr ( szDebuggee , MAX_PATH ) ) ; |
|
|
|||
ASSERT |
( FALSE |
== IsBadStringPtr ( szCmdLine , MAX_PATH ) ) ; |
|
|
|||
ASSERT |
( FALSE |
== IsBadWritePtr ( lpPID , sizeof ( DWORD ) ) ) ; |
|
||||
ASSERT |
( FALSE |
== IsBadReadPtr ( pUserClass , |
|
|
|
|
|
|
|
|
sizeof ( CDebugBaseUser * ) ) ) ; |
|
|||
ASSERT |
( FALSE |
== IsBadWritePtr ( lpDebugSyncEvents , |
|
|
|
||
|
|
|
sizeof ( HANDLE ) * |
|
|
|
|
|
|
|
NUM_DEBUGEVENTS ) ) ; |
|
|
||
// Проверяем их существование. |
|
|
|
|
|
||
if ( ( |
TRUE == |
IsBadStringPtr ( szDebuggee , MAX_PATH ) |
) |
|| |
|
||
( |
TRUE == |
IsBadStringPtr ( szCmdLine , MAX_PATH ) |
) |
|| |
|
||
( |
TRUE == |
IsBadWritePtr ( lpPID , sizeof ( DWORD ) |
) ) |
|| |
|
||
( |
TRUE == |
IsBadReadPtr ( pUserClass , |
|
|
|
|
|
|
|
|
sizeof ( CDebugBaseUser * ) ) ) |
|| |
|
||
( |
TRUE == |
IsBadWritePtr ( lpDebugSyncEvents , |
|
|
|
|
|
|
|
|
sizeof ( HANDLE ) * |
|
|
|
|
|
|
|
NUM_DEBUGEVENTS ) |
) |
) |
|
|
{ |
|
|
|
|
|
|
|
SetLastError ( ERROR_INVALID_PARAMETER ) ; return ( INVALID_HANDLE_VALUE ) ;
}
//Строка для события стартового подтверждения. TCHAR szStartAck [ MAX_PATH ] = _T ( "\0" ) ;
//Загружаем строку для стартового подтверждения.
if ( 0 == LoadString ( GetDllHandle ( ) |
, |
IDS_DBGEVENTINIT |
, |
szStartAck |
, |
MAX_PATH |
) ) |
{ |
|
ASSERT ( !"LoadString IDS_DBGEVENTINIT failed!" ) ; return ( INVALID_HANDLE_VALUE ) ;
}
//Описатель стартового подтверждения, которого будет ждать
//эта функция, пока не запустится отладочный поток.
HANDLE hStartAck = NULL ;
// Создаем событие стартового подтверждения.
см. след. стр.
82 ЧАСТЬ I Сущность отладки
hStartAck = CreateEvent ( NULL |
, |
// Безопасность по умолчанию. |
|
TRUE |
, |
// |
Событие с ручным сбросом. |
FALSE |
, |
// |
Начальное состояние=Not signaled. |
szStartAck ) ; // Имя события. ASSERT ( NULL != hStartAck ) ;
if ( NULL == hStartAck )
{
return ( INVALID_HANDLE_VALUE ) ;
}
// Связываем параметры. |
|
|
|
THREADPARAMS stParams ; |
|
|
|
stParams.lpPID = lpPID ; |
|
|
|
stParams.pUserClass = pUserClass ; |
|
||
stParams.szDebuggee |
= szDebuggee |
; |
|
stParams.szCmdLine |
= szCmdLine |
; |
|
// Описатель для отладочного потока. |
|
||
HANDLE hDbgThread = INVALID_HANDLE_VALUE ; |
|
||
// Пробуем создать поток. |
|
|
|
UINT dwTID = 0 ; |
|
|
|
hDbgThread = (HANDLE)_beginthreadex ( NULL |
, |
||
|
|
0 |
, |
|
|
DebugThread |
, |
|
|
&stParams |
, |
|
|
0 |
, |
|
|
&dwTID |
) ; |
ASSERT ( INVALID_HANDLE_VALUE != hDbgThread ) ; |
|
||
if (INVALID_HANDLE_VALUE == hDbgThread ) |
|
||
{ |
|
|
|
VERIFY ( CloseHandle ( hStartAck ) ) ; return ( INVALID_HANDLE_VALUE ) ;
}
//Ждем, пока отладочный поток не придет в норму и продолжаем. DWORD dwRet = ::WaitForSingleObject ( hStartAck , INFINITE ) ; ASSERT (WAIT_OBJECT_0 == dwRet ) ;
if (WAIT_OBJECT_0 != dwRet )
{
VERIFY ( CloseHandle ( hStartAck ) ) ; VERIFY ( CloseHandle ( hDbgThread ) ) ; return ( INVALID_HANDLE_VALUE ) ;
}
//Избавляемся от описателя подтверждения.
VERIFY ( CloseHandle ( hStartAck ) ) ;
//Проверяем, что отладочный поток еще выполняется. Если это не так,
//отлаживаемое приложение, вероятно, не может запуститься.
|
ГЛАВА 3 Отладка при кодировании |
83 |
|
|
|
|
|
|
|
|
|
DWORD dwExitCode = ~STILL_ACTIVE ; |
|
|
if ( FALSE |
== GetExitCodeThread ( hDbgThread , &dwExitCode ) ) |
|
{ |
|
|
ASSERT |
( !"GetExitCodeThread failed!" ) ; |
|
VERIFY |
( CloseHandle ( hDbgThread ) ) ; |
|
return |
( INVALID_HANDLE_VALUE ) ; |
|
} |
|
|
ASSERT ( STILL_ACTIVE == dwExitCode ) ; if ( STILL_ACTIVE != dwExitCode )
{
VERIFY ( CloseHandle ( hDbgThread ) ) ; return ( INVALID_HANDLE_VALUE ) ;
}
//Создаем события синхронизации, чтобы главный поток
//мог сообщить отладочному циклу, что делать.
BOOL bCreateDbgSyncEvts =
CreateDebugSyncEvents ( lpDebugSyncEvents , *lpPID ) ; ASSERT ( TRUE == bCreateDbgSyncEvts ) ;
if ( FALSE == bCreateDbgSyncEvts )
{
//Это серьезная проблема. Отладочный поток выполняется, но
//я не смог создать события синхронизации, необходимые потоку
//пользовательского интерфейса для управления отладочным потоком.
//Мое единственное мнение — выходить. Я закрою отладочный поток
//и просто выйду. Больше я ничего не могу сделать.
TRACE ( "StartDebugging : CreateDebugSyncEvents failed\n" ) ;
VERIFY ( TerminateThread ( hDbgThread , (DWORD) 1 ) ) ;
VERIFY ( CloseHandle ( hDbgThread ) ) ;
return ( INVALID_HANDLE_VALUE ) ;
}
//Просто на случай, если кто то изменит функцию
//и не сможет правильно указать возвращаемое значение. ASSERT ( INVALID_HANDLE_VALUE != hDbgThread ) ;
//Жизнь прекрасна!
return ( hDbgThread ) ;
}
Утверждения в .NET Windows Forms или консольных приложениях
Перед тем как перейти к мелким подробностям утверждений .NET, хочу отметить одну ключевую ошибку, которую я встречал практически во всех кодах .NET, осо$ бенно во многих примерах, из которых разработчики берут код для создания своих приложений. Все забывают, что можно передать в объектном параметре значе$ ние null. Даже когда разработчики используют утверждения, код выглядит при$ мерно так:
84 ЧАСТЬ I Сущность отладки
void DoSomeWork ( string TheName )
{
Debug.Assert ( TheName.Length > 0 ) ;
Если TheName имеет значение null, то вместо срабатывания утверждения вызов свойства Length приводит к исключению System.NullReferenceException, тут же об$ рушивая ваше приложение. Это тот ужасный случай, когда утверждение вызывает нежелательный побочный эффект, нарушая основное правило утверждений. И, разумеется, отсюда следует, что если разработчики не проверяют наличие пустых объектов в утверждениях, то не делают этого и при обычной проверке парамет$ ров. Окажите себе огромную услугу: начните проверять объекты на null.
То, что приложения .NET не должны заботиться об указателях и блоках памя$ ти означает, что по крайней мере 60% утверждений, использовавшихся нами в дни C++, ушли в прошлое. В сфере утверждений команда .NET добавила в простран$ ство имен System.Diagnostic два объекта — Debug и Trace, активных, только если в компиляции приложения вы определили DEBUG или TRACE соответственно. Оба эти определения могут быть указаны в диалоговом окне Property Pages проекта. Как вы видели, метод Assert обрабатывает утверждения в .NET. Довольно интересно, что и Debug и Trace обладают похожими методами, включая Assert. Мне кажется, что наличие двух возможных утверждений, компилирующихся по разным усло$ виям, может сбить с толку. Следовательно, поскольку утверждения должны быть активны только в отладочных сборках, для утверждений я использую только Debug.Assert. Это позволяет избежать сюрпризов от конечных пользователей, зво$ нящих мне с вопросами о странных диалоговых окнах или сообщениях о том, что что$то пошло не так. Я настоятельно рекомендую вам делать то же самое, внося свой вклад в целостность мира утверждений.
Есть три перегруженных метода Assert. Все они принимают значение булев$ ского типа в качестве первого или единственного параметра, и, если оно равно false, инициируется утверждение. Как видно из предыдущих примеров, где я ис$ пользовал Debug.Assert, один из методов принимает второй параметр типа string, который отображается в выдаваемом сообщении. Последний перегруженный метод Assert принимает третий параметр типа string, предоставляющий еще больше дан$ ных при срабатывании утверждения. По моему опыту случай с двумя параметра$ ми — самый простой для использования, так как я просто копирую условие, про$ веряемое в первом параметре, и вставляю его как строку. Конечно, теперь, когда нужное в утверждении условное выражение находится в кавычках, проверяя пра$ вильность кода, следует контролировать, чтобы строковое значение всегда совпа$ дало с реальным условием. Следующий код демонстрирует все три метода Assert в действии.
Debug.Assert ( i > 3 |
) |
|
|
|
|
|
||||
Debug.Assert |
( |
i |
> |
3 |
, "i |
> |
3" |
) |
|
|
Debug.Assert |
( |
i |
> |
3 |
, |
"i |
> |
3" |
, |
"This means I got a bad parameter") |
Объект Debug в .NET интересен тем, что позволяет представлять результат раз$ ными способами. Исходящая информация от объекта Debug (и соответственно объекта Trace) проходит через другой объект — TraceListener. Классы$потомки
ГЛАВА 3 Отладка при кодировании |
85 |
|
|
TraceListener добавляются в свойство объекта Debug — набор Listener. Прелесть такого подхода в том, что при каждом нарушении утверждения объект Debug перебирает набор Listener и по очереди вызывает каждый объект TraceListener. Благодаря этой удобной функциональности даже при появлении новых усовершенствованных способов уведомления для утверждений вам не придется вносить серьезных из$ менений в код, чтобы задействовать их преимущества. Более того, в следующем разделе я покажу, как добавить новые объекты TraceListener, вообще не изменяя код, что обеспечивает превосходную расширяемость!
Используемый по умолчанию объект TraceListener называется DefaultTraceListener. Он направляет исходящую информацию в два разных места, самым заметным из которых является диалоговое окно утверждения (рис. 3$1). Как видите, большая его часть занята информацией из стека и типами параметров. Также указаны ис$ точник и строка для каждого элемента. В верхних строках окна выводятся стро$ ковые значения, переданные вами в Debug.Assert. На рис. 3$1 я в качестве второго параметра передал в Debug.Assert строку «Debug.Assert assertion».
Результат нажатия каждой кнопки описан в строке заголовка информацион$ ного окна. Единственная интересная клавиша — Retry. Если вы исполняете код в отладчике, вы просто переходите в отладчик на строку, следующую за утвержде$ нием. Если вы не в отладчике, щелчок Retry инициирует специальное исключе$ ние и запускает селектор отладчика по требованию, позволяющий выбрать заре$ гистрированный отладчик для отладки утверждения.
В дополнение к выводу в информационном окне Debug.Assert также направля$ ет всю исходящую информацию через OutputDebugString, поэтому ее получает под$ ключенный отладчик. Эта информация предоставляется в схожем формате, кото$ рый показан в следующем коде. Поскольку DefaultTraceListener выполняет вывод через OutputDebugString, вы можете воспользоваться прекрасной программой Марка Руссиновича (Mark Russinovich) DebugView (www.sysinternals.com), чтобы просмот$ реть его, не находясь в отладчике. Ниже я расскажу об этом подробнее.
——DEBUG ASSERTION FAILED ——
——Assert Short Message —— Debug.Assert assertion
——Assert Long Message ——
at HappyAppy.Fum() d:\asserterexample\asserter.cs(15)
at HappyAppy.Fo(StringBuilder sb) d:\asserterexample\asserter.cs(20) at HappyAppy.Fi(IntPtr p) d:\asserterexample\asserter.cs(24)
at HappyAppy.Fee(String Blah) d:\asserterexample\asserter.cs(29) at HappyAppy.Baz(Double d) d:\asserterexample\asserter.cs(34)
at HappyAppy.Bar(Object o) d:\asserterexample\asserter.cs(39) at HappyAppy.Foo(Int32 i) d:\asserterexample\asserter.cs(46) at HappyAppy.Main() d:\\asserterexample\asserter.cs(76)
86 ЧАСТЬ I Сущность отладки
Рис. 3 1. Информационное окно DefaultTraceListener
Обладая информацией, предоставляемой Debug.Assert, вы никогда больше не будете раздумывать, почему сработало утверждение! .NET Framework также пре$ доставляет два других объекта TraceListener. Для записи исходящей информации в текстовый файл используйте класс TextWriterTraceListener, а для записи ее в журнал событий — класс EventLogTraceListener. К сожалению, классы TextWriterTraceListener
и EventLogTraceListener практически бесполезны, потому что записывают только поля сообщений ваших утверждений и не включают информацию о стеке. Хоро$ шая новость в том, что реализовать собственные объекты TraceListener неслож$ но, поэтому в рамках BugslayerUtil.NET.DLL я пошел дальше и написал для вас ис$ правленные версии TextWriterTraceListener и EventLogTraceListener: Bugslayer TextWriterTraceListener и BugslayerEventLogTraceListener соответственно.
И BugslayerTextWriterTraceListener, и BugslayerEventLogTraceListener — вполне заурядные классы. BugslayerTextWriterTraceListener наследует напрямую от TextWri terTraceListener, и все, что он делает, — переопределяет метод Fail, который Debug.Assert вызывает для вывода информации. Помните, что при использовании
BugslayerTextWriterTraceListener или TextWriterTraceListener соответствующий тек$ стовый файл с исходящей информацией не сбрасывается на диск, если не задать true атрибуту autoflush элемента trace в конфигурационном файле приложения, не вызвать явно Close для потока или файла или не задать Debug.AutoFlush значе$ ние true, чтобы каждая запись автоматически вызывала сброс на диск. По каким$ то причинам класс EventLogTraceListener является закрытым, поэтому я не мог на$ следовать от него напрямую и создал потомок прямо от абстрактного класса TraceListener. Однако я все$таки получил информацию о стеке весьма интересным способом. Как показано ниже, стандартный класс StackTrace, предоставляемый .NET, позволяет в любой момент легко получить информацию о стеке.
StackTrace StkTrc = new StackTrace ( ) ;
В сравнении с действиями, которые надо было выполнять в машинном коде, чтобы получить такую информацию, способ, предоставляемый .NET, служит пре$ красным примером того, как .NET облегчает вашу жизнь. StackTrace возвращает набор объектов StackFrame, представляющих стек. Просмотрев документацию на StackFrame, вы увидите, что в нем есть все виды интересных методов для получе$ ния строки и номера источника. Объект StackTrace содержит метод ToString, и я был абсолютно уверен, что через него как$то можно добавлять источник и стро$ ку в итоговую информацию о стеке. Увы, я ошибался. Поэтому мне пришлось 30
ГЛАВА 3 Отладка при кодировании |
87 |
|
|
минут писать и тестировать класс BugslayerStackTrace, наследующий от StackTrace и переопределяющий ToString, чтобы иметь возможность добавить информацию об источнике и строке к каждому методу. В листинге 3$3 показаны два метода из BugslayerStackTrace, выполняющие эти действия.
Листинг 3-3. BugslayerStackTrace, собирающий полную информацию о стеке,
втом числе сведения об источнике и строке
///<summary>
///Создает читаемое представление информации о стеке.
///</summary>
///<returns>
///Читаемое представление информации о стеке.
///</returns>
public override string ToString ( )
{
//Обновляем StringBuilder для хранения всего необходимого. StringBuilder StrBld = new StringBuilder ( ) ;
//Первое, что надо внести, — перевод строки.
StrBld.Append ( DefaultLineEnd ) ;
//Зациклить и сделать! Здесь нельзя использовать foreach,
//так как StackTrace не наследует от IEnumerable.
for ( int i = 0 ; i < FrameCount ; i++ )
{
StackFrame StkFrame = GetFrame ( i ) ; if ( null != StkFrame )
{
BuildFrameInfo ( StrBld , StkFrame ) ;
}
}
return ( StrBld.ToString ( ) ) ;
}
/*///////////////////////////////////////////////////////////////// // Закрытые методы
/////////////////////////////////////////////////////////////////*/
///<summary>
///Выполняет мелкую работу по преобразованию фрейма
///в строку и внесению его в StringBuilder.
///</summary>
///<param name="StrBld">
///StringBuilder для внесения результатов.
///</param>
///<param name="StkFrame">
///Фрейм стека для преобразования.
///</param>
private void BuildFrameInfo ( StringBuilder StrBld |
, |
см. след. стр.
88 ЧАСТЬ I Сущность отладки
StackFrame StkFrame )
{
//Получаем метод через механизм отражения. MethodBase Meth = StkFrame.GetMethod ( ) ;
//Если ничего не получили, выходим отсюда. if ( null == Meth )
{
return ;
}
//Присваиваем метод.
String StrMethName = Meth.ReflectedType.Name ;
//Вносим отступ функции (function indent), если он есть. if ( null != FunctionIndent )
{
StrBld.Append ( FunctionIndent ) ;
}
//Получаем тип и имя класса.
StrBld.Append ( StrMethName ) ;
StrBld.Append ( "." ) ;
StrBld.Append ( Meth.Name ) ;
StrBld.Append ( "(" ) ;
//Вносим параметры, включая все их имена. ParameterInfo[] Params = Meth.GetParameters ( ) ; for ( int i = 0 ; i < Params.Length ; i++ )
{
ParameterInfo CurrParam = Params[ i ] ; StrBld.Append ( CurrParam.ParameterType.Name ) ; StrBld.Append ( " " ) ;
StrBld.Append ( CurrParam.Name ) ; if ( i != ( Params.Length 1 ) )
{
StrBld.Append ( ", " ) ;
}
}
//Закрываем список параметров.
StrBld.Append ( ")" ) ;
// Получаем источник и строку, только если они есть. if ( null != StkFrame.GetFileName ( ) )
{
//Мне надо определять источник? Если да, то нужно
//вставить в конце разрыв строки и отступ.
if ( null != SourceIndentString )
{
|
ГЛАВА 3 Отладка при кодировании |
89 |
|
|
|
|
|
|
|
|
|
StrBld.Append ( |
LineEnd ) ; |
|
StrBld.Append ( |
SourceIndentString ) ; |
|
} |
|
|
else |
|
|
{ |
|
|
// Просто добавляем пробел. |
|
|
StrBld.Append ( |
' ' ) ; |
|
} |
|
|
// Здесь получаем имя файла и строку с проблемой. |
|
|
StrBld.Append ( StkFrame.GetFileName ( ) ) ; |
|
|
StrBld.Append ( "(" |
) ; |
|
StrBld.Append ( StkFrame.GetFileLineNumber().ToString()); |
|
|
StrBld.Append ( ")" |
) ; |
|
} |
|
|
// Всегда добавляйте перевод строки. |
|
|
StrBld.Append ( LineEnd |
) ; |
|
}
Теперь, когда у вас есть другие классы TraceListener, которые стоит добавить в набор Listeners, мы в коде можем добавлять и удалять объекты TraceListener. Как и в любом наборе .NET, чтобы добавить объект в набор, вызовите метод Add, а чтобы избавиться от объекта — метод Remove. Стандартный TraceListener называется «Default». Вот как добавить BugslayerTextWriterTraceListener и удалить Default TraceListener:
Stream AssertFile = File.Create ( "BSUNBTWTLTest.txt" ) ;
BugslayerTextWriterTraceListener tListener =
new BugslayerTextWriterTraceListener ( AssertFile ) ;
Debug.Listeners.Add ( tListener ) ;
Debug.Listeners.Remove ( "Default" ) ;
Управление объектом TraceListener через файлы конфигурации
Если вы разрабатываете консольные приложения и приложения Windows Forms, то по большей части DefaultTraceListener должен удовлетворить все ваши потреб$ ности. Однако появляющееся время от времени информационное окно может нарушить работу любых автоматизированных тестов. Или, может быть, вы исполь$ зуете компонент сторонних производителей в службе Win32, и его отладочная сборка правильно использует Debug.Assert. В обоих случаях вам потребуется от$ ключить информационное окно, вызываемое DefaultTraceListener. Можно добавить код для удаления объекта DefaultTraceListener, но его можно удалить и не прика$ саясь к коду.
Любому двоичному коду .NET может быть сопоставлен внешний конфигура$ ционный файл XML. Этот файл располагается в том же каталоге, что и двоичный файл, и имеет такое же имя с добавленным в конце словом .CONFIG. Например, конфигурационный файл для FOO.EXE называется FOO.EXE.CONFIG. Можно лег$
90 ЧАСТЬ I Сущность отладки
ко добавить конфигурационный файл к проекту, добавив новый XML$файл с именем APP.CONFIG. Этот файл будет автоматически скопирован в каталог конечных фай$ лов и назван в соответствии с именем двоичного файла.
Элемент assert, расположенный внутри system.diagnostics в конфигурацион$ ном файле XML, имеет два атрибута. Если задать false первому атрибуту — assertuie nabled, .NET не будет отображать информационные окна, но исходящая инфор$ мация по$прежнему будет направляться через OutputDebugString. Второй атрибут — logfilename — позволяет указать файл, в который следует записывать любой вы$ вод утверждений. Интересно что при указании файла в атрибуте logfilename, в этом файле также появятся все операторы трассировки, о которых я расскажу ниже. В следующем отрывке показан минимальный конфигурационный файл. Он демон$ стрирует, как просто отключить информационные окна утверждений. Не забудь$ те: главный конфигурационный файл MACHINE.CONFIG включает такие же пара$ метры, что и обычные конфигурационные файлы, так что с их помощью вы вправе отключить информационные окна на всей машине.
<?xml version="1.0" encoding="UTF 8" ?>
<configuration>
<system.diagnostics>
<assert assertuienabled="false"
logfilename="tracelog.txt" />
</system.diagnostics>
</configuration>
Как я уже отмечал, можно добавлять и удалять приемники информации (liste$ ners), не затрагивая код, и, как вы, вероятно, догадались, это как$то связано с кон$ фигурационным файлом. В документации он выглядит вполне очевидным, но на момент написания этой книги документация содержала ошибки. Экспериментально я выявил все нужные приемы для корректного управления приемниками без из$ менений кода.
Все действия выполняются над элементом trace конфигурационного файла. Этот элемент содержит один очень важный необязательный атрибут, которому всегда следует задавать true, — autoflush. Сделав так, вы предписываете сбрасывать ис$ ходящий буфер на диск при каждой операции записи. В противном случае вам придется добавлять в код вызовы для сброса информации.
Внутри trace содержится элемент listener, через который добавляются и уда$ ляются объекты TraceListener. Удалить объект TraceListener очень просто. Укажи$ те элемент remove и задайте его атрибуту name строковое имя нужного объекта TraceListener. Ниже приведен полный конфигурационный файл, удаляющий Default TraceListener.
<?xml version="1.0" encoding="UTF 8" ?>
<configuration>
<system.diagnostics>
<trace autoflush="true" indentsize="0">
<listeners>
<remove name="Default" />
</listeners>
</trace>
</system.diagnostics>
</configuration>