摘要:本文說明如何使用自訂的 ITraceWriter 服務來記錄用戶端對你的 Web API 發出的請求。適用 ASP.NET Web API 2.x。
把用戶端對你的 Web API 請求全部記錄下來(例如寫入 log 檔案)有兩個好處:
這裡不打算提 Web API 框架運作的細節。不過,對於「如何記錄用戶端請求」這個目的,必須特別提醒:ITraceWriter 服務能夠記錄的東西很多,並不是只能夠用來記錄用戶端請求。若不理解這點,你可能會發現實際寫入 log 檔案的訊息多到不像話,就像接下來的範例所展示的那樣。
後記:經 91 兄提醒,欲記錄 Web API 的用戶端請求,使用 message handler 更輕巧,也更適合。我把這個部分補在文後〈延伸閱讀〉裡面。(明明以前寫過,卻硬是忘了提,該不會年紀大了...Orz)
範例
首先,在你的 Web API 專案中加入一個類別,命名為 WebApiTraceWriter,如下所示。
簡單起見,這裡只是用 File.AppendAllText 來把 log 內容寫入至檔案。通常我是用 NLog 來記錄,而且是記錄在一個單獨的檔案裡(例如 webapi-log.txt)。
接著,將你的自訂 ITraceWriter 服務註冊至 Web API 框架:
在 Visual Studio 中把這個 Web 應用程式執行起來,然後用瀏覽器對此應用程式的某個 Web API 發出請求,以便觀察 webapi-log.txt 的檔案內容。
結果是,明明只發出一次請求,webapi-log.txt 裡面卻出現了 26 筆重複的紀錄。像這樣:
正如前面說的,ITraceWriter 服務能夠紀錄的東西很多(因為觸發時機很多),這裡看到的重複紀錄只是因為 ITraceWriter 的 Trace 方法被觸發很多次而產生的現象。如果我們把剛才範例中的倒數第 5 行改成這樣:
這次多輸出了 TraceRecord 的 Operation 和 Operator 屬性,結果會像這樣:
這就明白為什麼先前範例所輸出的 log 檔案有來那麼多筆重複的紀錄了。
由於我只是想要紀錄用戶端對 Web API 發出的請求,因此勢必得要濾掉重複的紀錄。對此問題,我是利用 TraceRecord 的 RequestId 屬性來解決。
利用 TraceRecord.RequestId 來過濾重複的紀錄
把先前的範例改成底下這樣就行了:
小結
如果擔心此作法會影響應用程式的執行效能,可以在 web.config 裡面加一個參數,比如說 WebApiTracingEnabled。然後,在 WebApiConfig 的 Register 方法裡面判斷此參數的值,若為 False,就不要註冊我們自訂的 WebApiTraceWriter。
到 Github 取得範例原始碼(使用 NLog)
Happy coding!
延伸閱讀
把用戶端對你的 Web API 請求全部記錄下來(例如寫入 log 檔案)有兩個好處:
- 除錯
- 了解用戶端應用程式是如何使用你的 Web API
這裡不打算提 Web API 框架運作的細節。不過,對於「如何記錄用戶端請求」這個目的,必須特別提醒:ITraceWriter 服務能夠記錄的東西很多,並不是只能夠用來記錄用戶端請求。若不理解這點,你可能會發現實際寫入 log 檔案的訊息多到不像話,就像接下來的範例所展示的那樣。
後記:經 91 兄提醒,欲記錄 Web API 的用戶端請求,使用 message handler 更輕巧,也更適合。我把這個部分補在文後〈延伸閱讀〉裡面。(明明以前寫過,卻硬是忘了提,該不會年紀大了...Orz)
範例
首先,在你的 Web API 專案中加入一個類別,命名為 WebApiTraceWriter,如下所示。
using System; using System.Web.Http.Tracing; namespace TraceWriterDemo { public class WebApiTraceWriter : ITraceWriter { public void Trace(System.Net.Http.HttpRequestMessage request, string category, TraceLevel level, Action<tracerecord> traceAction) { if (level != TraceLevel.Off) { TraceRecord traceRecord = new TraceRecord(request, category, level); traceAction(traceRecord); Log(traceRecord); } } private void Log(TraceRecord traceRecord) { if (traceRecord.Request == null || traceRecord.Request.Method == null || traceRecord.Request.RequestUri == null) { return; } string s = String.Format("{0} {1}\r\n", traceRecord.Request.Method, traceRecord.Request.RequestUri); System.IO.File.AppendAllText(@"C:\temp\webapi-log.txt", s); } } }
簡單起見,這裡只是用 File.AppendAllText 來把 log 內容寫入至檔案。通常我是用 NLog 來記錄,而且是記錄在一個單獨的檔案裡(例如 webapi-log.txt)。
接著,將你的自訂 ITraceWriter 服務註冊至 Web API 框架:
public static class WebApiConfig { public static void Register(HttpConfiguration config) { // Web API configuration and services // (略) config.Services.Replace(typeof(ITraceWriter), new WebApiTraceWriter()); } }
在 Visual Studio 中把這個 Web 應用程式執行起來,然後用瀏覽器對此應用程式的某個 Web API 發出請求,以便觀察 webapi-log.txt 的檔案內容。
結果是,明明只發出一次請求,webapi-log.txt 裡面卻出現了 26 筆重複的紀錄。像這樣:
GET http://localhost:34942/api/Default GET http://localhost:34942/api/Default GET http://localhost:34942/api/Default GET http://localhost:34942/api/Default GET http://localhost:34942/api/Default GET http://localhost:34942/api/Default GET http://localhost:34942/api/Default GET http://localhost:34942/api/Default GET http://localhost:34942/api/Default GET http://localhost:34942/api/Default GET http://localhost:34942/api/Default GET http://localhost:34942/api/Default GET http://localhost:34942/api/Default GET http://localhost:34942/api/Default GET http://localhost:34942/api/Default GET http://localhost:34942/api/Default GET http://localhost:34942/api/Default GET http://localhost:34942/api/Default GET http://localhost:34942/api/Default GET http://localhost:34942/api/Default GET http://localhost:34942/api/Default GET http://localhost:34942/api/Default GET http://localhost:34942/api/Default GET http://localhost:34942/api/Default GET http://localhost:34942/api/Default GET http://localhost:34942/api/Default GET http://localhost:34942/api/Default
正如前面說的,ITraceWriter 服務能夠紀錄的東西很多(因為觸發時機很多),這裡看到的重複紀錄只是因為 ITraceWriter 的 Trace 方法被觸發很多次而產生的現象。如果我們把剛才範例中的倒數第 5 行改成這樣:
string s = String.Format("{0} {1} {2} {3}\r\n", traceRecord.Method, traceRecord.Operation, traceRecord.Operator, traceRecord.Request.RequestUri);
這次多輸出了 TraceRecord 的 Operation 和 Operator 屬性,結果會像這樣:
GET SelectController DefaultHttpControllerSelector http://localhost:34942/api/Default GET SelectController DefaultHttpControllerSelector http://localhost:34942/api/Default GET CreateController HttpControllerDescriptor http://localhost:34942/api/Default GET Create DefaultHttpControllerActivator http://localhost:34942/api/Default GET Create DefaultHttpControllerActivator http://localhost:34942/api/Default GET CreateController HttpControllerDescriptor http://localhost:34942/api/Default GET ExecuteAsync DefaultController http://localhost:34942/api/Default GET SelectAction ApiControllerActionSelector http://localhost:34942/api/Default GET SelectAction ApiControllerActionSelector http://localhost:34942/api/Default GET ExecuteBindingAsync HttpActionBinding http://localhost:34942/api/Default GET ExecuteBindingAsync HttpActionBinding http://localhost:34942/api/Default GET InvokeActionAsync ApiControllerActionInvoker http://localhost:34942/api/Default GET ExecuteAsync ReflectedHttpActionDescriptor http://localhost:34942/api/Default GET ExecuteAsync ReflectedHttpActionDescriptor http://localhost:34942/api/Default GET Negotiate DefaultContentNegotiator http://localhost:34942/api/Default GET GetPerRequestFormatterInstance XmlMediaTypeFormatter http://localhost:34942/api/Default GET GetPerRequestFormatterInstance XmlMediaTypeFormatter http://localhost:34942/api/Default GET Negotiate DefaultContentNegotiator http://localhost:34942/api/Default GET InvokeActionAsync ApiControllerActionInvoker http://localhost:34942/api/Default GET ExecuteAsync DefaultController http://localhost:34942/api/Default GET http://localhost:34942/api/Default GET WriteToStreamAsync XmlMediaTypeFormatter http://localhost:34942/api/Default GET WriteToStreamAsync XmlMediaTypeFormatter http://localhost:34942/api/Default GET Dispose DefaultController http://localhost:34942/api/Default GET Dispose DefaultController http://localhost:34942/api/Default
這就明白為什麼先前範例所輸出的 log 檔案有來那麼多筆重複的紀錄了。
由於我只是想要紀錄用戶端對 Web API 發出的請求,因此勢必得要濾掉重複的紀錄。對此問題,我是利用 TraceRecord 的 RequestId 屬性來解決。
利用 TraceRecord.RequestId 來過濾重複的紀錄
把先前的範例改成底下這樣就行了:
public class WebApiTraceWriter : ITraceWriter { private Guid _lastRequestId; public void Trace(System.Net.Http.HttpRequestMessage request, string category, TraceLevel level, Action<tracerecord> traceAction) { // 這部分維持不變 } private void Log(TraceRecord traceRecord) { if (traceRecord.Request == null || traceRecord.Request.Method == null || traceRecord.Request.RequestUri == null) { return; } if (traceRecord.RequestId.Equals(_lastRequestId)) // 忽略已經記錄過的請求 { return; } _lastRequestId = traceRecord.RequestId; string s = String.Format("{0} {1}\r\n", traceRecord.Request.Method, traceRecord.Request.RequestUri); System.IO.File.AppendAllText(@"C:\temp\webapi-log.txt", s); } }
如果擔心此作法會影響應用程式的執行效能,可以在 web.config 裡面加一個參數,比如說 WebApiTracingEnabled。然後,在 WebApiConfig 的 Register 方法裡面判斷此參數的值,若為 False,就不要註冊我們自訂的 WebApiTraceWriter。
到 Github 取得範例原始碼(使用 NLog)
Happy coding!
延伸閱讀
沒有留言: