ארכיון

ארכיון הכותב

log4net – המדריך למלגלג (חלק 2 – הגדרות וקוד)

28 אוקטובר, 2009 אין תגובות

הרעיון של הפוסט הזה הוא פשוט: מעין cookbook שמסביר איך להתחיל עם log4net מההתחלה, מתוך הנחה שכבר יש לכם Visual Studio 2005 ומעלה (או כל IDE אחר לפיתוח בדוט נט בגירסה 2.0 ומעלה)

אז מה יהיה לנו:

  • הורדה של log4net
  • דוגמה פשוטה של כתיבה ללוג באמצעות log4net ע"י שמירה לקובץ טקסט
  • כתיבה של exception
  • ומה עוד?

מת-חי-לים!

הורדה והתקנה של log4net

דבר ראשון – ההורדה של log4net – כדי להוריד את גירסה 1.2.10 אפשר פשוט ללחוץ כאן. לפרטים נוספים – כאן תמצאו את דף ההורדות של log4net ב Apache.

הורדתם? יופי. עכשיו לעשות unzip לתיקיה חביבה (שתשרת אתכם די הרבה). נניח C:\oss\log4net.

תוכנית ראשונה עם log4net

נמשיך:

ניצור פרוייקט פשוט ב VS, נניח Console Application, ונכתוב בו את ה hello world המפורסם:

static void Main(string[] args)
{
  Console.WriteLine("Hello world");
  Console.ReadLine();
}

עכשיו נכניס פנימה שירותי logging ע"י log4net:

1. נוסיף רפרנס ל log4net תוך שימוש בתיקיה ההיא, לנתיב bin\net\2.0\release\log4net.dll

2. כדי להגדיר את log4net דרך קובץ חיצוני (ולא דרך קוד):

א. נוסיף קובץ app.config (מה שנקרא "Application Configuration File")

ב. נערוך את ה app.config באופן הבא:

<configuration>
    <configSections>
        <section name="log4net" type="System.Configuration.IgnoreSectionHandler"/>
    </configSections>

    <log4net>

        <appender name="OhMyGodItsTheFileAppender" type="log4net.Appender.FileAppender">
            <file value="mylog.txt" />
            <appendToFile value="true" />
            <layout type="log4net.Layout.PatternLayout">
                <conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] – %message%newline" />
            </layout>
        </appender>

        <root>
            <level value="DEBUG" />
            <appender-ref ref="OhMyGodItsTheFileAppender" />
        </root>
    </log4net>
</configuration>

3. כדי ש log4net יתחיל לעבוד ויצא מתרדמת החורף שלו, צריך איזה טריגר שיעיר אותו. אפשר לעשות את זה דרך הקוד, ואפשר בצורה קצת יותר שקופה, ע"י שימוש בקובץ די ביישן בשם AssemblyInfo.cs. זה קובץ שנמצא מתחת ל Properties. שם צריך להוסיף את השורה הבאה (אחרי כל ה using שיש שם, במיקום כלשהו):

[assembly: log4net.Config.XmlConfigurator(Watch = true)]

4. עכשיו, כדי לכתוב ללוג, אנחנו צריכים להשתמש באיזה אובייקט שיתן לנו את שירותי ה logging. את זה עושים, למשל, ע"י הוספת שדה סטטי ל class שלנו (ולכל class שבו נצטרך logging):

א. להוסיף בחלק של ה using את שני הבחורים הבאים (אם אינם):

using System.Reflection;
using log4net;

ב. להוסיף את השדה הסטטי עצמו:

private static readonly ILog Logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

זהו, עכשיו אפשר להשתמש ב logger שלנו. הפעולות 1-3 הנ"ל הן פעולות חד פעמיות לפרויקט שלנו. פעולה מספר 4 חוזרת על עצמה בכל class בפרויקט. הנה קצת תוספות לקוד:

static void Main(string[] args)
{
  Logger.Debug("starting up");
  Console.WriteLine("Hello world");
  Logger.Debug("done");
  Console.ReadLine();
}

