基於.net core webapi和mongodb的日誌系統

開發環境vs2017,.NET Core2.1, 數據寫入到mongodb。思路就是1.提供接口寫入日誌,2.基於接口封裝類庫。3.引入類庫使用html

源碼在最後git

爲何要寫它github

不少開源項目像nlog、log4net、elk、exceptionless等都挺好的。就是除了引入所需類庫,還要在項目中添加配置,不喜歡。elk在分佈式海量數據收集和檢索方面可能更能發揮它的優點,單純記日誌也能夠,exceptionless就是基於elk的。就想着寫一個簡單易用的、能夠發郵件報警的,直接引入類庫就能用的一個記日誌工具,全部的配置信息和入庫都交給web api。這是當時問的問題,https://q.cnblogs.com/q/109489/。乾脆就實現了先web

接下里的代碼可能有不少能夠優化的地方,若是有些地方以爲不妥或者能夠用更好的方式實現或組織代碼,請告訴說,我改。另外實現完的接口沒有加訪問限制,先默認內網使用,固然有熱心網友給出實現的話就更好了,像ip限制或者簽名等等(已改,http basic Authorization進行認證)mongodb

1、實現Web Api數據庫

  •  新建.net core web api項目 【LogWebApi】

由於要發郵件和寫入mongodb,先改配置文件appsettings.jsonjson

{
  "ConnectionStrings": {
    "ConnectionString": "mongodb://yourmongoserver",
    "Database": "logdb",
    "LogCollection": "logdata"
  },
  "AllowedHosts": "*",
  "AppSettings": {
    "SendMailInfo": {
      "SMTPServerName": "smtp.qiye.163.com",
      "SendEmailAdress": "發送人郵箱",
      "SendEmailPwd": "",
      "SiteName": "郵件主題",
      "SendEmailPort": "123"
    }
  }
}
  • 實現依賴注入獲取配置文件信息

建立目錄結構以下圖api

AppSettings類服務器

public class AppSettings
    {
        public SendMailInfo SendMailInfo { get; set; }
    }
    public class SendMailInfo
    {
        public string SMTPServerName { get; set; }
        public string SendEmailAdress { get; set; }
        public string SendEmailPwd { get; set; }
        public string SiteName { get; set; }
        public string SendEmailPort { get; set; }
    }
View Code

DBSettings類app

/// <summary>
    /// 數據庫配置信息
    /// </summary>
    public class DBSettings
    {
        /// <summary>
        /// mongodb connectionstring
        /// </summary>
        public string ConnectionString { get; set; }
        /// <summary>
        /// mongodb database
        /// </summary>
        public string Database { get; set; }
        /// <summary>
        /// 日誌collection
        /// </summary>
        public string LogCollection { get; set; }
    }
View Code

 接下來Here is how we modify Startup.cs to inject Settings in the Options accessor model:

public void ConfigureServices(IServiceCollection services)
        {            
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

            services.Configure<DBSettings>(Configuration.GetSection("ConnectionStrings"));//數據庫鏈接信息
            services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));//其餘配置信息            

        }
View Code

在項目中將經過IOptions 接口來獲取配置信息,後面看代碼吧

IOptions<AppSettings>
IOptions<DBSettings>

配置文件信息獲取算是準備完了

  • 建立日誌信息Model

在Model文件夾下建立類LogEventData,也就是存到mongodb的信息

public class LogEventData
    {
        [BsonId]
        public ObjectId Id { get; set; }
        /// <summary>
        /// 時間
        /// </summary>
        [BsonDateTimeOptions(Representation = BsonType.DateTime, Kind = DateTimeKind.Local)]
        public DateTime Date { get; set; }
        /// <summary>
        /// 錯誤級別
        /// </summary>
        public string Level { get; set; }
        /// <summary>
        /// 日誌來源
        /// </summary>
        public string LogSource { get; set; }
        /// <summary>
        /// 日誌信息
        /// </summary>
        public string Message { get; set; }
        /// <summary>
        /// 類名
        /// </summary>
        public string ClassName { get; set; }
        /// <summary>
        /// 方法名
        /// </summary>
        public string MethodName { get; set; }
        /// <summary>
        /// 完整信息
        /// </summary>
        public string FullInfo { get; set; }
        /// <summary>
        /// 行號
        /// </summary>        
        public string LineNumber { get; set; }
        /// <summary>
        /// 文件名
        /// </summary>        
        public string FileName { get; set; }
        /// <summary>
        /// ip
        /// </summary>
        public string IP { get; set; }
        /// <summary>
        /// 是否發送郵件,不爲空則發送郵件,多個接收人用英文逗號隔開
        /// </summary>
        [JsonIgnore]
        public string Emails { get; set; }

        public override string ToString()
        {
            return JsonConvert.SerializeObject(this);
        }
    }
View Code
  • 定義database Context

站點根目錄新建文件夾Context和類,別忘了引用 MongoDB.Driver  nuget包

public class MongoContext
    {
        private readonly IMongoDatabase _database = null;
        private readonly string _logCollection;
        public MongoContext(IOptions<DBSettings> settings)
        {
            var client = new MongoClient(settings.Value.ConnectionString);
            if (client != null)
                _database = client.GetDatabase(settings.Value.Database);
            _logCollection = settings.Value.LogCollection;
        }

        public IMongoCollection<LogEventData> LogEventDatas
        {
            get
            {
                return _database.GetCollection<LogEventData>(_logCollection);
            }
        }
    }
View Code
  • 添加Repository

別糾結爲何叫這個名了,就是數據訪問類,像是經常使用的DAL,建立目錄以下,以後能夠經過依賴注入來訪問具體實現

IRepository類

public interface IRepository<T> where T:class
    {
        Task<IEnumerable<T>> GetAll();
        Task<T> Get(string id);
        Task Add(T item);
        Task<bool> Remove(string id);
        Task<bool> Update(string id, string body);
    }
View Code

LogRepository類

public class LogRepository : IRepository<LogEventData>
    {
        private readonly MongoContext _context = null;
        public LogRepository(IOptions<DBSettings> settings)
        {
            _context = new MongoContext(settings);
        }


        public async Task Add(LogEventData item)
        {
            await _context.LogEventDatas.InsertOneAsync(item);
        }
        public async Task<IEnumerable<LogEventData>> GetList(QueryLogModel model)
        {
            var builder = Builders<LogEventData>.Filter;
            FilterDefinition<LogEventData> filter = builder.Empty;
            if (!string.IsNullOrEmpty(model.Level))
            {
                filter = builder.Eq("Level", model.Level);
            }
            if (!string.IsNullOrEmpty(model.LogSource))
            {
                filter = filter & builder.Eq("LogSource", model.LogSource);
            }
            if (!string.IsNullOrEmpty(model.Message))
            {
                filter = filter & builder.Regex("Message", new BsonRegularExpression(new Regex(model.Message)));
            }
            if (DateTime.MinValue != model.StartTime)
            {
                filter = filter & builder.Gte("Date", model.StartTime);
            }
            if(DateTime.MinValue != model.EndTime)
            {
                filter = filter & builder.Lte("Date", model.EndTime);
            }
            return await _context.LogEventDatas.Find(filter)
                 .SortByDescending(log => log.Date)
                 .Skip((model.PageIndex - 1) * model.PageSize)
                 .Limit(model.PageSize).ToListAsync();
        }
        #region 未實現方法
        public async Task<LogEventData> Get(string id)
        {
            throw new NotImplementedException();
        }

        public async Task<IEnumerable<LogEventData>> GetAll()
        {
            throw new NotImplementedException();
        }

        public Task<bool> Remove(string id)
        {
            throw new NotImplementedException();
        }

        public Task<bool> Update(string id, string body)
        {
            throw new NotImplementedException();
        } 
        #endregion
    }
View Code

 爲了經過DI model來訪問LogRepository,修改Startup.cs ,ConfigureServices添加以下代碼

services.AddTransient<IRepository<LogEventData>, LogRepository>();//數據訪問

 到這基本的數據寫入和查詢算是寫完了,下面來實現Controller

  • 建立LogController

