2013-03-05 46 views
0

我一直在審查和重構一個同事的代碼,用於開發執行一組作業的控制檯應用程序。我想就如何改善系統的入口點提出意見,讓它覺得它可以更強大一點。我們使用NLog進行日誌記錄,配置爲自動顯示在控制檯上以及日誌文件中。同樣,我有一個catch (Exception ex)來嘗試乾淨地處理和記錄所有可以忽略的異常 - 理論上它不應該被打到,但最好在可能的情況下乾淨地處理這些事情。如何改善控制檯應用程序入口點

我具有{0}:在每個_logger.Info()呼叫開始的記錄風格尤爲不滿,但如果重構到其自身的功能,像這樣LogMe(methodName, "text to be logged"),我真的不保存所有的東西打字。請記住,我故意忽略保持線程活躍的代碼等。這超出了我所尋找的範圍。

以下情況可以改進嗎?或者說這樣做是否「好」,因爲它可以合理地沒有廣泛的努力/重構?

static void Main(string[] args) 
{ 
    string methodName = string.Format("{0}.Main()", typeof(Program).FullName); 
    try 
    { 
     _logger.Info("{0}: Launched", methodName); 
     IKernel kernel = IOC.SetupKernel(); 

     _logger.Info("{0}: Reading job schedules from the configuration file"); 
     JobScheduleSection scheduleSection = (JobScheduleSection)ConfigurationManager.GetSection("jobScheduleSection"); 
     if (scheduleSection == null) 
     { 
      _logger.Warn("{0}: No job schedule section found in configuration file", methodName); 
      return; 
     } 

     List<IJobSchedule> schedules = scheduleSection.JobSchedules.ToList(); 
     if (schedules == null) 
     { 
      _logger.Info("{0}: No job schedules found", methodName); 
      return; 
     } 
     _logger.Info("{0}: Found {1} job schedules", methodName, schedules.Count); 

     _logger.Info("{0}: Kicking Launcher...", methodName); 
     Launcher launcher = new Launcher(kernel, schedules); 
     launcher.LaunchSchedulerService(); 
    } 
    catch (Exception ex) 
    { 
     _logger.ErrorException(string.Format("{0}: An unhandled exception occurred", methodName), ex); 
    } 
    finally 
    { 
     _logger.Info("{0}: Exited. Program complete."); 
    } 
} 
+3

想如果這是最適合的代碼審查堆棧交換:codereview.stackexchange.com .... – Arran 2013-03-05 15:10:35

+0

@Arran,可能但它不是我的本意正好審查 - 但我會動如果詢問 – DiskJunky 2013-03-05 15:14:43

回答

1

更新

I f找到一個更乾淨的解決方案,而不是嘗試擴展NLog類或以其他方式創建方法/方法重載。 NLog支持在您的應用程序中部署的NLog.config文件中添加的以下字段;

layout="${callsite}" 

這可以應用到目標爲準適合你,CSV,控制檯,電子郵件等。在CSV,配置等;

<target name="CSVFile" xsi:type="File" fileName="${basedir}/Logging/BullorBear.Identity.API-${date:format=yyyy-MM-dd}.csv" 
     archiveEvery="Day" maxArchiveFiles="28"> 
    <layout xsi:type="CSVLayout"> 
    <column name="Index" layout="${counter}" /> 
    <column name="Time" layout="${longdate}" /> 
    <column name="Callsite" layout="${callsite}" /> 
    <column name="Severity" layout="${level:uppercase=true}" /> 
    <column name="Detail" layout="${message}" /> 
    <column name="Exception" layout="${exception:format=ToString}" /> 
    </layout> 
</target> 

輸出;

Index,Time,Callsite,Severity,Detail,Exception 
1,2013-03-12 12:35:07.6890,ProjectName.Controllers.SomeController.SomeMethod,INFO,Authenticating..., 
3

我這樣做的方法是創建一個NLOG包裝類,這將包裹每個日誌法,obuscate方法名拿走並使用堆棧跟蹤對象獲取方法名。那麼你不必每次都寫下來;調用Logging包裝器方法的方法名稱將自動注入。

它會看起來更乾淨,因爲無處不在{0}和methodName。

您甚至還可以進一步創建一個日誌記錄包裝類,該日誌包含一個Action日誌字符串和一個Action,執行Action並使用StackTrace對象調用日誌對象。

我已經使用它來執行時間操作並記錄它們,在一次調用中完成所有操作並保存重複代碼很方便。我的方法ExecuteTimedAction(字符串logString,Action actionToExecute)使用秒錶,記錄一個起始字符串,啓動秒錶,執行該方法(Action委託),停止秒錶,並且再次記錄具有時間戳記,組件名稱,以及呼叫從中啓動的方法的名稱。

