فصل ۱۳: Conditional Compilation، Debug و Trace، Process و StackTrace
فروش یا انتشار این ترجمه منوط به داشتن مجوز لازم از صاحب حقوق اثر است.
فصل ۱۳: عیبیابی و تشخیص (Diagnostics)
تصویر تزئینی آغاز فصل ۱۳
وقتی مشکلی پیش میآید، مهم است اطلاعاتی برای کمک به تشخیص مشکل در دسترس باشد. یک محیط توسعهٔ یکپارچه (Integrated Development Environment یا IDE) یا Debugger میتواند بسیار کمک کند، اما معمولاً فقط در زمان Development در دسترس است. پس از انتشار Application، خود Application باید اطلاعات Diagnostic را جمعآوری و ثبت کند. برای پاسخ به این نیاز، .NET مجموعهای از امکانات برای Log کردن اطلاعات Diagnostic، پایش رفتار Application، تشخیص Runtime Errorها و در صورت وجود، یکپارچهشدن با Toolهای Debugging فراهم میکند.
برخی Toolها و APIهای Diagnostic مختص Windows هستند چون به قابلیتهای Windows Operating System تکیه میکنند. Microsoft برای جلوگیری از شلوغشدن BCL در .NET با APIهای Platform-specific، آنها را در NuGet Packageهای جداگانه منتشر کرده است که میتوانید بهصورت اختیاری Reference کنید. بیش از دوازده Package مخصوص Windows وجود دارد که میتوانید همه را یکجا با Master Package با نام Microsoft.Windows.Compatibility Reference کنید.
Typeهای این فصل عمدتاً در Namespace با نام System.Diagnostics تعریف شدهاند.
Conditional Compilation
در C# میتوانید هر بخش از Code را با Preprocessor Directiveها بهصورت شرطی Compile کنید. Preprocessor Directiveها دستورهای ویژهای برای Compiler هستند که با علامت # شروع میشوند و برخلاف دیگر Constructهای C# باید روی Line جداگانه قرار گیرند. از نظر منطقی پیش از Compilation اصلی اجرا میشوند، هرچند در عمل Compiler آنها را در Phase مربوط به Lexical Parsing پردازش میکند. Directiveهای مربوط به Conditional Compilation عبارتاند از #if، #else، #endif و #elif.
Directive با نام #if به Compiler میگوید یک بخش Code را نادیده بگیرد مگر اینکه Symbol مشخصی تعریف شده باشد. میتوانید Symbol را با #define در Source Code تعریف کنید ـ در این حالت فقط برای همان File اعمال میشود ـ یا در فایل .csproj با Element با نام <DefineConstants> تعریف کنید ـ در این حالت برای کل Assembly اعمال میشود:
#define TESTMODE // #define directives must be at top of file
// Symbol names are uppercase by convention.
using System;
class Program
{
static void Main()
{
#if TESTMODE
Console.WriteLine ("in test mode!"); // OUTPUT: in test mode!
#endif
}
}
اگر Line اول را حذف کنیم، Program طوری Compile میشود که Statement با نام Console.WriteLine کاملاً از Executable حذف شده باشد؛ درست مانند اینکه Comment شده باشد.
#else مشابه Statement با نام else در C# است و #elif معادل #else و سپس #if است. Operatorهای ||، && و ! بهترتیب Operationهای or، and و not را انجام میدهند:
#if TESTMODE && !PLAYMODE // if TESTMODE and not PLAYMODE
...
اما به خاطر داشته باشید یک C# Expression عادی نمیسازید و Symbolهایی که روی آنها Operation انجام میدهید هیچ ارتباطی با Variableها ـ Static یا غیره ـ ندارند.
میتوانید Symbolهایی تعریف کنید که برای همهٔ Fileهای یک Assembly اعمال شوند؛ با ویرایش فایل .csproj، یا در Visual Studio با رفتن به Tab با نام Build در Project Properties. نمونهٔ زیر دو Constant با نامهای TESTMODE و PLAYMODE تعریف میکند:
<PropertyGroup>
<DefineConstants>TESTMODE;PLAYMODE</DefineConstants>
</PropertyGroup>
اگر Symbol را در سطح Assembly تعریف کردهاید و میخواهید برای File خاصی آن را «Undefine» کنید، میتوانید از Directive با نام #undef استفاده کنید.
Conditional Compilation در برابر Static Variable Flagها
میتوانستید مثال قبلی را با یک Static Field ساده نیز پیادهسازی کنید:
static internal bool TestMode = true;
static void Main()
{
if (TestMode) Console.WriteLine ("in test mode!");
}
این روش مزیت Runtime Configuration را دارد. پس چرا Conditional Compilation را انتخاب کنیم؟ چون Conditional Compilation به جاهایی میرسد که Variable Flag نمیتواند؛ از جمله موارد صفحهٔ بعد.
- قرار دادن شرطی یک Attribute؛
- تغییر Type اعلانشدهٔ یک Variable؛
- Switch کردن بین Namespaceها یا Type Aliasهای مختلف در یک
using Directive؛ برای مثال:
using TestType =
#if V2
MyCompany.Widgets.GadgetV2;
#else
MyCompany.Widgets.Gadget;
#endif
حتی میتوانید Refactoring بزرگی را زیر Conditional Compilation Directive انجام دهید تا فوراً بین Version قدیم و جدید Switch کنید، و Libraryهایی بنویسید که مقابل چند Runtime Version مختلف Compile شوند و هرجا در دسترس است از جدیدترین Featureها استفاده کنند.
مزیت دیگر Conditional Compilation این است که Debugging Code میتواند به Typeهایی در Assemblyهایی Reference کند که در Deployment وجود ندارند.
Attribute با نام Conditional
Attribute با نام Conditional به Compiler دستور میدهد اگر Symbol مشخصی تعریف نشده است، تمام Callهای یک Class یا Method مشخص را نادیده بگیرد.
برای دیدن کاربرد آن، فرض کنید Methodای برای Log کردن Status Information مینویسید:
static void LogStatus (string msg)
{
string logFilePath = ...
System.IO.File.AppendAllText (logFilePath, msg + "\r\n");
}
حالا تصور کنید میخواهید این Method فقط وقتی Symbol با نام LOGGINGMODE تعریف شده اجرا شود. راهحل اول این است که تمام Callهای LogStatus را داخل #if قرار دهید:
#if LOGGINGMODE
LogStatus ("Message Headers: " + GetMsgHeaders());
#endif
نتیجه ایدهآل است اما خستهکننده. راهحل دوم قراردادن #if داخل خود LogStatus است. اما اگر LogStatus بهشکل زیر فراخوانی شود مشکل ایجاد میکند:
LogStatus ("Message Headers: " + GetComplexMessageHeaders());
GetComplexMessageHeaders همیشه فراخوانی میشود و ممکن است Performance Cost داشته باشد.
میتوانیم عملکرد راهحل اول را با راحتی راهحل دوم ترکیب کنیم؛ با اتصال Attribute با نام Conditional ـ تعریفشده در System.Diagnostics ـ به Method با نام LogStatus:
[Conditional ("LOGGINGMODE")]
static void LogStatus (string msg)
{
...
}
این کار به Compiler میگوید Callهای LogStatus را طوری رفتار دهد که انگار داخل Directive با نام #if LOGGINGMODE قرار دارند. اگر Symbol تعریف نشده باشد، تمام Callهای LogStatus در Compilation کاملاً حذف میشوند، از جمله Expressionهای ارزیابی Argumentها؛ بنابراین Expressionهای دارای Side Effect نیز Bypass میشوند. این حتی وقتی LogStatus و Caller در Assemblyهای متفاوت باشند کار میکند.
Attribute با نام Conditional در Runtime نادیده گرفته میشود؛ این Attribute صرفاً دستور به Compiler است.
جایگزینهای Conditional Attribute
اگر لازم است Functionality را در Runtime بهصورت Dynamic فعال یا غیرفعال کنید، Conditional Attribute بیفایده است و باید روش مبتنی بر Variable را بهکار ببرید. سؤال باقیمانده این است که چگونه هنگام فراخوانی Logging Methodهای شرطی، ارزیابی Argumentها را بهشکلی تمیز دور بزنیم. رویکرد Functional این مشکل را حل میکند:
using System;
using System.Linq;
class Program
{
public static bool EnableLogging;
static void LogStatus (Func<string> message)
{
string logFilePath = ...
if (EnableLogging)
System.IO.File.AppendAllText (logFilePath, message() + "\r\n");
}
}
Lambda Expression اجازه میدهد بدون Syntax اضافه این Method را فراخوانی کنید:
LogStatus ( () => "Message Headers: " + GetComplexMessageHeaders() );
اگر EnableLogging برابر false باشد، GetComplexMessageHeaders هرگز Evaluate نمیشود.
Classهای Debug و Trace
Debug و Trace Classهای استاتیکی هستند که قابلیتهای پایهٔ Logging و Assertion را ارائه میکنند. دو Class بسیار شبیهاند؛ تفاوت اصلی در کاربرد موردنظر آنهاست. Debug برای Debug Build طراحی شده و Trace برای هر دو Debug و Release Build. به این منظور:
All methods of the Debug class are defined with [Conditional("DEBUG")].
All methods of the Trace class are defined with [Conditional("TRACE")].
یعنی همهٔ Callهایی که به Debug یا Trace میزنید توسط Compiler حذف میشوند مگر اینکه Symbol با نام DEBUG یا TRACE تعریف شده باشد. Visual Studio در Tab با نام Build از Project Properties Checkboxهایی برای تعریف این Symbolها دارد و در Projectهای جدید Symbol با نام TRACE را بهطور پیشفرض فعال میکند.
هر دو Class با نامهای Debug و Trace متدهای Write، WriteLine و WriteIf را ارائه میدهند. بهطور پیشفرض Messageها به Output Window در Debugger فرستاده میشوند:
Debug.Write ("Data");
Debug.WriteLine (23 * 34);
int x = 5, y = 3;
Debug.WriteIf (x > y, "x is greater than y");
Class با نام Trace همچنین Methodهای TraceInformation، TraceWarning و TraceError دارد. تفاوت رفتار آنها با Write Methodها به TraceListenerهای فعال بستگی دارد؛ در «TraceListener» صفحهٔ 612 بررسی میشود.
Fail و Assert
هر دو Class با نام Debug و Trace متدهای Fail و Assert دارند. Fail Message را برای هر TraceListener موجود در Collection با نام Listeners در Debug یا Trace ارسال میکند؛ بهطور پیشفرض Message در Debug Output نوشته میشود:
Debug.Fail ("File data.txt does not exist!");
Assert اگر Argument بولی برابر false باشد فقط Fail را فراخوانی میکند؛ به این کار Assertion میگویند و نقض آن نشاندهندهٔ Bug در Code است. Failure Message اختیاری است:
Debug.Assert (File.Exists ("data.txt"), "File data.txt does not exist!");
var result = ...
Debug.Assert (result != null);
متدهای Write، Fail و Assert Overloadهایی دارند که افزون بر Message یک String با نام Category میگیرند و این میتواند در پردازش Output مفید باشد.
جایگزین Assertion این است که اگر Condition مخالف برقرار بود Exception پرتاب کنید. این هنگام Validation کردن Method Argumentها رایج است:
public void ShowMessage (string message)
{
if (message == null) throw new ArgumentNullException ("message");
...
}
چنین «Assertion»هایی بدون شرط Compile میشوند و از این نظر انعطاف کمتری دارند که نمیتوانید نتیجهٔ Failed Assertion را از طریق TraceListenerها کنترل کنید. از نظر فنی هم Assertion نیستند. Assertion چیزی است که اگر نقض شود Bug در Code همان Method را نشان میدهد؛ در حالی که Exception ناشی از Argument Validation نشاندهندهٔ Bug در Code مربوط به Caller است.
TraceListener
Class با نام Trace یک Property استاتیک با نام Listeners دارد که Collectionی از Instanceهای TraceListener را برمیگرداند. اینها مسئول پردازش Content تولیدشده توسط Methodهای Write، Fail و Trace هستند.
بهطور پیشفرض Collection با نام Listeners در هرکدام شامل یک Listener به نام DefaultTraceListener است. Default Listener دو Feature کلیدی دارد:
- وقتی به Debuggerی مانند Visual Studio متصل است، Messageها در Debug Output Window نوشته میشوند؛ در غیر این صورت Content Message نادیده گرفته میشود.
- وقتی Method با نام
Fail فراخوانی شود یا Assertion شکست بخورد، Application Terminate میشود.
میتوانید این رفتار را با حذف اختیاری Default Listener و افزودن یک یا چند Listener خودتان تغییر دهید. میتوانید Trace Listener را از صفر با Subclass کردن TraceListener بنویسید یا یکی از Typeهای آماده را استفاده کنید:
TextWriterTraceListener در Stream یا TextWriter مینویسد یا به File Append میکند.
EventLogTraceListener در Windows Event Log مینویسد و Windows-only است.
EventProviderTraceListener در زیرسیستم Event Tracing for Windows یا ETW مینویسد و Cross-platform Support دارد.
TextWriterTraceListener خود به ConsoleTraceListener، DelimitedListTraceListener، XmlWriterTraceListener و EventSchemaTraceListener Subclass شده است.
مثال زیر Default Listener مربوط به Trace را پاک و سه Listener اضافه میکند: یکی برای Append کردن در File، یکی برای Console و یکی برای Windows Event Log:
// Clear the default listener:
Trace.Listeners.Clear();
// Add a writer that appends to the trace.txt file:
Trace.Listeners.Add (new TextWriterTraceListener ("trace.txt"));
// Obtain the Console's output stream, then add that as a listener:
System.IO.TextWriter tw = Console.Out;
Trace.Listeners.Add (new TextWriterTraceListener (tw));
// Set up a Windows Event log source and then create/add listener.
// CreateEventSource requires administrative elevation, so this would
// typically be done in application setup.
if (!EventLog.SourceExists ("DemoApp"))
EventLog.CreateEventSource ("DemoApp", "Application");
Trace.Listeners.Add (new EventLogTraceListener ("DemoApp"));
در Windows Event Log، Messageهایی که با Write، Fail یا Assert مینویسید همیشه بهصورت Message از نوع «Information» در Windows Event Viewer نمایش داده میشوند. Messageهایی که با TraceWarning و TraceError مینویسید بهترتیب Warning یا Error نمایش داده میشوند.
TraceListener همچنین Propertyای با نام Filter و Type با نام TraceFilter دارد که میتوانید برای کنترل نوشتهشدن یا نشدن Message روی Listener تنظیم کنید. برای این کار یا یکی از Subclassهای آماده ـ EventTypeFilter یا SourceFilter ـ را Instantiate میکنید، یا TraceFilter را Subclass و Method با نام ShouldTrace را Override میکنید. برای مثال میتوان با این روش بر اساس Category Filter کرد.
TraceListener همچنین Propertyهای IndentLevel و IndentSize برای کنترل Indentation و Property با نام TraceOutputOptions برای نوشتن Data اضافی تعریف میکند:
TextWriterTraceListener tl = new TextWriterTraceListener (Console.Out);
tl.TraceOutputOptions = TraceOptions.DateTime | TraceOptions.Callstack;
TraceOutputOptions هنگام استفاده از Trace Methodها اعمال میشود:
Trace.TraceWarning ("Orange alert");
DiagTest.vshost.exe Warning: 0 : Orange alert
DateTime=2007-03-08T05:57:13.6250000Z
Callstack= at System.Environment.GetStackTrace(Exception e, Boolean
needFileInfo)
at System.Environment.get_StackTrace() at ...
Flush و Close کردن Listenerها
برخی Listenerها مانند TextWriterTraceListener در نهایت در Streamای مینویسند که Cache دارد. دو پیامد:
- ممکن است Message فوراً در Output Stream یا File ظاهر نشود.
- پیش از پایان Application باید Listener را Close یا حداقل Flush کنید؛ وگرنه Content داخل Cache را از دست میدهید ـ اگر در File مینویسید، بهطور پیشفرض تا 4 KB.
Classهای Trace و Debug Methodهای استاتیک Close و Flush دارند که Close یا Flush را روی همهٔ Listenerها فراخوانی میکنند و آنها نیز همین کار را روی Writer و Stream زیرین انجام میدهند.
Close بهطور ضمنی Flush را فراخوانی میکند، File Handleها را میبندد و مانع نوشتن Data بیشتر میشود.
قاعدهٔ کلی: پیش از پایان Application، Close را فراخوانی کنید و هر زمان میخواهید مطمئن شوید Message Data فعلی نوشته شده است، Flush را فراخوانی کنید؛ در Listenerهای File- یا Stream-based این مهم است.
Trace و Debug همچنین Property با نام AutoFlush دارند که اگر true باشد پس از هر Message یک Flush اجباری انجام میدهد.
یکپارچگی با Debugger
گاهی مفید است Application در صورت وجود با Debugger تعامل داشته باشد. در Development، Debugger معمولاً IDE شماست، مانند Visual Studio؛ در Deployment احتمالاً Tool سطح پایینتری مانند WinDbg، Cordbg یا MDbg است.
Attach و Break
Class استاتیک Debugger در System.Diagnostics Functionهای پایه برای تعامل با Debugger را ارائه میکند: Break، Launch، Log و IsAttached.
Debugger برای Debug کردن Application ابتدا باید به آن Attach شود. اگر Application را از داخل IDE Start کنید، این کار خودکار انجام میشود مگر اینکه «Start without debugging» را انتخاب کنید. گاهی Start کردن Application در Debug Mode از داخل IDE دشوار یا ناممکن است؛ نمونه Windows Service یا حتی Visual Studio Designer است.
یک راه این است که Application را عادی Start کنید و سپس در IDE گزینهٔ Debug Process را انتخاب کنید. اما در این حالت نمیتوانید Breakpointهای خیلی زودهنگام در اجرای Program بگذارید.
راهحل فراخوانی Debugger.Break از داخل Application است. این Method یک Debugger را Launch، به آن Attach و Execution را در همان نقطه Suspend میکند. Launch همین کار را بدون Suspend کردن Execution انجام میدهد. پس از Attach، میتوانید با Method با نام Log Messageها را مستقیم در Output Window Debugger بنویسید. Property با نام IsAttached مشخص میکند Debugger Attach شده است یا نه.
Debugger Attributeها
Attributeهای DebuggerStepThrough و DebuggerHidden به Debugger پیشنهاد میدهند Single-stepping را برای Method، Constructor یا Class مشخص چگونه مدیریت کند.
DebuggerStepThrough از Debugger میخواهد بدون تعامل User از Function عبور کند. این Attribute در Methodهای Auto-generated و Proxy Methodهایی که کار واقعی را به Method دیگری Forward میکنند مفید است. در حالت دوم، اگر Breakpoint داخل Method «واقعی» قرار داشته باشد، Debugger هنوز Proxy Method را در Call Stack نشان میدهد؛ مگر اینکه DebuggerHidden نیز اضافه شود. میتوانید این دو Attribute را در Proxyها ترکیب کنید تا User روی Debugging منطق Application متمرکز شود نه Plumbing:
[DebuggerStepThrough, DebuggerHidden]
void DoWorkProxy()
{
// setup...
DoWork();
// teardown...
}
void DoWork() {...} // Real method...
Processها و Process Threadها
در بخش پایانی فصل 6 توضیح دادیم چگونه با Process.Start یک Process جدید Launch کنید. Class با نام Process همچنین اجازه میدهد Processهای دیگر در حال اجرا روی همان Computer یا Computer دیگری را Query و با آنها تعامل کنید. Process بخشی از .NET Standard 2.0 است، هرچند Featureهای آن برای UWP Platform محدود شدهاند.
بررسی Processهای در حال اجرا
Methodهای Process.GetProcessXXX یک Process مشخص را با Name یا Process ID، یا همهٔ Processهای در حال اجرا روی Computer فعلی یا Computer مشخصشده، میگیرند. هم Processهای Managed و هم Unmanaged را شامل میشود. هر Process Instance مجموعهٔ بزرگی از Propertyها دارد که Statisticهایی مانند Name، ID، Priority، Memory و Processor Utilization، Window Handle و غیره را Map میکنند. نمونهٔ زیر تمام Processهای در حال اجرا روی Computer فعلی را Enumerate میکند:
foreach (Process p in Process.GetProcesses())
using (p)
{
Console.WriteLine (p.ProcessName);
Console.WriteLine (" PID: " + p.Id);
Console.WriteLine (" Memory: " + p.WorkingSet64);
Console.WriteLine (" Threads: " + p.Threads.Count);
}
Process.GetCurrentProcess Process فعلی را برمیگرداند.
با Method با نام Kill میتوانید یک Process را Terminate کنید.
بررسی Threadهای یک Process
میتوانید با Property با نام Process.Threads Threadهای Processهای دیگر را نیز Enumerate کنید. Objectهایی که میگیرید System.Threading.Thread نیستند؛ ادامه در صفحهٔ بعد.
آنها ProcessThread هستند و برای کارهای Administrative طراحی شدهاند، نه Synchronization. یک ProcessThread اطلاعات Diagnostic دربارهٔ Thread زیرین میدهد و اجازه میدهد بعضی جنبهها مانند Priority و Processor Affinity را کنترل کنید:
public void EnumerateThreads (Process p)
{
foreach (ProcessThread pt in p.Threads)
{
Console.WriteLine (pt.Id);
Console.WriteLine (" State: " + pt.ThreadState);
Console.WriteLine (" Priority: " + pt.PriorityLevel);
Console.WriteLine (" Started: " + pt.StartTime);
Console.WriteLine (" CPU time: " + pt.TotalProcessorTime);
}
}
StackTrace و StackFrame
Classهای StackTrace و StackFrame View فقطخواندنی از Execution Call Stack ارائه میدهند. میتوانید Stack Trace را برای Thread فعلی یا یک Exception Object بگیرید. این Information بیشتر برای Diagnostic مفید است، هرچند در Programming ـ Hackها ـ نیز قابل استفاده است. StackTrace یک Call Stack کامل را نمایندگی میکند؛ StackFrame یک Method Call در آن Stack را.
اگر StackTrace را بدون Argument یا با Boolean بسازید، Snapshot از Call Stack Thread فعلی میگیرید. اگر Boolean برابر true باشد، StackTrace در صورت وجود فایلهای Assembly با پسوند .pdb را میخواند و File Name، Line Number و Column Offset را در اختیار میگذارد. Project Debug Fileها هنگام Compile با Switch با نام /debug ساخته میشوند؛ Visual Studio معمولاً با این Switch Compile میکند مگر اینکه در Advanced Build Settings خلاف آن را بخواهید.
پس از گرفتن StackTrace میتوانید Frame مشخصی را با GetFrame بررسی یا همهٔ آنها را با GetFrames بگیرید:
static void Main() { A (); }
static void A() { B (); }
static void B() { C (); }
static void C()
{
StackTrace s = new StackTrace (true);
Console.WriteLine ("Total frames: " + s.FrameCount);
Console.WriteLine ("Current method: " + s.GetFrame(0).GetMethod().Name);
Console.WriteLine ("Calling method: " + s.GetFrame(1).GetMethod().Name);
Console.WriteLine ("Entry method: " + s.GetFrame
(s.FrameCount-1).GetMethod().Name);
Console.WriteLine ("Call Stack:");
foreach (StackFrame f in s.GetFrames())
Console.WriteLine (
" File: " + f.GetFileName() +
" Line: " + f.GetFileLineNumber() +
" Col: " + f.GetFileColumnNumber() +
" Offset: " + f.GetILOffset() +
" Method: " + f.GetMethod().Name);
}
Output:
Total frames: 4
Current method: C
Calling method: B
Entry method: Main
Call stack:
File: C:\Test\Program.cs Line: 15 Col: 4 Offset: 7 Method: C
File: C:\Test\Program.cs Line: 12 Col: 22 Offset: 6 Method: B
File: C:\Test\Program.cs Line: 11 Col: 22 Offset: 6 Method: A
File: C:\Test\Program.cs Line: 10 Col: 25 Offset: 6 Method: Main
میانبُر گرفتن اطلاعات اصلی کل StackTrace این است که روی آن ToString را فراخوانی کنید. Result شبیه زیر است:
at DebugTest.Program.C() in C:\Test\Program.cs:line 16
at DebugTest.Program.B() in C:\Test\Program.cs:line 12
at DebugTest.Program.A() in C:\Test\Program.cs:line 11
at DebugTest.Program.Main() in C:\Test\Program.cs:line 10
میتوانید Stack Trace یک Exception Object ـ نشاندهندهٔ آنچه به پرتاب Exception منجر شده ـ را نیز با دادن Exception به Constructor مربوط به StackTrace بگیرید.
Windows Event Logها
Platform با نام Win32 مکانیزم Logging متمرکزی در قالب Windows Event Logها فراهم میکند.
Classهای Debug و Trace که قبلاً استفاده کردیم، اگر EventLogTraceListener Register کنید در Windows Event Log مینویسند. اما با Class با نام EventLog میتوانید بدون Trace یا Debug مستقیماً در Windows Event Log بنویسید. همچنین میتوانید با این Class Event Data را بخوانید و Monitor کنید.
سه Windows Event Log استاندارد با این Nameها وجود دارند:
ApplicationSystemSecurity
بیشتر Applicationها معمولاً در Log با نام Application مینویسند.
نوشتن در Event Log
برای نوشتن در Windows Event Log:
- یکی از سه Event Log را انتخاب کنید؛ معمولاً Application.
- برای Source یک Name انتخاب کنید و اگر لازم است آن را بسازید؛ Create کردن Permission مدیریتی لازم دارد.
EventLog.WriteEntry را با Log Name، Source Name و Message Data فراخوانی کنید.
Source Name نامی است که Application شما را بهراحتی قابل شناسایی میکند. پیش از استفاده باید Source Name را Register کنید؛ Method با نام CreateEventSource این کار را انجام میدهد. سپس میتوانید WriteEntry را فراخوانی کنید؛ Code در مقالهٔ بعد ادامه دارد.