log4net and azure

11 בדצמבר 2011

אין תגובות


 


בדרך כלל אני משתמש בתשתית log4net כדי לשמור לוגים, אני אוהב לשמור אותם אותם לקבצים ואני משתמש ב – RollingFileAppender.

 

כמובן שכשעובדים עם windows azure אי אפשר לעבוד בצורה כזו, מכיוון שכדי לקרוא את הלוגים נצטרך להכנס למכונה ב – remote וגרוע מכך בדרך כלל יש יותר ממכונה אחת – מה שאומר שהלוגים נשמרים על מכונות שונות, וכמובן אחר restart שלהם לא יהיה זכר ללוגים.

 

 

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

 

לכן חשבתי על הפיתרון הבא:

1. נכתוב את הלוג לבסיס נתונים.

2. פעם ביום נעביר את המידע משם ל – storage, כך נוודא שבסיס הנתונים לא גודל ולא כותבים ל – storage יותר מפעם ביום,

3. כדי לא להעמיס יותר מידי על בסיס הנתונים, נשמור את הלוגים לזיכרון, ובתהליך נפרד אחת לדקה נעביר את כל הלוגים לבסיס הנתונים.

 

 

את הפיתרון והקוד המלא (הדוגמא בבלוג לא מכילה את הקוד המלא) ניתן להוריד כאן.

(הדוגמא מכילה גם קוד sql ליצירת בסיס נתונים, לא לשכוח לשנות בקונפיגים את ההפנייה לבסיס הנתונים)

 

 

הקוד יראה כך:

 

 

ראשית נבנה בסיס נתונים שנראה כך:

 

log object

 

 

נייצר פרוייקט אשר נקרא לו Logging, הוא יהיה הפרוייקט היחידי אשר יחזיק reference ל – log4net.dll, שאר הפרוייקטים רק יצטרכו לכתוב בקונפיג את הגדרות ה – logging.

 

(בדוגמא השימוש הוא בעזרת entity framework 4.0)

 

אחרי שתייבאו את בסיס הנתונים למודל, נרצה לכתוב קוד שיודע להמיר בין אובייקט של log4net לבין אובייקט Log של המודל, נכתוב את הקוד הבא

 



public partial class Log


{


    public override string ToString()


    {


        return string.Format("{0} about {1} on {2} at {3}\r\n{4}\r\n{5}\r\n\r\n",


            this.Level,


            this.Project,


            this.Instance,


            this.Date,


            this.Message,


            this.Exception);


    }


 


    public static implicit operator Log(LoggingEvent le)


    {


        return new Log()


        {


            Date = le.TimeStamp,


            Exception =  le.GetExceptionString(),


            Message = le.MessageObject != null ? le.MessageObject.ToString() : "",


            Instance = 0,


            Level = le.Level.Name,


            Project = AssemblyName


        };


    }


 


    private static string _name;


 


    private static string AssemblyName


    {


        get


        {


            if (_name == null)


            {


                _name = Assembly.GetExecutingAssembly().GetName().Name;


            }


 


            return _name;


        }


    }


}


 

נשתמש במנגנון partial calss כדי להוסיף יכולות למודל שלנו.

נדרוס את מתודת ToString כדי לקבל מחרוזת "אנושית" עבור כל שגיאה,

לאחר מכן נשתמש במנגון implicit casting כדי להמיר בין אובייקט של LoggingEvent (ששיך ל – log4net) לבין אובייקט Log (ששיך למודל).

 

 

כעת נוסיף מחלקה בשם Logger שיהיו לה שני תפקידים, ראשית – היא תעשה עטיפה לכל הפונקציות של log4net, כדי שניתן יהיה לכתוב ללוג (לא מופיע בדוגמא רק בקוד להורדה),

שנית – היא תחזיק timer אשר בכל פעימה תיקח את כל הנתונים מהזיכרון (כזכור אנחנו משתמשים ב – log4net ושומרים את המידע בזיכרון) ותשפוך אותם לבסיס הנתונים,

