PDA

View Full Version : آموزش: فریم ورک(چوب بست!)ی کامل جهت Logging سراسری در پروژه ها



Saeed_m_Farid
پنج شنبه 11 خرداد 1391, 21:43 عصر
بنام خدا

سلام

تقدیم به ... :بامزه:

مقدمات:

این مطالب کلاً حوزه هایی رو پوشش میده در مورد: روند ثبت وقایع، خطاها، اطلاعات و کلاً تمام مواردی که جهت پیگیری فعالیت های پروژه یا پروژه ها و سیستم های وابسته یا مرتبط با اونها هست و منجر به درک و تشخیص مشکلات در بررسی های آتی میشوند؛ پس اگه فکر می کنید ربطی به کار شما نداره، همینجا بای بای، چون یه سری مزخرفات هم داخلشون هست که محتملاً وقت گرانبهای شما رو بیخودی تلف میکنه ...
اگه منصرف نشدین، اذعان میکنم که فردی سخت پی جو و باحوصله هستید، در ادامه خواهیم دید چقدر دوام می آوردید ...

خزعبلات:
* پیشنهاد میکنم اگه فقط جهت کسب اطلاعات فنی هستید و یا انسانی قانون مدار، منطقی، دارای پیشینه تشنج، دارای مشکل قلبی هستید بیخیال این قسمت شده و Skip and goto : اصل قضیات (از ما گفتن بود)...
==============
قبلش بگم که اسمش رو گذاشتم آموزش که مردم جمع بشن و ببینن چه خبره! ولی بیشتر برای اینه که نقاط ضعف کلاسی که نوشتم رو بشناسم و همیشه برای من اینطوری بوده که وقتی در مورد یه چیزی به کسی اطلاعات دادم، بیشتر خودم یاد گرفتم تا طرف! و هروقت این سنت حسن-ه:لبخند: رو کنار گذاشتم، بیسوادیم سیلی ای بس محکم بر گونه هایم نواخته است؛ از طرف دیگه میخوام لینک این تاپیک رو یه جای دیگه بدم تا اون هم خیلی طولانی نشه و یکی هم خدای نکرده حوصله کرد بازش کرد با دیدنش فرار نکنه ...
خوب، قضیه اینه که من مدتها با زبانهای native و managed مجزا، برنامه هایی نوشته بودم که مجبور بودن با هم ارتباط داشته باشن، نه سرور-کلاینت بلکه مثل شبکه بهم وصل میشدن و برای مقاصدی با هم گفتگو میکردن و خودم هم یکی از اون برنامه ها شده بودم و اصولاً تعامل با آدمیزادها به ورطهء فراموشی سپرده شده بود، قابل ذکر هست که تو خود هر node هم بازم تو چندین سطح همین داستان بود! بگذریم، میخوام بگم کاملاً میدونم که گزارش خطا، اطلاعات هر برنامه و نخهای مجزایی که توش کار میکنن، مشکلات ارتباطی، سخت افزار و خلاصه هزاران مورد مرتبط با سیستم تشخیص و ردیابی اطلاعاتی برای پروژه های بزرگ حیاتی هستن؛ بنابراین در چنین سیستم هایی Logging نقش حیاتی داره و کوچکترین موارد اطلاعاتی در پروژه های بزرگ (با قید صحیح زمان وقوع، درجه اهمیت، نوع log، کلاس، تابع و ...) میتونه کسی رو که رفته سر چوبه دار، نجات بده یا محکوم کنه و باگ های سیستم مشخص بشه و ...
همونطورکه گفته شد، روند ثبت وقایع و خطاها برای درک فعالیت های سیستم های پیچیده، به ویژه در مورد برنامه های کاربردی گسترده که یا با کاربران تعامل ندارن یا ارتباط بسیار کمی با کاربر دارن و معمولاً واشون میکنن به امان خدا و ماه به ماه سر نمیزنن مگه اینکه یه خامی سر سیستم ریخته بشه؛ شاید به اندازه هدف اصلی برنامه ضروری باشه.
راستش من اصلاً دات نتی نبودم و دقیقاً مثل پیرمردها که میگن شما چه میدونین زمان ما نون از زیر کوه قاف که اژدها ازش محافظت میکرد، در میومد! منم همچین دیدی داشتم و هروقت دات نت میشنیدم یاد شومپت میافتادم!(فحش نیست (http://www.sibpress.com/home/2674.html?comment_id=1)!) فقط از وب سرویس هاش (SOAP) استفاده مرده بودم، بعداً که یواش یواش جوانی از کف رفت و قدر ندانستیم و علم هم مانند الاغ رم کرده زنجیر برید و اصلاً نام پروتکل ها هم چه مخابراتی، چه شبکه و ... نامونسمان شد، تف کردهء مان را لیس زده و به جمع دوستان دهه هفتاد، هشتادی پیوستیم! ولی در این وادی هم کم کم متوجه شدم که اونطوری که فکر میکردم هم نیست و هرنوع تعصب احمقانه ای اشتباه اِ .
برگردیم سر logging؛ درسته من اولها با ™log4net (http://logging.apache.org/log4net/) (+ (http://logging.apache.org/log4net/release/manual/configuration.html)) و ™ (http://logging.apache.org/log4net/release/manual/configuration.html)Log4j (http://logging.apache.org/log4j/1.2/) استفاده کرده بودم، و فکر میکردم آخر قابلیت ها تو اونهاست ولی با دیدن کلاس TraceSource (http://msdn.microsoft.com/en-us/library/system.diagnostics.tracesource.aspx) از System.Diagnostics (http://msdn.microsoft.com/en-us/library/system.diagnostics.aspx) بازم دچار کف کردگی شدم و امکانات زیاد و راحتی استفاده اش (قدرت اصلی MS ای ها!) غافلگیرم کرد؛ واقعاً همه چی رو مثل آب خوردن کرده درحالیکه امکاناتی که داره فوق العاده زیاد هستن و معمولاً جمع ایندو در یکجا کاری بسیار مشکل هست، بهرصورت فکر کنم خزعبلات کافیه و بریم سر اصل قضیه ...
=============

اصل قضیات:
قابلیت هایی که این Framework داره خیلی زیادن و من سعی کنم در قالب یه Q&A اونها رو معرفی کنم :


سوال: میشه یه معرفی ابتدایی از System.Diagnostics.TraceSource بکنید؟
جواب: چرا نمیشه عزیزم؟ این فریم ورک یک سیستم بسیار قدرتمند، انعطاف پذیر، باثبات و با کارایی بالا جهت روند ثبت وقایع، خطاها، اطلاعات و کلاً تمام مواردی که برای پیگیری زیر به زیر فعالیت های پروژه ها و برنامه های کاربردی لازمه رو فراهم می کنه، دلیل اینهمه اغراق رو هم به زودی میگم!



سوال: آره راست میگی مهندز! بنظر من هم اغراق میاد تعاریف بالا؛ من که به کلاس نگاه میکنم چیز خاصی نمیبینم، حتا درحد یه ساب کلاس ™log4net (http://logging.apache.org/log4net/) هم نیست، تازه اون مال آپاچی هست و هزار تا امکانات و ...
جواب: اولاً مهندز خودتون هستید، درثانی فلفل نبین چه ریزه، بلکه خوب داکیومنت نشده! از MS بعیده (چون فکر کنم بیشتر از 73.265% همه قدرت و محبوبیت اِش بابت همین MSDN اَش هست!)؛ بگذریم، بیشتر دِوِلوپِر (توسعه دهنده ها) و بهمین علت از قابلیت ها و امکانات سهل الوصولش آگاهی ندارن و استفاده نمی کنن ولی خوان نعمتی که گسترده شده، ما پیر پاتال ها که نونمون از زیر سنگ در میومد، قدرش رو میدونیم (خوب بینیم بابا تو هم!)



سوال: از شما میخوام این امکانات رو برای ما بشمارین بیزحمت، ممنون از لطفتون
جواب: خواهش می کنم، اصلاً واسه همین این تاپیک رو زدم. ولی از فردا 4 روز تعطیله و همسر من هم منتظر هست بریم مسافرت؛ الان ساعت 21:33:33.235 هست و من نشستم آسمون ریسمون میبافم، اونم با لنگه کفش منتظره؛ فعلاً علی الحساب دو تا کلاس (منظور فایل حاوی class نه کلاس درسی یا کلاس باکلاسی!) با کمی تعییرات -که نام شرکت و پروژه هام و ... لو نرن!- میذارم که یکی مربوط میشه به همین TraceSource و اونیکی هم اشانتیون واسه اینکه اونقدر حوصله داشتی تا اینجا چرندیات پلاس پرندیات منو تحمل کردی؛ اونیکی هم یه کلاس کامل برای نگهداری Configuration Settings از نوع XML هست که تمام پروژه ها میتونن بصورت share ازش استفاده کنن و ...



جواب که سوال کننده میده: دیدی باید زد تو سرت! یه بار مثل آدم و با احترام سوال پرسیدم! خِرِفت :افسرده: واقعاً خوددرگیری داری تو نه؟
جواب جواب کننده به سوال کننده که خودسر حواب داده: تو اگه یه دونه خط بدون کامنت یا تست نشده پیدا کردی تو کلاس هام، 10 تا تشکر میکنم ازت! آدم حسابی هم کذی به این استانداردی دارم واست میدارم هم حرف میشنوم! نمیگم باگ نداره یا بهیچ وجه کد بهینه نمیشه ولی از خیلی کدهای دیگه که دیدم کاملتر و جامع تر و تست شده تر هست، عجب ها ...

در پایان قبل از شروع، شما هم اگه سوال یا نظری در مورد کلاس ها و ایده ها و .. داشتید بیزحمت دریع نکنین، ایمیل هم نکنین بذارین همینجا که تا می تونه شلوع بشه و بهم بریزه، یکمی حال کنیم ...

barnamenevisjavan
جمعه 12 خرداد 1391, 13:31 عصر
داداش كل متنت رو خوندم از اول تا اخر فقط خنديدم(بهتر از خنده بازار بود) ولي يك كلمه از حرفات حالي نشدم اصلا راجب به چي حرف ميزدي؟:لبخند:

Saeed_m_Farid
چهارشنبه 17 خرداد 1391, 13:57 عصر
پست قبلی رو کلاً بیخیال شده و صحبت رو بصورت کمی جدی تر ادامه میدم ولی قبلش :


داداش كل متنت رو خوندم از اول تا اخر فقط خنديدم(بهتر از خنده بازار بود) ولي يك كلمه از حرفات حالي نشدم اصلا راجب به چي حرف ميزدي؟:لبخند:
اشکال نداره عزیزم، هروقت لازمت شد سر در میاری، خودشو ناراحنت نکن...
===========
ادامهء مبحث:
داشتیم در مورد امکانات این فریم ورک صحبت میکردیم؛ اول مباحث ابتدایی:


TraceSource کجا بدرد میخوره؟ در تمام مواردی که شما نیاز دارین بدونید که چه اتفاقاتی در مسیر اجرای برنامه رخ میدن، می تونید از این فریم ورک برای ثبت کلیه وقایع با ذکر زمان، سطح و اهمیت رویداد، کلاس و متد و ... استفاده کنید، این سطوح شامل موارد بحرانی، خطاها، اخطارها، صرفاً اطلاعات، Verbose و همه یا هیچکدام از موارد ذکر شده میشن...
چرا بصورت دستی مثلاً فایل متنی یا Xml و ... نه؟ چون این فریم ورک بدون کمترین تلاشی تمام موارد ذکر شده رو همزمان پوشش میده و شما رو مطمئن میکنه که این روند بصورت کاملاً جامع و همزمان در Listener های مختلفی که می تونن فایل متنی یا Xml، مانیتورینگ خود ویندوز Event Viewer، کنسول ویندوز و اگه اینها هم کافی نبود Listener های آماده دیگه ای که مثلآً در codeplex هست (http://essentialdiagnostics.codeplex.com)، باشن؛ ثبت میشوند.
دیگه؟ خوب، شما اگه قرار بود تمام این کارها رو بصورت جداگانه انجام بدین، سطح نمایش هر کدوم از listener ها رو باید بصورت جداگانه تعیین میکردین، یعنی مثلاً شما تو کنسول فقط رویدادهای بحرانی (Critical) رو نمایش میدین، تو فایل متنی همه موارد و تو Event Viewer خطاها و بحرانی؛ حالا چطور میخواین همه اینها رو تو فراخوانی تابع افزودن log یه جا به کلاستون بفهمونید؟ با 5-6 تا ورودی تابع و یا چندین تابع overload شده و ... ولی با این کلاس بصورت کلی یکبار تمام Listener ها رو به TraceSource تون اضافه می کنید و هروقت خواستین، سطح ثبت وقایع رو برای هرکدوم از listener ها که خواستین عوض میکنید؛ علاوه بر این، می تونید کلاً برای TraceSource یک SourceLevel تعیین کنید که برای همه Listener ها شامل بشه.
میشه آدم رو کمتر گیج کنید؟ با مثال لطفاً توضیح بدین: OK! مثلاً فرض کنید شما یک شیء از نوع TraceSource ایجاد می کنید (در namespace System.Diagnostics)، به اینصورت : TraceSource Logger = new TraceSource("MyGlobalTracer", SourceLevels.All); حالا بصورت پیش فرض در این شیء یک Listener بنام Default قرار داره؛ شما می تونید با تغییر در فایل Web.config در برنامه های تحت وب، یا Appname.exe.config در برنامه های self-hosted (غیر تحت وب!) Listener های خودتون رو مدیریت کنید، مثلاً من اینجا با حذف Listener Default یه فایل متنی رو اضافه کردم : <?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.diagnostics>
<trace autoflush="true" indentsize="4">
<listeners>
<add name="myListener" type="System.Diagnostics.TextWriterTraceListener" initializeData="application.log" />
<remove name="Default" />
</listeners>
</trace>
</system.diagnostics>
</configuration> شما می تونید چندین Listener رو به همین طریق به TraceSource اضافه کنید و نام و سطح دسترسی و ... رو براشون مشخص کنید، مطالب بیشتر رو می تونید اینجا (http://msdn.microsoft.com/en-us/library/ms733025.aspx) ببینید..
من نمیخوام درگیر فایل کانفیگ بشم، چکار باید بکنم؟ کلاً اینکار درست نیست، چون پویایی روند ثبت رخدادها رو به این طریق با hard code کردن از دست میدین، ولی شما می تونید بصورت دستی هم Listener هاتون رو به Tracer اضافه کنید، من در مثالی که گذاشتم، دو مورد رو آوردم، ولی بهرصورت در برنامه های جدی، معمولآً باید دست ادمین رو برای کانفیگ باز بذارید، حتی اگه همچین کاری هم کردین خودتون یه فایل xml (مثلاً) در اختیار کاربر بذارین تا بتونه سطوح و نوع Listener ها رو تعیین کنه و شما از اون فایل برای load کردن Listener ها استفاده کنید که اون رو هم من فایل مدیریتش رو تو پست قبلی ضمیمه کردم، برای نمونه افزودن کنسول به لیست Listener ها : /// <summary>
/// Add a new System.Diagnostics.ConsoleTraceListener to
/// the collection of TraceSource's Listener(s).
/// </summary>
/// <param name="useErrorStream">
/// true to write tracing and debugging output to the standard error stream;
/// false to write tracing and debugging output to the standard output stream.
/// </param>
/// <param name="name">
/// Set the name of the trace listener, which helps identify this
/// particular instance within the trace listener collection.
/// </param>
public static void AddConsoleListener(bool useErrorStream = false,
string name = "MyConsoleTracer")
{
ConsoleTraceListener console = new ConsoleTraceListener(useErrorStream);
console.Name = name;
Logger.Listeners.Add(console);
Logger.Listeners[console.Name].Filter = Logger.Listeners[0].Filter;
}


من کنسول و فایل متنی رو می تونم یه Tracer اضافه کنم ولی Event Viewer اضافه نمیشه! چرا؟ برای افزودن Event Log ویندوز به لیست Listener هاتون برنامه شما باید با کاربر admin اجرا شده باشه! یه تاپیک در مورد بررسی admin بودن یکم پیش با دوستان داشتیم، می تونید اینجا (http://barnamenevis.org/showthread.php?343295) رو ببینید.
خوب به فرض ما Listener ها مون رو اضافه کردیم، چطور رویدادها رو log کنیم؟ واسه اینکار دستتون اونقدر بازه که شاید من نتونم حق مطلب رو ادا کنم، شما می تونید از توابع TraceData، TraceEvent، TraceInformation و TraceTransfer با انواع overload هاشون برای اینکار استفاده کنید ولی قبلش باید SourceLevel مربوط به Switch.Level در TraceSource تون رو مشخص کنید و حتماً هم قبلش Filter برای هرکدوم از Listener ها مشخص کردین دیگه! من برای اینکار یه تابع نوشتم که حداقل یه Listener از نوع فایل متنی به TraceSourceاضافه میکنه و کلاً کارهای اولیه Initialize رو انجام میده و نتیجه رو برمیگردونه :
/// <summary>
/// Initializes a new instance of the System.Diagnostics.SourceSwitch class,
/// specifying the display name, the default value for the source switch, the
/// fileName to write to, and the SourceLevels to event type of the messages to trace.
/// </summary>
/// <param name="displayName">
/// The name of the source switch.
/// </param>
/// <param name="defaultSwithValue">
/// The default value for the switch.
/// </param>
/// <param name="fileName">
/// The name of the file the System.Diagnostics.TextWriterTraceListener writes to
/// </param>
/// <param name="level">
/// A bitwise combination of the System.Diagnostics.SourceLevels values that
/// specifies the event type of the messages to trace.
/// </param>
/// <returns>
/// A boolean indicator specify if Tracer inialized or not?
/// </returns>
public static bool InitLogger(
string fileName = "GlobalLogs.log",
string displayName = "sourceSwitch",
string defaultSwithValue = "All")
{
if (!((isInitialized) || (Logger.Listeners["Default"] == null)))
{

Logger.Switch = new SourceSwitch(displayName, defaultSwithValue);
Logger.Listeners.Remove("Default");
TextWriterTraceListener textListener =
new TextWriterTraceListener(fileName);
Logger.Listeners.Add(textListener);
SourceLevels level = SourceLevels.All;
switch (defaultSwithValue)
{
case "All":
level = SourceLevels.All;
break;
case "Off":
level = SourceLevels.Off;
break;
case "Critical":
level = SourceLevels.Critical;
break;
case "Error":
level = SourceLevels.Error;
break;
case "Warning":
level = SourceLevels.Warning;
break;
case "Information":
level = SourceLevels.Information;
break;
case "Verbose":
level = SourceLevels.Verbose;
break;
default:
break;
}
Logger.Listeners[0].Filter = new EventTypeFilter(level);
Logger.Switch.Level = level;
return isInitialized = true;
}
return false;
}


چطوری باید از کلاسی که نوشتین استفاده کنیم؟ برای اینکار چون من کلاس رو static تعریف کردم، و ممکنه چندین پروژه بصورت همزمان از این کلاس استفاده کنن یا اصلاً هیچ پروژه ای TraceSource ما رو Initialize نکنه، ما باید اول هر پروژه ببینیم Listener های مورد نظرمون اضافه شدن یا نه؟ ضمناً همونطورکه گفتم باید دست ادمین برای کانفیگ logger باز باشه، چون ممکنه برای یکی حجم لاگ ها مهم باشه و فقط یه فایل متنی با رویدادهای بحرانی واسش کافی باشه ولی در یک سیستم حساس که کاربر نقشی توش نداره، ممکنه تمام سطوح logging مد نظر باشه و در چندین listener باید گزارشات دیده بشن، من حالت دوم بیشتر مدنظرم بوده و در کدی که این پایین میارم، اول از فایل کانفیگ XML که مسیرش در ورودی تابع گرفته میشه، تنظیمات رو میگیرم و سپس با این تنظیمات (در صورت موجود بودن!) شیء درست شده از کلاس ام رو مقداردهی می کنم :
/// <summary>
/// initialize default values from XML base settings
/// configuration file beside service excecutable ...
/// </summary>
private bool InitializeDefaultSettings(string configPath = "MyConfigs.config")
{
try
{
// Load setting configurations to static dictionary ...
if (Settings.SharedConfigItems.Count < 5)
{
Configuration configs = new Configuration(configPath);

if (!configs.Loaded)
{
if (Tracer.InitLogger())
{
Tracer.AddEventLogListener();
Tracer.LogError("Can't load configuration Xml file, there essential and have to exist. Service f****d and doomed!",
this.ToString(),
(new System.Diagnostics.StackFrame()).GetMethod().Name) ;
}
return false;
}
}

Dictionary<string, string> traceSettings = Settings.GetTracerSettings();

// Initializing global tracing system and specifying with default values ...
if (Tracer.InitLogger(
(traceSettings.ContainsKey("LogFile")) ? traceSettings["LogFile"] : "MyLogs.log",
(traceSettings.ContainsKey("DisplayName")) ? traceSettings["DisplayName"] : "sourceSwitch",
(traceSettings.ContainsKey("SourceLevel")) ? traceSettings["SourceLevel"] : "All"
))
{
AddLog("Global tracing system Initialized with default values ...");

if (traceSettings.ContainsKey("EventLog"))
{
if (Tracer.AddEventLogListener(traceSettings["EventLog"]))
AddLog("EventLogListener added to the TraceSource's Listener(s) list.");
else
AddLog(@"Can't add EventLogListener to the TraceSource's Listener's list,
probably you don't have Admin permissions; you must Run application/service as administrator to have Event viewer logs ...", TraceEventType.Warning);
}

if (traceSettings.ContainsKey("Console"))
Tracer.AddConsoleListener(false, traceSettings["Console"]);

AddLog("Initializing default values from: " + configPath);
}
return true;
}
catch (Exception ex)
{
Tracer.LogError(
ex.Message,
this.ToString(),
(new System.Diagnostics.StackFrame()).GetMethod().Name) ;
throw;
}
}

در پایانِ مباحث ابتدایی؟ همانطورکه در پست قبلی گفتم، بیشتر در این آموزش هدف بر رفع نواقص بوده و از اساتید محترم عاجزانه خواهشمندم نظر لطفی بر مرقومات این حقیر بنمایند، ضمناً هرکس هم که تا اینجا فقط دچار :متعجب: شده است که اینها چیستند شیخنا! باید بگم یکمی که سیستم هایی که نوشتین رفت دست مشتری و شما هر 2 هفته یکبار خط عوض کردید، متوجه میشوید که اونقدرها هم مباحث مزخرف و بیربطی نیستند و کار 10 تا پشتیبان بدین طریق کم میشود و ...

FastCode
چهارشنبه 17 خرداد 1391, 16:34 عصر
سلام.
خیلی خوبه.
روی مونو هم تست شده؟

Saeed_m_Farid
چهارشنبه 17 خرداد 1391, 16:51 عصر
روی mono تست شده و bug هست که در میاد:لبخند: : 1 (http://lists.ximian.com/pipermail/mono-devel-list/2011-January/036669.html) و 2 (http://www.mail-archive.com/mono-bugs@lists.ximian.com/msg62916.html) و ... (http://www.mail-archive.com/mono-bugs@lists.ximian.com/msg82417.html)

h_assefi
پنج شنبه 12 اردیبهشت 1392, 10:47 صبح
سلام دوست عزیز
مطالبتون را کامل خوندم و واقعا به یک همچین چیزی نیاز دارم اما خیلی کم از چیزهایی که گفتید را فهمیدم
منبع کاملی توی این زمینه وجود داره که از ابتدای کار توضیح داده باشه ؟ اگه هست میشه معرفی کنید تا من آن را از ابتدا بخونم ؟
ممنون میشم اگه راهنمایی کنید

FastCode
پنج شنبه 12 اردیبهشت 1392, 16:03 عصر
روی mono تست شده و bug هست که در میاد:لبخند: : 1 (http://lists.ximian.com/pipermail/mono-devel-list/2011-January/036669.html) و 2 (http://www.mail-archive.com/mono-bugs@lists.ximian.com/msg62916.html) و ... (http://www.mail-archive.com/mono-bugs@lists.ximian.com/msg82417.html)
شماره ۲ درست شده.
Fixed in r133981.