獲取方法的代碼很簡單,使用StackTrace對象並獲取前一個調用的StackFrame。

 var stackTrace = new StackTrace(); 
     var callingMethodName = stackTrace.GetFrame(2).GetMethod().Name; 

注意我上面有2個硬編碼,但這是因爲一個額外的包裝調用;如果您直接撥打電話,則可能需要使用GetFrame(1)。最好的方法是使用即時窗口並嘗試不同的框架,或使用StackTrace對象的GetFrames()方法循環查看您獲得的內容。

我正在尋找保持字符串格式的參數,併爲日誌封裝添加第一個參數。這是可以做到這樣的事:

public static class LogWrapper 
{ 
    private static Logger _logger // where Logger assumes that is the actual NLog logger, not sure if it is the right name but this is for example 

    public static void Info(string logString, object[] params) 
    { 
     // Just prepend the method name and then pass the string and the params to the NLog object 
     _logger.Info(
      string.Concat(
       GetMethodName(), 
       ": ", 
       logString 
      ), 
      params 
     ); 
    } 

    public static void Warn(string logString, object[] params) 
    { 
     // _logger.Warn(
     // You get the point ;) 
     //) 
    } 

    private static string GetMethodName() 
    { 
     var stackTrace = new StackTrace(); // Make sure to add using System.Diagnostics at the top of the file 
     var callingMethodName = stackTrace.GetFrame(2).GetMethod().Name; // Possibly a different frame may have the correct method, might not be 2, might be 1, etc. 
    } 
} 

然後在你的調用代碼,在_logger成員成爲LoggerWrapper,不記錄儀,並調用它完全相同的方式,但你刪除{0}從代碼。你需要檢查是否有空值,也許如果沒有其他參數,那麼只需要調用沒有參數的方法重載;我不確定NLog是否支持,所以你必須檢查這個。

...編輯:

只是爲了興趣點我使用這種類型的代碼在可能被一大堆程序集引用的程序集公共庫類型,所以我可以得到的信息,如調用程序集,方法名稱等,而不需要在我的日誌代碼中對它進行硬編碼或擔心它。它也確保了其他使用代碼的人不必擔心它。他們只是調用Log()或Warn()或其他任何東西,程序集會自動保存在日誌中。

下面是一個例子(我知道你說過分誇大你,但如果你可能需要這樣的東西,可以爲將來思考食物)。在這個例子中,我只記錄程序集,而不是方法名,但它可以很容易地組合。

#region :   Execute Timed Action      : 

    public static T ExecuteTimedAction<T>(string actionText, Func<T> executeFunc) 
    { 
     return ExecuteTimedAction<T>(actionText, executeFunc, null); 
    } 

    /// <summary> 
    /// Generic method for performing an operation and tracking the time it takes to complete (returns a value) 
    /// </summary> 
    /// <typeparam name="T">Generic parameter which can be any Type</typeparam> 
    /// <param name="actionText">Title for the log entry</param> 
    /// <param name="func">The action (delegate method) to execute</param> 
    /// <returns>The generic Type returned from the operation's execution</returns> 

    public static T ExecuteTimedAction<T>(string actionText, Func<T> executeFunc, Action<string> logAction) 
    { 
     string beginText = string.Format("Begin Execute Timed Action: {0}", actionText); 

     if (null != logAction) 
     { 
      logAction(beginText); 
     } 
     else 
     { 
      LogUtil.Log(beginText); 
     } 

     Stopwatch stopWatch = Stopwatch.StartNew(); 
     T t = executeFunc(); // Execute the action 
     stopWatch.Stop(); 

     string endText = string.Format("End Execute Timed Action: {0}", actionText); 
     string durationText = string.Format("Total Execution Time (for {0}): {1}", actionText, stopWatch.Elapsed); 

     if (null != logAction) 
     { 
      logAction(endText); 
      logAction(durationText);     
     } 
     else 
     { 
      LogUtil.Log(endText); 
      LogUtil.Log(durationText); 
     } 

     return t; 
    } 

    public static void ExecuteTimedAction(string actionText, Action executeAction) 
    { 
     bool executed = ExecuteTimedAction<bool>(actionText,() => { executeAction(); return true; }, null); 
    } 

    /// <summary> 
    /// Method for performing an operation and tracking the time it takes to complete (does not return a value) 
    /// </summary> 
    /// <param name="actionText">Title for the log entry</param> 
    /// <param name="action">The action (delegate void) to execute</param> 

    public static void ExecuteTimedAction(string actionText, Action executeAction, Action<string> logAction) 
    { 
     bool executed = ExecuteTimedAction<bool>(actionText,() => { executeAction(); return true; }, logAction); 
    } 

    #endregion 

在這裏,日誌功能看起來是這樣的,你可以看到我的日誌功能是不是硬編碼到ExecuteTimedAction,這樣我就可以通過任何日誌行動吧。