[Route("api/[controller]")]
    [ApiController]
    public class LogController : ControllerBase
    {
        private readonly LogRepository _logRepository;
        IOptions<AppSettings> _appsettings;        
        public LogController(IRepository<LogEventData> logRepository,IOptions<AppSettings> appsettings)
        {
            _logRepository = (LogRepository)logRepository;
            _appsettings = appsettings;
        }

        [Route("trace")]
        [HttpPost]
        public void Trace([FromBody] LogEventData value)
        {
            Add(value);
        }
        [Route("debug")]
        [HttpPost]
        public void Debug([FromBody] LogEventData value)
        {
            Add(value);

        }
        [Route("info")]
        [HttpPost]
        public void Info([FromBody] LogEventData value)
        {
            Add(value);
        }
        [Route("warn")]
        [HttpPost]
        public void Warn([FromBody] LogEventData value)
        {
            Add(value);
        }
        [Route("error")]
        [HttpPost]
        public void Error([FromBody] LogEventData value)
        {
            Add(value);
        }
        [Route("fatal")]
        [HttpPost]
        public void Fatal([FromBody] LogEventData value)
        {
            Add(value);
        }
        private async void Add(LogEventData data)
        {
            if (data != null)
            {
                await _logRepository.Add(data);
                if (!string.IsNullOrEmpty(data.Emails))
                {
                    new EmailHelpers(_appsettings).SendMailAsync(data.Emails, "監測郵件", data.ToString());
                }
            }
        }

        [HttpGet("getlist")]
        public async Task<ResponseModel<IEnumerable<LogEventData>>> GetList([FromQuery] QueryLogModel model)
        {
            ResponseModel<IEnumerable<LogEventData>> resp = new ResponseModel<IEnumerable<LogEventData>>();
            resp.Data = await _logRepository.GetList(model);
            return resp;
        }
    }
View Code

 

控制器裏整個邏輯很簡單,除了向外提供不一樣日誌級別的寫入接口,也實現了日誌查詢接口給日誌查看站點用,基本上夠用了。到這編譯的話會報錯,有一些類還沒加上,稍後加上。在Add方法內部,用到了new EmailHelpers。講道理按.net core 對依賴注入的使用 ,這個 new是不該該出如今這的,就先這麼着吧,下面補類:

先建立Model文件夾下的兩個類,很簡單就不解釋了

QueryLogModel類

public class QueryLogModel
    {
        private int _pageindex = 1;
        private int _pagesize = 20;
        public int PageIndex
        {
            get { return _pageindex; }
            set { _pageindex = value; }
        }
        public int PageSize
        {
            get { return _pagesize; }
            set { _pagesize = value; }
        }
        public string Level { get; set; }
        public string LogSource { get; set; }
        public string Message { get; set; }
        public DateTime StartTime { get; set; }
        public DateTime EndTime { get; set; }
    }
View Code

ResponseModel類

public class ResponseModel<T>
    {
        private HttpStatusCode _resultCode = HttpStatusCode.OK;
        private string _message = "請求成功";        
        private T _data = default(T);
        /// <summary>
        /// 返回碼
        /// </summary>
        public HttpStatusCode ResultCode
        {
            get { return this._resultCode; }
            set { this._resultCode = value; }
        }
        /// <summary>
        /// 結果說明
        /// </summary>
        public string Message
        {
            get { return this._message; }
            set { this._message = value; }
        }        
        /// <summary>
        /// 返回的數據
        /// </summary>
        public T Data
        {
            get { return this._data; }
            set { this._data = value; }
        }
    }
View Code

 建立EmailHelpers類

public class EmailHelpers
    {
        private SendMailInfo _mailinfo;
        
        public EmailHelpers(IOptions<AppSettings> appsettings)
        {
            _mailinfo = appsettings.Value.SendMailInfo;
        }
        /// <summary>
        /// 異步發送郵件
        /// </summary>
        /// <param name="emails">email地址</param>
        /// <param name="subject">郵件標題</param>
        /// <param name="content">郵件內容</param>
        public void SendMailAsync(string emails, string subject, string content)
        {
            Task.Factory.StartNew(() =>
            {
                SendEmail(emails, subject, content);
            });
        }
        /// <summary>
        /// 郵件發送方法
        /// </summary>
        /// <param name="emails">email地址</param>
        /// <param name="subject">郵件標題</param>
        /// <param name="content">郵件內容</param>
        /// <returns></returns>
        public void SendEmail(string emails, string subject, string content)
        {
            string[] emailArray = emails.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
            string fromSMTP = _mailinfo.SMTPServerName;        //郵件服務器
            string fromEmail = _mailinfo.SendEmailAdress;      //發送方郵件地址
            string fromEmailPwd = _mailinfo.SendEmailPwd;//發送方郵件地址密碼
            string fromEmailName = _mailinfo.SiteName;   //發送方稱呼
            try
            {
                //新建一個MailMessage對象
                MailMessage aMessage = new MailMessage();
                aMessage.From = new MailAddress(fromEmail, fromEmailName);
                foreach (var item in emailArray)
                {
                    aMessage.To.Add(item);
                }
                aMessage.Subject = subject;
                aMessage.Body = content;
                System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
                aMessage.BodyEncoding = Encoding.GetEncoding("utf-8");
                aMessage.IsBodyHtml = true;
                aMessage.Priority = MailPriority.High;                
                aMessage.ReplyToList.Add(new MailAddress(fromEmail, fromEmailName));
                SmtpClient smtp = new SmtpClient();

                smtp.Host = fromSMTP;
                smtp.Timeout = 20000;
                smtp.UseDefaultCredentials = false;
                smtp.EnableSsl = true;
                smtp.DeliveryMethod = SmtpDeliveryMethod.Network;
                smtp.Credentials = new NetworkCredential(fromEmail, fromEmailPwd); //發郵件的EMIAL和密碼
                smtp.Port = int.Parse(_mailinfo.SendEmailPort);                
                smtp.Send(aMessage);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
    }
View Code

此類裏須要引用nuget:System.Text.Encoding.CodePages, 那行報錯的代碼若是不想引用刪掉就行

到這接口基本上就能夠用了。

可是再加三個東西

  • 擴展

 添加全局異常捕獲服務

ExceptionMiddlewareExtensions類

/// <summary>
    /// 全局異常處理中間件
    /// </summary>
    public static class ExceptionMiddlewareExtensions
    {
        public static void ConfigureExceptionHandler(this IApplicationBuilder app, IOptions<DBSettings> settings)
        {
            LogRepository _repository = new LogRepository(settings);
            app.UseExceptionHandler(appError =>
            {
                appError.Run(async context =>
                {
                    context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
                    context.Response.ContentType = "application/json";

                    var contextFeature = context.Features.Get<IExceptionHandlerFeature>();
                    if (contextFeature != null)
                    {
                        await _repository.Add(new LogEventData
                        {
                            Message= contextFeature.Error.ToString(),
                            Date=DateTime.Now,
                            Level="Fatal",
                            LogSource= "LogWebApi"
                        }); 
                        await context.Response.WriteAsync(context.Response.StatusCode + "-Internal Server Error.");
                    }
                });
            });
        }
    }
View Code

修改Startup.cs

public void Configure(IApplicationBuilder app, IHostingEnvironment env,IOptions<DBSettings> settings)
        {            
            app.ConfigureExceptionHandler(settings);
        }
View Code

添加MessagePack擴展

messagepack可讓咱們在post數據的時候序列化數據,「壓縮」數據傳輸大小,這個會結合針對接口封裝的類庫配合使用。

引用nuget: MessagePack和WebApiContrib.Core.Formatter.MessagePack

在ConfigureServices添加代碼

services.AddMvcCore().AddMessagePackFormatters();
services.AddMvc().AddMessagePackFormatters();

擴展了media type,用以支持"application/x-msgpack", "application/msgpack",在接下來封裝的類庫中會使用"application/x-msgpack",在web api來引入這個東西就是爲了能解析從客戶端傳過來的數據

添加Swagger支持

引用nuget:Swashbuckle.AspNetCore

修改ConfigureServices

services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new Info { Title = "My API", Version = "v1" }); });