הקוד יראה כך:

 



public static class Logger


{


    internal static readonly ILog _log;


    private static Timer _timer;


 


    public static int Period


    {


        get


        {


            int period;


 


            if (int.TryParse(ConfigurationManager.AppSettings["LogPeriod"], out period))


            {


                return period;


            }


 


            return 60000;


        }


    }


 


    static Logger()


    {


        XmlConfigurator.Configure();


        _log = LogManager.GetLogger("");


 


        _timer = new Timer(TimerCallback, null, 0, Period);


    }


 


    private static object lookObject = new object();


    private static void TimerCallback(object state)


    {


        try


        {


            lock (lookObject)


            {


                using (LogEntities context = new LogEntities("Logger"))


                {


                    MemoryAppender ap = (MemoryAppender)LogManager.GetLoggerRepository().GetAppenders()[0];


                    var logs = ap.GetEvents();


                    ap.Clear();


 


                    foreach (var item in logs)


                    {


                        context.Logs.AddObject(item);


                    }


 


                    context.SaveChanges();


                }


            }


        }


        catch (Exception ex)


        {


 


        }


    }


 


}

 


 

 

ב – static ctor נאתחל את הלוג כפי שיוגדר בקבצי הקונפיגורציה (נראה בהמשך).

 

המאפיין Period יאותחל כברירת מחדל על דקה – או בהגדרה בקונפיג. (מגדיר כל כמה זמן לאסוף את הנתונים מהזיכרון ולשמור בבסיס הנתונים).

 

וכמובן הפוקנציה של ה – timer, אוספת את הנתונים מה – Logger ושומרת אותם לבסיס הנתונים, (כל אובייקט מתוך GetEvents יהיה מסוג LoggingEvent, ובקריאה ל – AddObject הוא עובר המרה אוטומטית לאובייקט מסוג Log בעזרת הפונקציה מהמחלקה הקודמת שמשתמשת ב – Implicit casting.

 

 

כל מי שירצה לכתוב ללוג, יוסיף reference לפרוייקט זה, ויוכל לקרוא לפונקציות הכתיבה בלוג, הדבר היחיד שצריך לעשות זה להוסיף בקבצי הקונפיגורציה את הקוד הבא:

 



<configSections>


  <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />


</configSections>


<log4net>


  <appender name="MemoryAppender" type="log4net.Appender.MemoryAppender">


  </appender>


  <root>


    <level value="DEBUG" />


    <appender-ref ref="MemoryAppender" />


  </root>


</log4net>


 

 

הדבר האחרון שנשאר – הוא לוודא שפעם ביום נעביר את הלוגים מבסיס הנתונים ל – storage.

 

נייצר פרוייקט מסוג worker role ונכתוב את הקוד הבא:

 

 



public override void Run()


{


    while (true)


    {


        InitBlob();


        Thread.Sleep(86400000);


 


        using (LogEntities context = new LogEntities("Logger"))


        {


            var list = context.Logs.ToList();


            string str = "";


            foreach (var item in list)


            {


                str += (item.ToString());


                context.Logs.DeleteObject(item);


            }


 


            CloudBlob blob = _blobContainer.GetBlobReference(DateTime.Now.ToString("dd-MM-yyyy HH-mm"));


            blob.UploadText(str);


 


            context.SaveChanges();


        }


 


    }


}


 

הפונקציה InitBlob מאתחלת את המשתנים שמגדירים את הגישה ל – blob, לאחר מכן ניתן לתהליך לנוח כיום עבודה.

 

נאסוף את כל הנתונים מבסיס הנתונים, נשמור אותם ל – storage עם שם שמכיל את התאריך, ובא לציון גואל.

 

הוסף תגובה
facebook linkedin twitter email

כתיבת תגובה

האימייל לא יוצג באתר. שדות החובה מסומנים *