在日誌I類在靜態變量一旦保存條目集名稱,並用它爲所有的日誌...

private static readonly string _entryAssemblyName = Assembly.GetEntryAssembly().GetName().Name; 

希望這給你足夠的深思一些重構!

+0

包裝記錄器的有趣方法 - 你有一些簡短的示例代碼?秒錶方法對我們所需要的東西有點矯枉過正,但是對於更原子化,基於任務的部分,這絕對是我記住的事情,因爲記錄器已配置爲記錄每個條目的日期/時間 – DiskJunky 2013-03-05 15:30:26

+0

@DiskJunky - 現在寫一個樣本... – 2013-03-05 15:33:22

+0

有趣!這是什麼性能打擊還是有一個?如果項目編譯爲發佈,是否會引發異常? – DiskJunky 2013-03-05 15:49:04

2

我不特別喜歡這種包裝NLog的方式。 GetMethodName沒有任何理由。 NLog有能力自動提供方法名稱和類名(通過正確配置佈局)。當包裝NLog(或者log4net)時,關鍵是按照NLog.Logger.Log實現日誌記錄方法(Info,Trace,Debug)。 Log的參數之一是記錄器的類型(即NLog封裝器的類型)。當NLog想要寫出方法名稱時,它只是遍歷堆棧跟蹤直到找到該類型。這將是「記錄器」和應用程序之間的界限。再一步提高堆棧跟蹤,並且您可以從調用站點獲取堆棧。通過該NLog可以記錄方法名稱和類名稱。

此外,靜態NLog包裝的問題是您失去了記錄器名稱的能力。通常情況下,用於檢索記錄的模式是在每一個類這樣的代碼,你可能要登錄:

public class MyClassFromWhichIWantToLog 
{ 
    private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); 

    public void DoSomething() 
    { 
    _logger.Info("Hello!"); 
    } 
} 

LogManager.GetCurrentClassLogger返回記錄器,其「名」的一個實例是完全合格的類名的類。由於我們使用靜態類變量來保存記錄器,因此每種類型都有一個記錄器實例(即MyClassFromWhichIWantToLog的所有實例將共享同一個記錄器實例)。由於記錄器是爲其類而命名的,因此您可以更好地控制生成日誌記錄輸出的方式。您可以配置NLog(通過NLog.config),以便所有記錄器全部記錄日誌。或者您可以對其進行配置,使得只有特定的記錄器記錄(或某些記錄器記錄在一個等級上,而其他記錄器記錄在不同的等級上)。假設您有一個包含各種組件的程序。他們似乎都工作正常,但你必須實施一個新的組件。在開發過程中,您可能希望將其日誌記錄方式向上(即獲取更多信息),同時將程序的其他部分調低(即從程序中正常工作的部分獲取信息)。此外,您可以通過記錄器名稱重定向日誌記錄(例如,將某個類或名稱空間的所有日誌記錄消息發送到某個日誌記錄目標(如果您正在調試程序的某個部分,則可能是調試器目標),併發送其他消息(包括那些要調試器)到您的輸出文件或數據庫)。如果您有靜態記錄器包裝器,則無法控制每個類或每個名稱空間的日誌記錄。

看看我這個問題的答案:

How to retain callsite information when wrapping NLog

我的回答提供的源代碼(直接從NLOG的源代碼庫)的NLOG包裝,保持正確的調用站點的信息。請注意,來自NLog的示例更多地說明了如何擴展NLog.Logger(通過添加「EventID」)而不是將其包裝。如果你忽略EventID的東西,你會發現關鍵是將你的包裝類型傳遞給NLog的Logger.Log方法。

這是一個非常簡潔的NLog包裝(只有一個方法(Info)),它應該正確包裝NLog以保持呼叫站點信息。

public class MyLogger 
    { 
    public MyLogger(Logger logger) 
    { 
     _logger = logger; 
    } 

    private Logger _logger; 
    private void WriteMessage(LogLevel level, string message) 
    { 
     // 
     // Build LogEvent here... 
     // 
     LogEventInfo logEvent = new LogEventInfo(logLevel, context.Name, message); 
     logEvent.Exception = exception; 

     // 
     // Pass the type of your wrapper class here... 
     // 
     _logger.Log(typeof(MyLogger), logEvent); 
    } 

    public void Info(string message) 
    { 
     WriteMessage(LogLevel.Info, message); 
    } 
    } 

你會使用這樣的:

public class MyClassWhereIWantToUseLogging 
{ 
    private static readonly _logger = new MyLogger(LogManager.GetCurrentClassLogger()); 

    public void DoSomething() 
    { 
    _logger.Info("Hello!"); //If you log call site info, you should class name and method name. 
    } 
} 

欲瞭解更多信息NLOG,看到這個流行的(如果我不這樣說我自己;-))NLOG後:

Most useful NLog configurations

相關問題