修改Configure

// Enable middleware to serve generated Swagger as a JSON endpoint.
            app.UseSwagger();

            // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), 
            // specifying the Swagger JSON endpoint.
            app.UseSwaggerUI(c =>
            {
                c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
                c.RoutePrefix = string.Empty;//在應用的根 (http://localhost:<port>/) 處提供 Swagger UI
            });
View Code

 

到這整個web api站點算是寫完了,編譯不出錯就ok了。



2、實現類庫

類庫總體目錄結構以下

1.新建類庫LogApiHandler

2.實現

  • 建立日誌信息類,和WebApi那個對應,LogEventData
/// <summary>
    /// 日誌數據
    /// post到日誌接口的數據
    /// </summary>
    public class LogEventData
    {
        /// <summary>
        /// 時間
        /// </summary>
        public DateTime Date { get; set; }
        /// <summary>
        /// 錯誤級別
        /// </summary>
        public string Level { get; set; }
        /// <summary>
        /// 日誌來源
        /// </summary>
        public string LogSource { get; set; }
        /// <summary>
        /// 日誌信息
        /// </summary>
        public string Message { get; set; }
        /// <summary>
        /// 類名
        /// </summary>
        public string ClassName { get; set; }
        /// <summary>
        /// 方法名
        /// </summary>
        public string MethodName { get; set; }
        /// <summary>
        /// 完整信息
        /// </summary>
        public string FullInfo { get; set; }
        /// <summary>
        /// 行號
        /// </summary>        
        public string LineNumber { get; set; }
        /// <summary>
        /// 文件名
        /// </summary>        
        public string FileName { get; set; }
        /// <summary>
        /// ip
        /// </summary>
        public string IP { get; set; }
        /// <summary>
        /// 不爲空則發送郵件,多個接收人用英文分號 ; 隔開
        /// </summary>
        public string Emails { get; set; }
    }    
View Code
  • 建立日誌級別類,就是其餘開源項目常見的Level
internal class LogLevel
    {
        /// <summary>
        /// Trace log level.
        /// </summary>        
        public static readonly LogLevel Trace = new LogLevel("Trace", 0);

        /// <summary>
        /// Debug log level.
        /// </summary>        
        public static readonly LogLevel Debug = new LogLevel("Debug", 1);

        /// <summary>
        /// Info log level.
        /// </summary>        
        public static readonly LogLevel Info = new LogLevel("Info", 2);

        /// <summary>
        /// Warn log level.
        /// </summary>        
        public static readonly LogLevel Warn = new LogLevel("Warn", 3);

        /// <summary>
        /// Error log level.
        /// </summary>        
        public static readonly LogLevel Error = new LogLevel("Error", 4);

        /// <summary>
        /// Fatal log level.
        /// </summary>        
        public static readonly LogLevel Fatal = new LogLevel("Fatal", 5);
        private readonly int _ordinal;
        private readonly string _name;
        /// <summary>
        /// Initializes a new instance of <see cref="LogLevel"/>.
        /// </summary>
        /// <param name="name">The log level name.</param>
        /// <param name="ordinal">The log level ordinal number.</param>
        private LogLevel(string name, int ordinal)
        {
            _name = name;
            _ordinal = ordinal;
        }
        /// <summary>
        /// Gets the name of the log level.
        /// </summary>
        public string Name => _name;
        /// <summary>
        /// Gets the ordinal of the log level.
        /// </summary>
        public int Ordinal => _ordinal;
        /// <summary>
        /// 請求地址
        /// </summary>
        public string LogApi
        {
            get
            {
                switch (_name)
                {
                    case "Trace":
                        return "http://localhost:56503/api/log/trace";                        
                    case "Debug":
                        return "http://localhost:56503/api/log/debug";                        
                    case "Info":
                        return "http://localhost:56503/api/log/info";                        
                    case "Warn":
                        return "http://localhost:56503/api/log/warn";                        
                    case "Error":
                        return "http://localhost:56503/api/log/error";                        
                    case "Fatal":
                        return "http://localhost:56503/api/log/fatal";                        
                    default:
                        return "";
                }
            }
        }
        /// <summary>
        /// Returns the <see cref="T:NLog.LogLevel"/> that corresponds to the supplied <see langword="string" />.
        /// </summary>
        /// <param name="levelName">The textual representation of the log level.</param>
        /// <returns>The enumeration value.</returns>
        public static LogLevel FromString(string levelName)
        {
            if (levelName == null)
            {
                throw new ArgumentNullException(nameof(levelName));
            }

            if (levelName.Equals("Trace", StringComparison.OrdinalIgnoreCase))
            {
                return Trace;
            }

            if (levelName.Equals("Debug", StringComparison.OrdinalIgnoreCase))
            {
                return Debug;
            }

            if (levelName.Equals("Info", StringComparison.OrdinalIgnoreCase))
            {
                return Info;
            }

            if (levelName.Equals("Warn", StringComparison.OrdinalIgnoreCase))
            {
                return Warn;
            }

            if (levelName.Equals("Error", StringComparison.OrdinalIgnoreCase))
            {
                return Error;
            }

            if (levelName.Equals("Fatal", StringComparison.OrdinalIgnoreCase))
            {
                return Fatal;
            }

            throw new ArgumentException($"Unknown log level: {levelName}");
        }
    }
View Code

上面代碼是NLog Level源碼,修改了一下,由於這個項目並不複雜,也不須要讀配置。類裏面有個LogApi屬性會根據級別返回相應的日誌接口,生產環境得在部署完WebApi站點之後,將裏面的接口信息替換掉而後編譯發佈再用。

  • 建立日誌追蹤信息類,行號、類名、文件等等

LogEventData類中有幾個屬性以下圖,接下來建立的類就是爲了獲取它們

在項目中建立文件夾Core,結構以下,建立順序爲MethodItem、StackFrameItem、LocationInfo,它們層層相扣,最終由LocationInfo提供所需信息

MethodItem類

internal class MethodItem
    {
        #region Public Instance Constructors

        /// <summary>
        /// constructs a method item for an unknown method.
        /// </summary>
        public MethodItem()
        {
            m_name = NA;
            m_parameters = new string[0];
        }

        /// <summary>
        /// constructs a method item from the name of the method.
        /// </summary>
        /// <param name="name"></param>
        public MethodItem(string name)
            : this()
        {
            m_name = name;
        }

        /// <summary>
        /// constructs a method item from the name of the method and its parameters.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="parameters"></param>
        public MethodItem(string name, string[] parameters)
            : this(name)
        {
            m_parameters = parameters;
        }

        /// <summary>
        /// constructs a method item from a method base by determining the method name and its parameters.
        /// </summary>
        /// <param name="methodBase"></param>
        public MethodItem(System.Reflection.MethodBase methodBase)
            : this(methodBase.Name, GetMethodParameterNames(methodBase))
        {
        }

        #endregion

        private static string[] GetMethodParameterNames(System.Reflection.MethodBase methodBase)
        {
            ArrayList methodParameterNames = new ArrayList();
            try
            {
                System.Reflection.ParameterInfo[] methodBaseGetParameters = methodBase.GetParameters();

                int methodBaseGetParametersCount = methodBaseGetParameters.GetUpperBound(0);

                for (int i = 0; i <= methodBaseGetParametersCount; i++)
                {
                    methodParameterNames.Add(methodBaseGetParameters[i].ParameterType + " " + methodBaseGetParameters[i].Name);
                }
            }
            catch (Exception ex)
            {
                //LogLog.Error(declaringType, "An exception ocurred while retreiving method parameters.", ex);
            }

            return (string[])methodParameterNames.ToArray(typeof(string));
        }

        #region Public Instance Properties

        /// <summary>
        /// Gets the method name of the caller making the logging 
        /// request.
        /// </summary>
        /// <value>
        /// The method name of the caller making the logging 
        /// request.
        /// </value>
        /// <remarks>
        /// <para>
        /// Gets the method name of the caller making the logging 
        /// request.
        /// </para>
        /// </remarks>
        public string Name
        {
            get { return m_name; }
        }

        /// <summary>
        /// Gets the method parameters of the caller making
        /// the logging request.
        /// </summary>
        /// <value>
        /// The method parameters of the caller making
        /// the logging request
        /// </value>
        /// <remarks>
        /// <para>
        /// Gets the method parameters of the caller making
        /// the logging request.
        /// </para>
        /// </remarks>
        public string[] Parameters
        {
            get { return m_parameters; }
        }

        #endregion

        #region Private Instance Fields

        private readonly string m_name;
        private readonly string[] m_parameters;

        #endregion

        #region Private Static Fields

        /// <summary>
        /// The fully qualified type of the StackFrameItem class.
        /// </summary>
        /// <remarks>
        /// Used by the internal logger to record the Type of the
        /// log message.
        /// </remarks>
        private readonly static Type declaringType = typeof(MethodItem);

        /// <summary>
        /// When location information is not available the constant
        /// <c>NA</c> is returned. Current value of this string
        /// constant is <b>?</b>.
        /// </summary>
        private const string NA = "?";

        #endregion Private Static Fields
    }