נריץ את הפרויקט. ונראה את ה hello world הידוע לשמצה. ואיפה כל ההודעות שרשמנו ללוג? הן נמצאות בתוך הקובץ mylog.txt, שנמצא במיקום של ה exe של הפרוייקט שלנו: בד"כ בנתיב bin\debug או bin\release. אפשר לשנות את שם הקובץ mylog.txt לכל שם אחר. ויש עוד הרבה הגדרות, לא לדאוג…

בשלב זה יש לנו תוכנית עם logger, שמבצעת כתיבות ללוג, שלמעשה נשמרות לקובץ. זה הבסיס ל-log4net. זה המתכון, אם תרצו, להוספת log4net לפרוייקט שלכם בצורה יחסית פשוטה ופורטבילית.

כתיבת exception ללוג

לכתוב exception ללוג זה לא סיפור גדול: זה כמעט אותו הדבר כמו כתיבה רגילה, רק משתמשים בחתימה שכוללת אובייקט מסוג Exception. מן הסתם, זה יהיה בתוך try/catch. הנה דוגמה קטנה:

static void Main(string[] args)
{
  Logger.Debug("starting up");
  string fileName = "file-does-not-exist.txt";
  try
  {
    string contents = System.IO.File.ReadAllText(fileName);
  }            
  catch (Exception ex)
  {
    Logger.Error("cannot read contents of file " + fileName, ex);
    Console.WriteLine("oops, an error occured. see log for details");
  }

  Logger.Debug("done");
  Console.ReadLine();
}

מומלץ מאוד להשתמש במתודות עם חתימת ה Exception, כי ה exception נשמר קצת אחרת, וזה מאפשר לבצע חתכים אחרים ולתצוגה שונה דרך ה viewer (בפוסט הבא).

לאן ממשיכים מכאן

אפשר לכתוב ל-EventLog. שימו לב שזה דורש הרשאות גבוהות יחסית.

אפשר לכתוב ל-Trace ולעקוב אחריו עם כלים חיצוניים כמו DebugView של SysInternals. זה שקול ל-System.Diagnostics.Trace.WriteLine

ואפשר עוד הרבה דברים, כמו לכתוב ל DB, לכתוב ל TCP, ל UDP, לשלוח מייל ועוד. גשו לדף הדוגמאות ב Apache ותהנו.

אגב, לפי ההגדרות שבפוסט הזה, אפשר לשנות בזמן ריצה את ההגדרות של ה log4net – וההתנהגות של ה logging תשתנה בהתאם. מאגניביישן.

לסיכום, זה פוסט שכל המטרה שלו היא לתת מתכון פשוט לשימוש ב log4net. בפוסט הבא נראה איך אפשר לנתח את הלוגים, ע"י viewer כמו Chainsaw.

לגלגו ותהנו 🙂

פרויקט דוגמה

קטגוריות:תכנות תגיות:

log4net – המדריך למלגלג (חלק 1 – מבוא)

23 אוקטובר, 2009 אין תגובות

בפוסט הזה:

  • מבוא
  • למה בכלל לכתוב ללוג?
  • סוגי הודעות לפי רמות חוּמרה
  • מה כותבים ללוג

מבוא

