前段時間在開發一個協議站點供客戶端(Android/IOS)使用,因業務需要統計各協議的調用頻率。將記錄以日志的形式記錄在日志系統中。
簡單分析了一下,技術方案大致分為兩種:
方案A:每個業務模塊需要埋點的協議單獨埋點。
方案B:封裝一個HttpModule。記錄所有的請求。
方案A與方案B的優缺點就不在分析了。在我們的項目有兩個個小組做類似的協議站點,我們采用的是方案B。而另外的一個小組采用方案A。
在此我重點說一下采用的方案B的理由:
1、麻煩一個人,一個人開發完畢後,其他人不再去花時間增加記錄。統一記錄。
2、方便排錯,還可以做請求參數與輸出參數的記錄,也不用在業務中記錄返回的值。業務開發只關心業務即可。記錄業務中的常規日志即可。
代碼比較簡單。與協議之間走的是Post請求發送Json串。請求串全在Request.InputStream中。兩個文件一個處理請求(Handle.cs),一個獲取數據(CatchTextStream.cs)
Handle.cs
using System; using System.IO; using System.Web; using Logs; namespace HttpModule.Api { public class Handle : IHttpModule { private string json = string.Empty; private string requestItemKey = "request.json"; private string responseItemKey = "response.json"; void IHttpModule.Dispose() { } void IHttpModule.Init(HttpApplication context) { context.BeginRequest += new EventHandler(beginRequest); context.EndRequest += new EventHandler(endRequest); } private void beginRequest(object sender, EventArgs e) { HttpApplication application = (HttpApplication)sender; var context = application.Context; if (context == null) { return; } //排除不需要的鏈接 if (context.Request.InputStream.Length > 0) { context.Response.Filter = new CatchTextStream(context.Response.Filter, responseItemKey); context.Request.InputStream.Position = 0; //設置流的位置 StreamReader reader = new StreamReader(context.Request.InputStream); //request請求流 string json = reader.ReadToEnd(); context.Request.InputStream.Seek(0, SeekOrigin.End);//重置流讀取位置,如果不重置在方法中無法獲取json串 context.Request.RequestContext.HttpContext.Items[requestItemKey] = json; } else//只記錄請求參數 { _log.Info("請求url:" + context.Request.Url); } } private void endRequest(object sender, EventArgs e) { HttpApplication application = (HttpApplication)sender; var context = application.Context; if (context.Request.RequestContext.HttpContext.Items.Contains(requestItemKey)) { string requestJson = context.Request.RequestContext.HttpContext.Items[requestItemKey].ToString(); string responseJson = context.Request.RequestContext.HttpContext.Items[responseItemKey].ToString(); //當前請求狀態 返回值中帶code:0表示協議請求成功 status=日志系統使用標識,1=請求正常返回,0=請求異常 int status = responseJson.Contains("\"code\":\"0\"") ? 1 : 0; writeLog(context.Request.Url.LocalPath, requestJson, "return=" + responseJson, status); } } private void writeLog(string url, string parms, string msg, int status) { var entity = new Logs.LogRecord("站點名稱", LogLevelType.Monitor, url) { ParasNameValue = parms, ErrMessage = msg, Status = status }; Logs.LogNetHelper.WriteLog(entity); } } }
注:Logs包為公司項目封裝的通用記錄方法。
CatchTextStream.cs
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using System.Web; namespace HttpModule.Api { class CatchTextStream : Stream { private Stream output; private List<byte> _listByte = new List<byte>(); private string _itemKey; public CatchTextStream(Stream s, string itemKey) { _itemKey = itemKey; output = s; } public override bool CanRead { get { return output.CanRead; } } public override bool CanSeek { get { return output.CanSeek; } } public override bool CanWrite { get { return output.CanWrite; } } public override void Flush() { output.Flush(); if (_listByte.Any()) { var utf8 = Encoding.UTF8; string json = utf8.GetString(_listByte.ToArray(), 0, _listByte.Count); HttpContext.Current.Request.RequestContext.HttpContext.Items[_itemKey] = json; } } public override long Length { get { return output.Length; } } public override long Position { get { return output.Position; } set { output.Position = value; } } public override int Read(byte[] buffer, int offset, int count) { return output.Read(buffer, offset, count); } public override long Seek(long offset, SeekOrigin origin) { return output.Seek(offset, origin); } public override void SetLength(long value) { output.SetLength(value); } public override void Write(byte[] buffer, int offset, int count) { output.Write(buffer, offset, count); if (HttpContext.Current != null) { if (HttpContext.Current.Request.RequestContext.HttpContext.Items.Contains(_itemKey)) { var temp = buffer.Skip(offset).Take(count); _listByte.AddRange(temp); } } } } }
接下來在Web.config配置Modules即可。
<modules runAllManagedModulesForAllRequests="true"> <!--記錄請求日志--> <add name="apilog" type="HttpModule.Api.Handle,HttpModule.Api" /> </modules>