View Code

StackFrameItem類

internal class StackFrameItem
    {
        #region Public Instance Constructors

        /// <summary>
        /// returns a stack frame item from a stack frame. This 
        /// </summary>
        /// <param name="frame"></param>
        /// <returns></returns>
        public StackFrameItem(StackFrame frame)
        {
            // set default values
            m_lineNumber = NA;
            m_fileName = NA;
            m_method = new MethodItem();
            m_className = NA;

            try
            {
                // get frame values
                m_lineNumber = frame.GetFileLineNumber().ToString(System.Globalization.NumberFormatInfo.InvariantInfo);
                m_fileName = frame.GetFileName();
                // get method values
                MethodBase method = frame.GetMethod();
                if (method != null)
                {
                    if (method.DeclaringType != null)
                        m_className = method.DeclaringType.FullName;
                    m_method = new MethodItem(method);
                }
            }
            catch (Exception ex)
            {

            }

            // set full info
            m_fullInfo = m_className + '.' + m_method.Name + '(' + m_fileName + ':' + m_lineNumber + ')';
        }

        #endregion

        #region Public Instance Properties

        /// <summary>
        /// Gets the fully qualified class name of the caller making the logging 
        /// request.
        /// </summary>
        /// <value>
        /// The fully qualified class name of the caller making the logging 
        /// request.
        /// </value>
        /// <remarks>
        /// <para>
        /// Gets the fully qualified class name of the caller making the logging 
        /// request.
        /// </para>
        /// </remarks>
        public string ClassName
        {
            get { return m_className; }
        }

        /// <summary>
        /// Gets the file name of the caller.
        /// </summary>
        /// <value>
        /// The file name of the caller.
        /// </value>
        /// <remarks>
        /// <para>
        /// Gets the file name of the caller.
        /// </para>
        /// </remarks>
        public string FileName
        {
            get { return m_fileName; }
        }

        /// <summary>
        /// Gets the line number of the caller.
        /// </summary>
        /// <value>
        /// The line number of the caller.
        /// </value>
        /// <remarks>
        /// <para>
        /// Gets the line number of the caller.
        /// </para>
        /// </remarks>
        public string LineNumber
        {
            get { return m_lineNumber; }
        }

        /// <summary>
        /// Gets the method name of the caller.
        /// </summary>
        /// <value>
        /// The method name of the caller.
        /// </value>
        /// <remarks>
        /// <para>
        /// Gets the method name of the caller.
        /// </para>
        /// </remarks>
        public MethodItem Method
        {
            get { return m_method; }
        }

        /// <summary>
        /// Gets all available caller information
        /// </summary>
        /// <value>
        /// All available caller information, in the format
        /// <c>fully.qualified.classname.of.caller.methodName(Filename:line)</c>
        /// </value>
        /// <remarks>
        /// <para>
        /// Gets all available caller information, in the format
        /// <c>fully.qualified.classname.of.caller.methodName(Filename:line)</c>
        /// </para>
        /// </remarks>
        public string FullInfo
        {
            get { return m_fullInfo; }
        }

        #endregion Public Instance Properties

        #region Private Instance Fields

        private readonly string m_lineNumber;
        private readonly string m_fileName;
        private readonly string m_className;
        private readonly string m_fullInfo;
        private readonly MethodItem m_method;

        #endregion

        #region Private Static Fields

        /// <summary>
        /// The fully qualified type of the StackFrameItem class.
        /// </summary>
        /// <remarks>
        /// Used by the internal logger to record the Type of the
        /// log message.
        /// </remarks>
        private readonly static Type declaringType = typeof(StackFrameItem);

        /// <summary>
        /// When location information is not available the constant
        /// <c>NA</c> is returned. Current value of this string
        /// constant is <b>?</b>.
        /// </summary>
        private const string NA = "?";

        #endregion Private Static Fields
View Code

LocationInfo類

internal class LocationInfo
    {
        #region Public Instance Constructors

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="callerStackBoundaryDeclaringType">The declaring type of the method that is
        /// the stack boundary into the logging system for this call.</param>
        /// <remarks>
        /// <para>
        /// Initializes a new instance of the <see cref="LocationInfo" />
        /// class based on the current thread.
        /// </para>
        /// </remarks>
        public LocationInfo(Type callerStackBoundaryDeclaringType)
        {
            // Initialize all fields
            m_className = NA;
            m_fileName = NA;
            m_lineNumber = NA;
            m_methodName = NA;
            m_fullInfo = NA;

#if !(NETCF || NETSTANDARD1_3) // StackTrace isn't fully implemented for NETSTANDARD1_3 https://github.com/dotnet/corefx/issues/1797
            if (callerStackBoundaryDeclaringType != null)
            {
                try
                {
                    StackTrace st = new StackTrace(true);
                    int frameIndex = 0;

                    // skip frames not from fqnOfCallingClass
                    while (frameIndex < st.FrameCount)
                    {
                        StackFrame frame = st.GetFrame(frameIndex);
                        if (frame != null && frame.GetMethod().DeclaringType == callerStackBoundaryDeclaringType)
                        {
                            break;
                        }
                        frameIndex++;
                    }

                    // skip frames from fqnOfCallingClass
                    while (frameIndex < st.FrameCount)
                    {
                        StackFrame frame = st.GetFrame(frameIndex);
                        if (frame != null && frame.GetMethod().DeclaringType != callerStackBoundaryDeclaringType)
                        {
                            break;
                        }
                        frameIndex++;
                    }

                    if (frameIndex < st.FrameCount)
                    {
                        // take into account the frames we skip above
                        int adjustedFrameCount = st.FrameCount - frameIndex;
                        ArrayList stackFramesList = new ArrayList(adjustedFrameCount);
                        m_stackFrames = new StackFrameItem[adjustedFrameCount];
                        for (int i = frameIndex; i < st.FrameCount; i++)
                        {
                            stackFramesList.Add(new StackFrameItem(st.GetFrame(i)));
                        }

                        stackFramesList.CopyTo(m_stackFrames, 0);

                        // now frameIndex is the first 'user' caller frame
                        StackFrame locationFrame = st.GetFrame(frameIndex);

                        if (locationFrame != null)
                        {
                            System.Reflection.MethodBase method = locationFrame.GetMethod();

                            if (method != null)
                            {
                                m_methodName = method.Name;
                                if (method.DeclaringType != null)
                                {
                                    m_className = method.DeclaringType.FullName;
                                }
                            }
                            m_fileName = locationFrame.GetFileName();
                            m_lineNumber = locationFrame.GetFileLineNumber().ToString(System.Globalization.NumberFormatInfo.InvariantInfo);

                            // Combine all location info
                            m_fullInfo = m_className + '.' + m_methodName + '(' + m_fileName + ':' + m_lineNumber + ')';
                        }
                    }
                }
                catch (System.Security.SecurityException)
                {
                    // This security exception will occur if the caller does not have 
                    // some undefined set of SecurityPermission flags.
                    //LogLog.Debug(declaringType, "Security exception while trying to get caller stack frame. Error Ignored. Location Information Not Available.");
                }
            }
#endif
        }
        /// <summary>
        /// 自定義獲取位置信息,異步線程內獲取指望值
        /// </summary>
        /// <param name="callerStackBoundaryDeclaringType"></param>
        /// <param name="st"></param>
        public LocationInfo(Type callerStackBoundaryDeclaringType,StackTrace st)
        {
            // Initialize all fields
            m_className = NA;
            m_fileName = NA;
            m_lineNumber = NA;
            m_methodName = NA;
            m_fullInfo = NA;

#if !(NETCF || NETSTANDARD1_3) // StackTrace isn't fully implemented for NETSTANDARD1_3 https://github.com/dotnet/corefx/issues/1797
            if (callerStackBoundaryDeclaringType != null)
            {
                try
                {
                    //StackTrace st = new StackTrace(true);
                    int frameIndex = 0;

                    // skip frames not from fqnOfCallingClass
                    while (frameIndex < st.FrameCount)
                    {
                        StackFrame frame = st.GetFrame(frameIndex);
                        if (frame != null && frame.GetMethod().DeclaringType == callerStackBoundaryDeclaringType)
                        {
                            break;
                        }
                        frameIndex++;
                    }

                    // skip frames from fqnOfCallingClass
                    while (frameIndex < st.FrameCount)
                    {
                        StackFrame frame = st.GetFrame(frameIndex);
                        if (frame != null && frame.GetMethod().DeclaringType != callerStackBoundaryDeclaringType)
                        {
                            break;
                        }
                        frameIndex++;
                    }

                    if (frameIndex < st.FrameCount)
                    {
                        // take into account the frames we skip above
                        int adjustedFrameCount = st.FrameCount - frameIndex;
                        ArrayList stackFramesList = new ArrayList(adjustedFrameCount);
                        m_stackFrames = new StackFrameItem[adjustedFrameCount];
                        for (int i = frameIndex; i < st.FrameCount; i++)
                        {
                            stackFramesList.Add(new StackFrameItem(st.GetFrame(i)));
                        }

                        stackFramesList.CopyTo(m_stackFrames, 0);

                        // now frameIndex is the first 'user' caller frame
                        StackFrame locationFrame = st.GetFrame(frameIndex);

                        if (locationFrame != null)
                        {
                            System.Reflection.MethodBase method = locationFrame.GetMethod();

                            if (method != null)
                            {
                                m_methodName = method.Name;
                                if (method.DeclaringType != null)
                                {
                                    m_className = method.DeclaringType.FullName;
                                }
                            }
                            m_fileName = locationFrame.GetFileName();
                            m_lineNumber = locationFrame.GetFileLineNumber().ToString(System.Globalization.NumberFormatInfo.InvariantInfo);

                            // Combine all location info
                            m_fullInfo = m_className + '.' + m_methodName + '(' + m_fileName + ':' + m_lineNumber + ')';
                        }
                    }
                }
                catch (System.Security.SecurityException)
                {
                    // This security exception will occur if the caller does not have 
                    // some undefined set of SecurityPermission flags.
                    //LogLog.Debug(declaringType, "Security exception while trying to get caller stack frame. Error Ignored. Location Information Not Available.");
                }
            }
#endif
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="className">The fully qualified class name.</param>
        /// <param name="methodName">The method name.</param>
        /// <param name="fileName">The file name.</param>
        /// <param name="lineNumber">The line number of the method within the file.</param>
        /// <remarks>
        /// <para>
        /// Initializes a new instance of the <see cref="LocationInfo" />
        /// class with the specified data.
        /// </para>
        /// </remarks>
        public LocationInfo(string className, string methodName, string fileName, string lineNumber)
        {
            m_className = className;
            m_fileName = fileName;
            m_lineNumber = lineNumber;
            m_methodName = methodName;
            m_fullInfo = m_className + '.' + m_methodName + '(' + m_fileName +
                ':' + m_lineNumber + ')';
        }

        #endregion Public Instance Constructors

        #region Public Instance Properties

        /// <summary>
        /// Gets the fully qualified class name of the caller making the logging 
        /// request.
        /// </summary>
        /// <value>
        /// The fully qualified class name of the caller making the logging 
        /// request.
        /// </value>
        /// <remarks>
        /// <para>
        /// Gets the fully qualified class name of the caller making the logging 
        /// request.
        /// </para>
        /// </remarks>
        public string ClassName
        {
            get { return m_className; }
        }

        /// <summary>
        /// Gets the file name of the caller.
        /// </summary>
        /// <value>
        /// The file name of the caller.
        /// </value>
        /// <remarks>
        /// <para>
        /// Gets the file name of the caller.
        /// </para>
        /// </remarks>
        public string FileName
        {
            get { return m_fileName; }
        }

        /// <summary>
        /// Gets the line number of the caller.
        /// </summary>
        /// <value>
        /// The line number of the caller.
        /// </value>
        /// <remarks>
        /// <para>
        /// Gets the line number of the caller.
        /// </para>
        /// </remarks>
        public string LineNumber
        {
            get { return m_lineNumber; }
        }

        /// <summary>
        /// Gets the method name of the caller.
        /// </summary>
        /// <value>
        /// The method name of the caller.
        /// </value>
        /// <remarks>
        /// <para>
        /// Gets the method name of the caller.
        /// </para>
        /// </remarks>
        public string MethodName
        {
            get { return m_methodName; }
        }

        /// <summary>
        /// Gets all available caller information
        /// </summary>
        /// <value>
        /// All available caller information, in the format
        /// <c>fully.qualified.classname.of.caller.methodName(Filename:line)</c>
        /// </value>
        /// <remarks>
        /// <para>
        /// Gets all available caller information, in the format
        /// <c>fully.qualified.classname.of.caller.methodName(Filename:line)</c>
        /// </para>
        /// </remarks>
        public string FullInfo
        {
            get { return m_fullInfo; }
        }

#if !(NETCF || NETSTANDARD1_3)
        /// <summary>
        /// Gets the stack frames from the stack trace of the caller making the log request
        /// </summary>
        public StackFrameItem[] StackFrames
        {
            get { return m_stackFrames; }
        }
#endif

        #endregion Public Instance Properties

        #region Private Instance Fields

        private readonly string m_className;
        private readonly string m_fileName;
        private readonly string m_lineNumber;
        private readonly string m_methodName;
        private readonly string m_fullInfo;
#if !(NETCF || NETSTANDARD1_3)
        private readonly StackFrameItem[] m_stackFrames;
#endif

        #endregion Private Instance Fields

        #region Private Static Fields

        /// <summary>
        /// The fully qualified type of the LocationInfo class.
        /// </summary>
        /// <remarks>
        /// Used by the internal logger to record the Type of the
        /// log message.
        /// </remarks>
        private readonly static Type declaringType = typeof(LocationInfo);

        /// <summary>
        /// When location information is not available the constant
        /// <c>NA</c> is returned. Current value of this string
        /// constant is <b>?</b>.
        /// </summary>
        private const string NA = "?";

        #endregion Private Static Fields
View Code

爲何會有這麼多英文註釋呢,由於這是copy的log4net源碼。。。可是LocationInfo中重載了構造函數

我須要先獲取調用日誌方法的StackTrace,而後傳入構造方法,主要由於寫入日誌用到異步,若是在異步線程內用LocationInfo(Type callerStackBoundaryDeclaringType),會致使獲取不到咱們指望的那幾個追蹤信息,由於StackTrace是在它內部new的,這會致使獲取的是異步線程的信息。因此我要在進入異步線程前將StackTrace獲取到。

因此也就有了下面這個類(LogEventDataAsync),一個傳入異步線程的數據對象,裏面保存着一些基本的初始日誌信息

  • 建立LogEventDataAsync
/// <summary>
    /// 日誌數據,傳入異步執行方法的數據
    /// 主要爲提早獲取CallerStackBoundaryDeclaringType和CallerStackTrace,避免Core(log4net源碼)下追蹤信息在異步線程內與指望不一致
    /// </summary>
    internal class LogEventDataAsync
    {
        public string Message { get; set; }
        /// <summary>
        /// 錯誤級別
        /// </summary>
        public string Level { get; set; }
        /// <summary>
        /// 日誌來源
        /// </summary>
        public string LogSource { get; set; }
        /// <summary>
        /// 調用日誌方法實例類型
        /// </summary>
        public Type CallerStackBoundaryDeclaringType { get; set; }
        /// <summary>
        /// StackTrace
        /// </summary>
        public StackTrace CallerStackTrace { get; set; }
        /// <summary>
        /// 不爲空則發送郵件,多個接收人用英文逗號隔開
        /// </summary>
        public string Emails { get; set; }
    }
View Code

到目前爲止日誌信息的準備工做就作完了,下面來賦值寫入mongodb

先來建立兩個幫助類吧,一個用於異步,一個是比較常見的請求類,都很簡單

建立Common文件夾,在下面建立兩個類

AsyncHelpers(NLog源碼)

internal static class AsyncHelpers
    {
        internal static int GetManagedThreadId()
        {
#if NETSTANDARD1_3
            return System.Environment.CurrentManagedThreadId;
#else
            return Thread.CurrentThread.ManagedThreadId;
#endif
        }

        internal static void StartAsyncTask(Action<object> action, object state)
        {
#if NET4_0 || NET4_5 || NETSTANDARD
            System.Threading.Tasks.Task.Factory.StartNew(action, state, CancellationToken.None, System.Threading.Tasks.TaskCreationOptions.None, System.Threading.Tasks.TaskScheduler.Default);
#else
            ThreadPool.QueueUserWorkItem(new WaitCallback(action), state);
#endif
        }
    }
View Code

RequestHelpers

internal class RequestHelpers
    {
        /// <summary>
        /// 組裝普通文本請求參數。
        /// </summary>
        /// <param name="parameters">Key-Value形式請求參數字典</param>
        /// <returns>URL編碼後的請求數據</returns>
        public static String BuildQuery(IDictionary<String, String> parameters)
        {
            StringBuilder postData = new StringBuilder();
            bool hasParam = false;

            IEnumerator<KeyValuePair<String, String>> dem = parameters.GetEnumerator();
            while (dem.MoveNext())
            {
                String name = dem.Current.Key;
                String value = dem.Current.Value;
                // 忽略參數名或參數值爲空的參數
                if (!String.IsNullOrEmpty(name) && !String.IsNullOrEmpty(value))
                {
                    if (hasParam)
                    {
                        postData.Append("&");
                    }

                    postData.Append(name);
                    postData.Append("=");
                    postData.Append(HttpUtility.UrlEncode(value));
                    hasParam = true;
                }
            }

            return postData.ToString();
        }

        /// <summary>
        /// 執行HTTP POST請求。
        /// 對參數值執行UrlEncode
        /// </summary>
        /// <param name="url">請求地址</param>
        /// <param name="parameters">請求參數</param>
        /// <returns>HTTP響應</returns>
        public static String DoPost(String url, IDictionary<String, String> parameters)
        {
            HttpWebRequest req = GetWebRequest(url, "POST");
            req.ContentType = "application/x-www-form-urlencoded;charset=utf-8";

            Byte[] postData = Encoding.UTF8.GetBytes(BuildQuery(parameters));
            Stream reqStream = req.GetRequestStream();
            reqStream.Write(postData, 0, postData.Length);
            reqStream.Close();

            HttpWebResponse rsp = null;
            rsp = (HttpWebResponse)req.GetResponse();

            Encoding encoding = Encoding.GetEncoding(rsp.CharacterSet);
            return GetResponseAsString(rsp, encoding);
        }

        /// <summary>
        /// 執行HTTP POST請求。
        /// 該方法在執行post時不對請求數據進行任何編碼(UrlEncode)
        /// </summary>
        /// <param name="url">請求地址</param>
        /// <param name="data">請求數據</param>
        /// <returns>HTTP響應</returns>
        public static String DoPost(String url, string data)
        {
            HttpWebRequest req = GetWebRequest(url, "POST");
            req.ContentType = "application/x-www-form-urlencoded;charset=utf-8";

            Byte[] postData = Encoding.UTF8.GetBytes(data);
            Stream reqStream = req.GetRequestStream();
            reqStream.Write(postData, 0, postData.Length);
            reqStream.Close();

            HttpWebResponse rsp = null;
            rsp = (HttpWebResponse)req.GetResponse();

            Encoding encoding = Encoding.GetEncoding(rsp.CharacterSet);
            return GetResponseAsString(rsp, encoding);
        }
        /// <summary>
        /// post數據 T messagepack序列化格式 減小傳輸數據大小
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="url"></param>
        /// <param name="model"></param>
        public static void DoPost<T>(String url, T model)
        {
            var client = new HttpClient();
            //MessagePack.Resolvers.CompositeResolver.RegisterAndSetAsDefault(
            //        NativeDateTimeResolver.Instance,
            //        ContractlessStandardResolver.Instance);
            var messagePackMediaTypeFormatter = new MessagePackMediaTypeFormatter(ContractlessStandardResolver.Instance);
            var request = new HttpRequestMessage(HttpMethod.Post, url);
            request.Content = new ObjectContent<T>(
                model,messagePackMediaTypeFormatter);            
            request.Content.Headers.ContentType.MediaType = "application/x-msgpack";
            //client.Timeout = new TimeSpan(0,0,5);
            client.SendAsync(request);
        }

        /// <summary>
        /// 執行HTTP POST請求。
        /// 該方法在執行post時不對請求數據進行任何編碼(UrlEncode)
        /// </summary>
        /// <param name="url">請求地址</param>
        /// <param name="data">請求數據</param>
        /// <returns>HTTP響應</returns>
        public static String DoPostJson(String url, string data)
        {
            HttpWebRequest req = GetWebRequest(url, "POST");
            req.ContentType = "application/json;charset=UTF-8";
            req.Accept = "application/json";
            Byte[] postData = Encoding.UTF8.GetBytes(data);
            Stream reqStream = req.GetRequestStream();
            reqStream.Write(postData, 0, postData.Length);
            reqStream.Close();

            HttpWebResponse rsp = null;
            rsp = (HttpWebResponse)req.GetResponse();

            Encoding encoding = Encoding.GetEncoding(rsp.CharacterSet);
            return GetResponseAsString(rsp, encoding);
        }
        /// <summary>
        /// 執行HTTP GET請求。
        /// </summary>
        /// <param name="url">請求地址</param>
        /// <param name="parameters">請求參數</param>
        /// <returns>HTTP響應</returns>
        public static String DoGet(String url, IDictionary<String, String> parameters)
        {
            if (parameters != null && parameters.Count > 0)
            {
                if (url.Contains("?"))
                {
                    url = url + "&" + BuildQuery(parameters);
                }
                else
                {
                    url = url + "?" + BuildQuery(parameters);
                }
            }

            HttpWebRequest req = GetWebRequest(url, "GET");
            req.ContentType = "application/x-www-form-urlencoded;charset=utf-8";

            HttpWebResponse rsp = null;
            rsp = (HttpWebResponse)req.GetResponse();

            Encoding encoding = Encoding.GetEncoding(rsp.CharacterSet);
            return GetResponseAsString(rsp, encoding);
        }

        public static HttpWebRequest GetWebRequest(String url, String method)
        {
            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
            req.Method = method;
            return req;
        }

        /// <summary>
        /// 把響應流轉換爲文本。
        /// </summary>
        /// <param name="rsp">響應流對象</param>
        /// <param name="encoding">編碼方式</param>
        /// <returns>響應文本</returns>
        public static String GetResponseAsString(HttpWebResponse rsp, Encoding encoding)
        {
            Stream stream = null;
            StreamReader reader = null;

            try
            {
                // 以字符流的方式讀取HTTP響應
                stream = rsp.GetResponseStream();
                reader = new StreamReader(stream, encoding);
                return reader.ReadToEnd();
            }
            finally
            {
                // 釋放資源
                if (reader != null) reader.Close();
                if (stream != null) stream.Close();
                if (rsp != null) rsp.Close();
            }
        }

        public static string GetUrlData(string url, string encoding, out long logSize)
        {
            logSize = 0;
            string return_value = string.Empty;
            try
            {
                HttpWebRequest wq = WebRequest.Create(url) as HttpWebRequest;
                if (wq == null)
                {
                    return return_value;
                }
                wq.Credentials = CredentialCache.DefaultCredentials;
                wq.CookieContainer = new CookieContainer();
                wq.ContentType = "text/html";
                wq.Method = "GET";
                wq.UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:22.0) Gecko/20100101 Firefox/22.0";
                wq.Host = new Uri(url).Host;
                wq.Timeout = 10000;
                try
                {
                    HttpWebResponse rep = wq.GetResponse() as HttpWebResponse;
                    logSize = rep.ContentLength;
                    Stream responseStream = rep.GetResponseStream();
                    if (rep.ContentEncoding.ToLower().Contains("gzip"))
                    {
                        responseStream = new GZipStream(responseStream, CompressionMode.Decompress);
                    }
                    else if (rep.ContentEncoding.ToLower().Contains("deflate"))
                    {
                        responseStream = new DeflateStream(responseStream, CompressionMode.Decompress);
                    }
                    StreamReader reader = new StreamReader(responseStream, Encoding.GetEncoding(encoding));
                    return_value = reader.ReadToEnd();

                    responseStream.Close();
                    reader.Close();
                    rep.Close();
                }
                catch (Exception)
                {
                    return "nolog";
                }
            }
            catch (WebException ex)
            {
                return_value = "error_error";
            }
            return return_value;
        }
    }
View Code

在RequestHelpers中真正用到的是public static void DoPost<T>(String url, T model)方法,裏面用到了MessagePack以及擴展的MediaType "application/x-msgpack",因此這裏要引用兩個nuget包

MessagePack和Sketch7.MessagePack.MediaTypeFormatter

  • 建立LogWriter

LogWriter爲調用Post方法的類,裏面主要提供了異步調用的方法以及建立完整的日誌信息功能

internal class LogWriter
    {
        /// <summary>
        /// 私有構造函數
        /// </summary>
        private LogWriter() { }
        /// <summary>
        /// 獲取LogWriter實例
        /// </summary>
        /// <returns></returns>
        public static LogWriter GetLogWriter()
        {            
            return new LogWriter();
        }        
        
        public void Writer(object logEventDataAsync)
        {
            var led = GetLoggingEventData((LogEventDataAsync)logEventDataAsync);
            var level = LogLevel.FromString(led.Level);
            string logapi = level.LogApi;
            RequestHelpers.DoPost<LogEventData>(logapi, led);//MessagePack進行數據壓縮,減少傳輸數據
        }
        /// <summary>
        /// 獲取日誌數據
        /// </summary>
        /// <param name="logEventDataAsync"></param>
        /// <returns></returns>
        private LogEventData GetLoggingEventData(LogEventDataAsync logEventDataAsync)
        {
            LocationInfo locationInfo = new LocationInfo(logEventDataAsync.CallerStackBoundaryDeclaringType, logEventDataAsync.CallerStackTrace);
            LogEventData logData = new LogEventData
            {
                Message = logEventDataAsync.Message,
                Date = DateTime.Now,
                Level = logEventDataAsync.Level,
                LogSource = string.IsNullOrEmpty(logEventDataAsync.LogSource) ? locationInfo.ClassName : logEventDataAsync.LogSource,
                ClassName = locationInfo.ClassName,
                MethodName = locationInfo.MethodName,
                LineNumber = locationInfo.LineNumber,
                FileName = locationInfo.FileName,
                IP = "NA",
                Emails = logEventDataAsync.Emails,
                FullInfo=locationInfo.FullInfo
            };
            return logData;
        }
    }
View Code

GetLoggingEventData方法中就包含了LocationInfo的實例化,之因此放這裏是由於這裏異步執行,而獲取追蹤信息須要各類反射比較耗時,否則也就沒有LogEventDataAsync這個類了,獲取IP的方法沒實現。。。

  • 建立Logger類

這個類是真正暴露給開發者使用的類,就像開源項目log.debug這樣的方式

public class Logger
    {
        private readonly static Type declaringType = typeof(Logger);
        /// <summary>
        /// 日誌寫入實例
        /// </summary>
        private LogWriter _logWriter = null;
        /// <summary>
        /// 日誌來源
        /// 默認爲調用方法所在類
        /// </summary>
        private string _logSource = string.Empty;
        /// <summary>
        /// 私有構造函數
        /// </summary>
        private Logger()
        {
            _logWriter = LogWriter.GetLogWriter();
        }
        /// <summary>
        /// 私有構造函數
        /// </summary>
        /// <param name="logSource">日誌來源</param>
        private Logger(string logSource):this()
        {
            _logSource = logSource;
        }        
        /// <summary>
        /// 獲取Logger實例
        /// 默認日誌來源爲調用方法所在類:namespace.classname
        /// </summary>
        /// <param name="logSource">日誌來源</param>
        /// <returns></returns>
        public static Logger GetLogger(string logSource=null)
        {            
            return new Logger(logSource);
        }
        /// <summary>
        /// Trace
        /// </summary>
        /// <param name="message">日誌內容</param>
        /// <param name="emails">是否發送郵件,不爲空則發送郵件,多個接收人用英文分號;隔開</param>
        public void Trace(string message, string emails = null)
        {
            WriterToTargets(message, LogLevel.Trace, emails);
        }
        /// <summary>
        /// Trace
        /// </summary>
        /// <param name="ex">異常信息</param>
        /// <param name="emails">是否發送郵件,不爲空則發送郵件,多個接收人用英文分號;隔開</param>
        public void Trace(Exception ex, string emails = null)
        {
            
            WriterToTargets(ex.ToString(), LogLevel.Trace, emails);
        }
        /// <summary>
        /// Debug
        /// </summary>
        /// <param name="message">日誌內容</param>
        /// <param name="emails">是否發送郵件,不爲空則發送郵件,多個接收人用英文分號;隔開</param>
        public void Debug(string message, string emails = null)
        {
            WriterToTargets(message, LogLevel.Debug, emails);
        }
        /// <summary>
        /// Debug
        /// </summary>
        /// <param name="ex">異常信息</param>
        /// <param name="emails">是否發送郵件,不爲空則發送郵件,多個接收人用英文分號;隔開</param>
        public void Debug(Exception ex, string emails = null)
        {
            WriterToTargets(ex.ToString(), LogLevel.Debug, emails);
        }
        /// <summary>
        /// Info
        /// </summary>
        /// <param name="message">日誌內容</param>
        /// <param name="emails">是否發送郵件,不爲空則發送郵件,多個接收人用英文分號;隔開</param>
        public void Info(string message, string emails = null)
        {
            WriterToTargets(message, LogLevel.Info, emails);
        }
        /// <summary>
        /// Info
        /// </summary>
        /// <param name="ex">異常信息</param>
        /// <param name="emails">是否發送郵件,不爲空則發送郵件,多個接收人用英文分號;隔開</param>
        public void Info(Exception ex, string emails = null)
        {
            WriterToTargets(ex.ToString(), LogLevel.Info, emails);
        }
        /// <summary>
        /// Warn
        /// </summary>
        /// <param name="message">日誌內容</param>
        /// <param name="emails">是否發送郵件,不爲空則發送郵件,多個接收人用英文分號;隔開</param>
        public void Warn(string message, string emails = null)
        {
            WriterToTargets(message, LogLevel.Warn, emails);
        }
        /// <summary>
        /// Warn
        /// </summary>
        /// <param name="ex">異常信息</param>
        /// <param name="emails">是否發送郵件,不爲空則發送郵件,多個接收人用英文分號;隔開</param>
        public void Warn(Exception ex, string emails = null)
        {
            WriterToTargets(ex.ToString(), LogLevel.Warn, emails);
        }
        /// <summary>
        /// Error
        /// </summary>
        /// <param name="message">日誌內容</param>
        /// <param name="emails">是否發送郵件,不爲空則發送郵件,多個接收人用英文分號;隔開</param>
        public void Error(string message, string emails = null)
        {
            WriterToTargets(message, LogLevel.Error, emails);
        }
        /// <summary>
        /// Error
        /// </summary>
        /// <param name="ex">異常信息</param>
        /// <param name="emails">是否發送郵件,不爲空則發送郵件,多個接收人用英文分號;隔開</param>
        public void Error(Exception ex, string emails = null)
        {
            WriterToTargets(ex.ToString(), LogLevel.Error, emails);
        }
        /// <summary>
        /// Fatal
        /// </summary>
        /// <param name="message">日誌內容</param>
        /// <param name="emails">是否發送郵件,不爲空則發送郵件,多個接收人用英文分號;隔開</param>
        public void Fatal(string message, string emails = null)
        {
            WriterToTargets(message, LogLevel.Fatal, emails);
        }
        /// <summary>
        /// Fatal
        /// </summary>
        /// <param name="ex">異常信息</param>
        /// <param name="emails">是否發送郵件,不爲空則發送郵件,多個接收人用英文分號;隔開</param>
        public void Fatal(Exception ex, string emails = null)
        {
            WriterToTargets(ex.ToString(), LogLevel.Fatal, emails);
        }
        /// <summary>
        /// 寫日誌
        /// </summary>
        /// <param name="message">日誌信息</param>
        /// <param name="level">級別</param>
        /// <param name="emails">是否發送郵件,不爲空則發送郵件,多個接收人用英文分號;隔開</param>
        private void WriterToTargets(string message, LogLevel level,string emails=null)
        {
            try
            {
                LogEventDataAsync leda = new LogEventDataAsync
                {
                    LogSource = _logSource,
                    Level = level.Name,
                    CallerStackBoundaryDeclaringType = GetType(),//獲取當前實例
                    CallerStackTrace = new StackTrace(true),//獲取當前StackTrace
                    Message = message,
                    Emails = emails
                };

                AsyncHelpers.StartAsyncTask(_logWriter.Writer, leda);//執行異步寫日誌
            }
            catch
            {
            }            
        }
View Code

代碼很是簡單,重載了最經常使用的自定義信息和exception信息,以及郵件聯繫人。日誌來源主要爲了分類日誌,像不一樣的服務、站點等等,能夠方便入庫後查詢。到這已經能夠編譯使用了,可是爲了在.net core中能夠依賴注入這個Logger,最後添加一個擴展方法

  • 建立Extensions文件夾及LoggerServiceExtension類
/// <summary>
    /// 日誌服務注入擴展類
    /// </summary>
    public static class LoggerServiceExtension
    {
        /// <summary>
        /// 注入日誌服務
        /// </summary>
        /// <param name="service">IServiceCollection</param>
        /// <param name="logSource">日誌來源,默認日誌來源爲調用方法所在類:namespace.classname</param>
        /// <returns></returns>
        public static IServiceCollection AddLoggerService(this IServiceCollection service, string logSource=null)
        {
            return service.AddTransient(factory => Logger.GetLogger(logSource));
        }
    }
View Code

3、使用(引入類庫)

通常項目使用能夠這樣,好比控制檯

class Program
    {
        static Logger logger = LogApiHandler.Logger.GetLogger("logSource");
        static void Main(string[] args)
        {
            logger.Debug("text");

            Console.ReadLine();
        }        
    }
View Code

.net core的話好比web項目能夠這樣用依賴注入

在Startup中,ConfigureServices添加一行代碼

services.AddLoggerService("TestApi");

而後在其餘類中就能夠這麼用了,固然也能夠用new的方式使用

public class ValuesController : ControllerBase
    {
        private Logger _logger;
        public ValuesController(Logger logger)
        {
            _logger = logger;
        }
        // GET api/values
        [HttpGet]
        public ActionResult<IEnumerable<string>> Get()
        {
            _logger.Error("測試依賴注入logger", "ddd@ddd.com");
            return new string[] { "value1", "value2" };
        }
    }

大致使用方式就是這樣了。

 最後得須要本身寫一個頁面來調用web api的api/log/getlist接口查詢顯示日誌

mongodb客戶端用的Robo 3T,安裝完記得修改Options--Display Dates in--Local Timezone,否則默認utc,存入時的時間少8小時。mongodb存入數據就是下圖

點此下載源碼

更新:

1.LogWriter類Writer方法加try catch,由於異步線程內異常不會被主線程捕獲

2.添加請求認證

  • LogApiHandler類庫修改

LogWriter類新增兩個字段:

private readonly string UserName = "UserName";
private readonly string Password = "Password";

RequestHelpers類重載一個請求方法:

/// <summary>
        /// Authorization 認證
        /// post數據 T messagepack序列化格式 減小傳輸數據大小
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="url"></param>
        /// <param name="model"></param>
        /// <param name="username">帳戶</param>
        /// <param name="password">密碼</param>
        public static void DoPost<T>(string url, T model,string username,string password)
        {
            var client = new HttpClient();
            var messagePackMediaTypeFormatter = new MessagePackMediaTypeFormatter(ContractlessStandardResolver.Instance);
            var request = new HttpRequestMessage(HttpMethod.Post, url);
            request.Content = new ObjectContent<T>(
                model, messagePackMediaTypeFormatter);
            request.Content.Headers.ContentType.MediaType = "application/x-msgpack";

            string encoded = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(username + ":" + password));
            request.Headers.Add("Authorization", "Basic " + encoded);

            client.SendAsync(request);
        }
View Code

修改LogWriter類Writer方法,將請求改成上面重載的新方法:

RequestHelpers.DoPost<LogEventData>(logapi, led, UserName, Password);
  • LogWebApi站點修改

appsettings.json新增配置:

"AppSettings": {    
    "RequestAuth": {
      "UserName": "UserName",
      "Password": "Password"
    }
  }

Model--AppSettings下修改AppSettings類

public class AppSettings
    {
        public SendMailInfo SendMailInfo { get; set; }
        public RequestAuth RequestAuth { get; set; }
    }
    public class SendMailInfo
    {
        public string SMTPServerName { get; set; }
        public string SendEmailAdress { get; set; }
        public string SendEmailPwd { get; set; }
        public string SiteName { get; set; }
        public string SendEmailPort { get; set; }
    }
    public class RequestAuth
    {
        public string UserName { get; set; }
        public string Password { get; set; }
    }
View Code

Extensions文件夾下新建RequestAuthorizeMiddleware類:

public class RequestAuthorizeMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly IOptions<AppSettings> _appsettings;

        public RequestAuthorizeMiddleware(RequestDelegate next, IOptions<AppSettings> appsettings)
        {
            _next = next;
            _appsettings = appsettings;
        }

        public async Task InvokeAsync(HttpContext context)
        {
            var authHeader = context.Request.Headers["Authorization"].ToString();
            if (authHeader != null && authHeader.StartsWith("basic", StringComparison.OrdinalIgnoreCase))
            {
                var token = authHeader.Substring("Basic ".Length).Trim();
                var credentialstring = Encoding.GetEncoding("ISO-8859-1").GetString(Convert.FromBase64String(token));
                var credentials = credentialstring.Split(':');
                if (credentials[0] == _appsettings.Value.RequestAuth.UserName && credentials[1] == _appsettings.Value.RequestAuth.Password)
                {
                    await _next(context);
                }
                else
                {
                    context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
                    return;
                }
            }
            else
            {
                context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
                return;
            }
        }
    }
View Code

 Startup.cs Configure添加代碼:

app.UseMiddleware(typeof(RequestAuthorizeMiddleware));
相關文章
相關標籤/搜索