חלק בלתי נפרד מאפליקציה רצינית הוא הלוג – שירות שמאפשר כתיבת הודעות.
בזמן שהאפליקציה חיה ונושמת היא יכולה לכתוב הודעות שונות ללוג. ההודעות יכולות להיות בסגנון "עכשיו מתבצע כך וכך" או "יש כאן שגיאה לא צפויה".
הלוג עצמו יכול להישמר כקובץ, או קבצים, או כרשומות ב DB.
או שהלוג יכול לא להישמר כלל, אלא רק סוג של Console.WriteLine או Debug.WriteLine או Trace.WriteLine.
או שהלוג שולח את ההודעות באמצעות TCP או UDP למקום אחר.
או שהלוג שולח מייל למישהו…
בקיצור, יש את העניין של לכתוב ללוג ("יש כאן שגיאה לא צפויה"), ויש את העניין של מה תהיה התוצאה של הכתיבה ללוג (ההודעות נשמרות/מוצגות וכד').
בנוסף לכל זה, גם אם כתבנו ללוג והכל טוב ויפה, נרצה אח"כ גם לצפות בתוצאה, ולעשות חיתוכים שונים. במילים אחרות, נרצה איזה Viewer – תוכנה נורמלית עם UI חביב שתאפשר לנו את התצוגה והחיתוכים.
אה, ועוד משהו. נרצה גם לוג מספיק חכם כך שאם נרצה לשנות את ההגדרות שלו לא נצטרך להוריד את האפליקציה ולטעון אותה מחדש.

אז מה היה לנו: הודעות ללוג ואיך הן "נכתבות", תוכנת viewer, טעינה חכמה אחרי שינוי ההגדרות.
כל זה ניתן למימוש בקלות עם log4net – תשתית מוכחת ועובדת ללוג, שהיא open source. אבל לפני הכל, עוד קצת מבוא.

למה בכלל לכתוב ללוג?

שאלה טובה.

לא חייבים.

אבל הנסיון שלי מראה שכתיבה מושכלת ללוג מאפשרת למפתחים להבין באגים (ולפתור אותם) מהר יותר, בהשפעה אפסית על הביצועים.

בנוסף, קהל היעד של הלוג הוא לא רק מפתחים. גם אנשי IT או אנשים שמתחזקים את האפליקציה יכולים להעזר בו.

בשורה התחתונה, כתיבה ללוג היא גם כדי לוודא שהאפליקציה עובדת כמצופה וגם כדי לתעד מצבים לא תקינים ואת אפשרויות הטיפול בהם.

סוגי הודעות לפי רמות חוּמרה

רגע לפני שמתחילים, כמה מילים על ההודעות השונות שאנחנו מכניסים ללוג.

ב log4net, ולא רק שם, ההודעות ללוג מתחלקות לסוגים הבאים:

  • Debug
  • Info
  • Warn
  • Error
  • Fatal

נרחיב קצת…

הודעות Debug (ורק הן) מיועדות למפתחים עצמם, ואפשר להשתולל איתן כמה שרוצים. כפי שנראה מאוחר יותר, אפשר לשנות את ההגדרות כך ש log4net יתעלם מהן לחלוטין. בד"כ כותבים ל Debug הודעות כמו "עכשיו לפני פתיחת חיבור ל DB" ו"התחברתי בהצלחה ל DB" וכו'. בקיצור, אבני דרך למפתחים עצמם.

הודעות Info מכילות מידע מתומצת על המצב של האפליקציה, שהוא בחזקת "טוב לדעת". לדוגמה, כמעט כל service (או console שמדמה service) שכתבתי בדוט נט קורא את הקונפיגורציה ומיד אח"כ כותב הודעת info ללוג בנוסח "שירות xyz עלה בהצלחה עם ההגדרות הבאות: …".

הודעות Warn מתייחסות למקרים שבהם האפליקציה נתקלה ב exception או במצב לא תקין, אבל היא יודעת להתאושש ממנו. למשל, המערכת שלנו לא מצליחה להוציא מייל כרגע. לא נורא, היא תנסה שוב עוד דקה.

הודעות Error מתייחסות למקרים שבהם האפליקציה נתקלת ב exception או במצב לא תקין והיא לא יודעת להתאושש ממנו. זה יכול להשפיע רק על המשתמש הנוכחי (נניח אם זו אפליקציית web) וזה יכול להיות משהו יותר תשתיתי.

הודעות Fatal מתייחסות למקרי קצה שבהן האפליקציה שלנו לא יכולה להתאושש ממשהו לא תקין וכדאי שמישהו יסדר את זה בהקדם האפשרי. כלומר יש כאן גם הנחיה לגבי ניתוח הלוג וטיפול בשגיאות. הודעות Fatal צריכות להיות בודדות כדי לא ליצור מצב של "זאב-זאב".

מה כותבים ללוג

אם אתם לוקחים אתכם משהו מהפוסט הזה – קחו את זה: לא רק כותבים ללוג "היתה כאן שגיאה" ומצרפים את האובייקט של ה exception. כותבים את כל הקונטקסט הרלוונטי כדי שאח"כ נוכל להבין טוב יותר מה קרה. למשל "לא הצלחתי לקרוא את התוכן של הקובץ X כאשר הבקשה היתה Y" בצירוף ה exception. כן, זה לוקח עוד דקה, אבל זה כל כך שווה את זה.

אלא שלפעמים, מטעמי security או privacy לא כדאי לכתוב ממש הכל. למשל, "לא הצלחתי להתחבר ל SQL Server שנמצא בכתובת X עם שם משתמש U וסיסמה P" נשמע לי רעיון לא טוב, כי זה חושף את פרטי ההתחברות ל DB כולל הסיסמה. אפשר אולי במקום לכתוב את הסיסמה לכתוב את ה hash שלה לפי הדוט נט (GetHashCode), או לפי סטנדרט חזק יותר כמו MD5, כאשר כותבים את ערך ה hash ב base64 (וגם אז, אפשר לכתוב רק את N התוים הראשונים מטעמי חסכון ונוחות קריאה).

זהו, עד כאן המבוא. בפוסטים הבאים – איך משתמשים ב log4net כדי לכתוב ללוג, איזה viewer-ים קיימים ועוד.

קטגוריות:תכנות תגיות:

System.String is UTF16

26 ספטמבר, 2009 תגובה אחת

והפעם, טיפה Encoding 🙂

תדע כל מתכנתת עבריה: כל string בדוט נט הוא ב encoding של UTF16.

ואגב, זו הסיבה שסיריאליזציה ל XML היא כברירת מחדל UTF16.

"ואם אני רוצה string עם encoding אחר?"

-אין.

אין כזה דבר בדוט נט.

רוצה string? -יש רק ב encoding של UTF16.

רוצה משהו אחר (נניח, UTF32)? יש בייצוגים אחרים של הטקסט: byte array למשל.

הנה קצת קוד:

using System;
using System.Text;
namespace Playing.With.Strings
{
  class Program
  {
    static void Main(string[] args)
    {
      string myText = "hello";

      byte[] asciiBytes = Encoding.ASCII.GetBytes(myText);
      Console.WriteLine("ASCII bytes:");
      Console.WriteLine(ByteArrayToString(asciiBytes));
      Console.WriteLine(Encoding.ASCII.GetString(asciiBytes));

      byte[] unicodeBytes = Encoding.Unicode.GetBytes(myText);
      Console.WriteLine("Unicode bytes:");
      Console.WriteLine(ByteArrayToString(unicodeBytes));
      Console.WriteLine(Encoding.Unicode.GetString(unicodeBytes));

      byte[] utf32Bytes = Encoding.UTF32.GetBytes(myText);
      Console.WriteLine("UTF32 bytes:");
      Console.WriteLine(ByteArrayToString(utf32Bytes));
      Console.WriteLine(Encoding.UTF32.GetString(utf32Bytes));

      Console.WriteLine("done, press *enter* to quit…");
      Console.ReadLine();
    }

    public static string ByteArrayToString(byte[] source)
    {
      StringBuilder sb = new StringBuilder();
      sb.Append("[");
      int sourceLength = source.Length;
      for (int k = 0; k < sourceLength; k++)
      {
        sb.Append(source[k]);
        if (k != sourceLength 1)
          sb.Append(", ");
      }
      sb.Append("]");
      string result = sb.ToString();
      return result;
    }
  }
}

והתוצאה היא:

ASCII bytes:
[104, 101, 108, 108, 111]
hello
Unicode bytes:
[104, 0, 101, 0, 108, 0, 108, 0, 111, 0]
hello
UTF32 bytes:
[104, 0, 0, 0, 101, 0, 0, 0, 108, 0, 0, 0, 108, 0, 0, 0, 111, 0, 0, 0]
hello
done, press *enter* to quit...

בשורה התחתונה, כדי לייצג טקסט עם encoding אחר, אני בד"כ משתמש ב byte array.

קטגוריות:תכנות תגיות:,

תור מקבילי – Concurrent Queue

28 יוני, 2009 3 תגובות

רוב המתכנתים מכירים מבני נתונים בסיסיים, כמו מערך, רשימה מקושרת, וגם תור ומחסנית. הבעיה מתחילה כשרציתי למקבל תור. כלומר, ליצור מחלקה עם התכונות של תור, לצד העובדה שיכולים לגשת אליה במספר ת'רדים במקביל (אני לא מת על התרגום ל"חוטים", ומי שרוצה לתת תרגום אחר – תעירו חופשי).

נשמע פשוט, נכון? כולה לעטוף תור קיים ומוכח בנעילה קטנה, ויש לך תור מקבילי.

אז זהו, שלא.

כדי להמחיש את העניין, הנה המימוש הנאיבי ("כולה לעטוף תור קיים" וכו' וגו') של תור מקבילי:

public class NaiveConcurrentQueue<T>
{
  private Queue<T> queue = new Queue<T>();

  public void Enqueue(T item)
  {
    lock (queue)
    {
      queue.Enqueue(item);
    }
  }

  public T Dequeue()
  {
    lock (queue)
    {
      return queue.Dequeue();
    }
  }
}

בכוונה אין כאן את כל המתודות והממברים של תור, רק את השתיים האלו, כי זאת רק דוגמה. אז כאמור, רק נעילה סביב כל מתודה/ממבר של התור הפנימי, ויש לנו מקבול. אבל חבר'ה חבר'ה, רגע רגע, מה זה בדיוק נעילה? (לא, מוטל, התפילה המסיימת ביום כיפור זה לא מה שהתכוונתי)

נעילה בקוד, או lock, זה סוג של מאקרו ברמת הקומפיילר, שמאפשר לנו, המתכנתים העצלנים, לכתוב קוד שאמור לרוץ ע"י ת'רד אחד בכל פעם. למה מאקרו? כי פיסת הקוד הבאה:

lock (x)
{
  DoSomething();
}

למעשה הופכת להיות הקוד הבא:

System.Object obj = (System.Object)x;
System.Threading.Monitor.Enter(obj);
try
{
  DoSomething();
}
finally
{
  System.Threading.Monitor.Exit(obj);
}

נו, אז? סה"כ עטיפה נוחה ל Monitor.Enter ול Monitor.Exit.

אז בואו נקרא עוד קצת על Monitor.Enter. מתוך MSDN, כל עוד מדובר במערכת Win32, הוא ממופה לפקודה EnterCriticalSection. יופי, בואו נקרא עוד קצת על EnterCriticalSection, שוב מתוך MSDN. נתמקד במשפט אחד:

There is no guarantee about the order in which waiting threads will acquire ownership of the critical section

הממממם. מעניין. אז בואו נסתכל רגע על המצב הבא:

בזמן T0, הת'רד הנוכחי נועל משאב X, ומבצע משימה שלוקחת 5 שניות.

אחרי 2 שניות (נקרא לזה זמן T2), ת'רד נוסף, שנקרא לו מנדל, גם רוצה לנעול את משאב X, ולבצע משימה קטנטונת, שלוקחת 0.01 שניה. מכיוון ש X כבר נעול, מנדל מחכה.

שניה אחרי T2 (נקרא לזה זמן T3), ת'רד נוסף, שנקרא לו צביקי, גם רוצה לנעול את משאב X, ולבצע משימה קטנטונת, שלוקחת 0.01 שניה. שוב, מכיוון ש X כבר נעול, גם צביקי מחכה.

שניה אחרי T3 (נקרא לזה זמן T4), ת'רד נוסף, שנקרא לו פושקש, גם רוצה לנעול את משאב X, ולבצע משימה קטנטונת, שלוקחת 0.01 שניה. ושוב, מכיוון ש X עדיין נעול, גם פושקש מחכה.

עכשיו, סוף סוף עברו 5 שניות מאז T0, ומשאב X משתחרר. יש כרגע שלושה ת'רדים שממתינים ל X: מנדל, צביקי ופושקש. לפי התיעוד למעלה, סדר הביצוע של הת'רדים הוא לא בהכרח סדר ההגעה שלהם. כלומר, זה יכול להיות מנדל-צביקי-פושקש, וזה יכול גם להיות מנדל-פושקש-צביקי, וכל קומבינציה אחרת. בדקתי את העניין, וכתבתי תוכנית קטנה שממחישה את זה. ברוב ההרצות הסדר נשמר. בחלק מההרצות הסדר השתנה. זה מספיק כדי להמחיש שהסדר לא בהכרח נשמר.

בעיה. כי בדיוק רציתי תור, כלומר מבנה נתונים שישמור גם על סדר ההגעה של הת'רדים. מעין message queue.

"ב retlang יש את זה מובנה", אמר לי רשף. בדקתי. retlang זו ספריה בדוט נט שהופכת את התיכנות האסינכרוני להרבה יותר פשוט, מבוסס הודעות, בלי הצורך המעיק בנעילות. כלומר, הספריה הזו עושה בשביל המתכנת את כל הנעילות שהוא צריך. המתכנת, מאידך, צריך ללמוד איך משתמשים בזה. אחרי כמה דוגמאות ומספר נסיונות – זה ממש בסדר. ובאמת יש שם channel שנקרא QueueChannel. הסתכלתי בקוד עצמו. יש שם שימוש ב lock. כלומר, גם שם יש את הבעיה הזו.

כמה מיילים לעולם, ומישהו הציע להסתכל על PLinq, שיש שם מימוש כזה. אבל אני לא יכול להסתכן בספריה כמו Plinq היא עדיין CTP, ועוד לא יצאה לאור באופן רשמי. פעם נכוויתי מספריה לא בשלה (Atlas, למי שזוכר), ומאז כבר למדתי את הלקח.

גוגל! גוגל יעזור לי. רק צריך לבקש, ובעיקר לדעת מה לבקש. אז גיגלתי lock free queue, וחיפשתי גם ב stackoverflow, ומצאתי עולם ומלואו. עולם שלם של אלגוריתמים ומבני נתונים שמיועדים לסביבה שהיא Multi-Threaded ושאין בהם נעילות רגילות, אלא לכל היותר Interlock. אז גיגלתי וריאציות של lock free queue, והנה כמה דברים שמצאתי:

קודם כל, כבר עשו את זה. בכנס שנקרא בקיצור PODC96 ובאריכות Fifteenth ACM Symposium on Principles of Distributed Computing, מתואר מבנה נתונים שמספק את דרישת FIFO, במאמר בעל השם הקצרצר: Simple, Fast, and Practical Non-Blocking and Blocking Concurrent Queue Algorithms. כותבי המאמר, מייקל וסקוט, או בקיצור MS, מתארים איך אפשר להשתמש בטכניקות קלות יותר מאשר נעילות (כמו CAS, שבדוט נט זה Interlocked.CompareExchange) כדי לקבל את התוצאה הרצויה. התור (או הרעיון המרכזי שלו) נקרא בהרבה מקומות אחרים MS-Queue. לשם שינוי ה MS מציין את שמות המשפחה מייקל וסקוט, ולא חברת תוכנה ענקית.

יש כבר מימוש לזה בשפות רבות, וגם בדוט נט (C# ליתר דיוק). הבחורצ'יק הזה נותן לנו מימוש מלא.

שני ישראלים, אחד בשם ניר שביט ואחת בשם Edya Ladan Mozes (ואיך מתרגמים את זה לעברית?) מציגים במאמר בשם An Optimistic Approach to Lock-Free FIFO Queues שאפשר לייעל את האלגוריתם המקורי של MS-Queue ולהגיע ל throughput גבוה יותר (בין השאר). מה שנקרא, אנחנו על המפה. אה כן, הנה הלינק לרשימת המאמרים המלאה באתר של ניר שביט.

מה שנקרא, החכמנו.

Edya Ladan Mozes

קטגוריות:כללי תגיות:
Quantcast