当前位置: 首页 > news >正文

公司网站建设前期情况说明/关键词热度查询

公司网站建设前期情况说明,关键词热度查询,放在主机上的网站程序如何建压缩包然后直接下载,可以做网站背景音乐的英文歌曲开发环境vs2017,.NET Core2.1, 数据写入到mongodb。思路就是1.提供接口写入日志,2.基于接口封装类库。3.引入类库使用 源码在最后 为什么要写它 很多开源项目像nlog、log4net、elk、exceptionless等都挺好的。就是除了引入所需类库,还要在项目…

开发环境vs2017,.NET Core2.1, 数据写入到mongodb。思路就是1.提供接口写入日志,2.基于接口封装类库。3.引入类库使用

源码在最后

为什么要写它

很多开源项目像nlog、log4net、elk、exceptionless等都挺好的。就是除了引入所需类库,还要在项目中添加配置,不喜欢。elk在分布式海量数据收集和检索方面可能更能发挥它的优势,单纯记日志也可以,exceptionless就是基于elk的。就想着写一个简单易用的、可以发邮件报警的,直接引入类库就能用的一个记日志工具,所有的配置信息和入库都交给web api。这是当时问的问题,https://q.cnblogs.com/q/109489/。干脆就实现了先

接下里的代码可能有很多可以优化的地方,如果有些地方觉得不妥或者可以用更好的方式实现或组织代码,请告诉说,我改。另外实现完的接口没有加访问限制,先默认内网使用,当然有热心网友给出实现的话就更好了,像ip限制或者签名等等(已改,http basic Authorization进行认证)

一、实现Web Api

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

因为要发邮件和写入mongodb,先改配置文件appsettings.json

{"ConnectionStrings": {"ConnectionString": "mongodb://yourmongoserver","Database": "logdb","LogCollection": "logdata"},"AllowedHosts": "*","AppSettings": {"SendMailInfo": {"SMTPServerName": "smtp.qiye.163.com","SendEmailAdress": "发送人邮箱","SendEmailPwd": "","SiteName": "邮件主题","SendEmailPort": "123"}}
}
  • 实现依赖注入获取配置文件信息

创建目录结构如下图

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类

/// <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了。



二、实现类库

类库整体目录结构如下

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)){}#endregionprivate 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 Fieldsprivate 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 valuesm_lineNumber = NA;m_fileName = NA;m_method = new MethodItem();m_className = NA;try{// get frame valuesm_lineNumber = frame.GetFileLineNumber().ToString(System.Globalization.NumberFormatInfo.InvariantInfo);m_fileName = frame.GetFileName();// get method valuesMethodBase 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 infom_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 Fieldsprivate 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 fieldsm_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/1797if (callerStackBoundaryDeclaringType != null){try{StackTrace st = new StackTrace(true);int frameIndex = 0;// skip frames not from fqnOfCallingClasswhile (frameIndex < st.FrameCount){StackFrame frame = st.GetFrame(frameIndex);if (frame != null && frame.GetMethod().DeclaringType == callerStackBoundaryDeclaringType){break;}frameIndex++;}// skip frames from fqnOfCallingClasswhile (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 aboveint 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 frameStackFrame 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 infom_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 fieldsm_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/1797if (callerStackBoundaryDeclaringType != null){try{//StackTrace st = new StackTrace(true);int frameIndex = 0;// skip frames not from fqnOfCallingClasswhile (frameIndex < st.FrameCount){StackFrame frame = st.GetFrame(frameIndex);if (frame != null && frame.GetMethod().DeclaringType == callerStackBoundaryDeclaringType){break;}frameIndex++;}// skip frames from fqnOfCallingClasswhile (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 aboveint 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 frameStackFrame 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 infom_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 Fieldsprivate 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_3return System.Environment.CurrentManagedThreadId;
#elsereturn Thread.CurrentThread.ManagedThreadId;
#endif}internal static void StartAsyncTask(Action<object> action, object state){
#if NET4_0 || NET4_5 || NETSTANDARDSystem.Threading.Tasks.Task.Factory.StartNew(action, state, CancellationToken.None, System.Threading.Tasks.TaskCreationOptions.None, System.Threading.Tasks.TaskScheduler.Default);
#elseThreadPool.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),//获取当前StackTraceMessage = 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

三、使用(引入类库)

一般项目使用可以这样,比如控制台

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));

 

转载于:https://www.cnblogs.com/betterlife/p/9676033.html

http://www.lbrq.cn/news/1369405.html

相关文章:

  • 你学做网站学了多久/seo的五个步骤
  • 网站标题字符/十大免费域名
  • ts-428 WordPress/如何做seo
  • 怎么做微信小说网站/视频号链接怎么获取
  • 江门网站建设外包/yande搜索引擎官网入口
  • 移动端网站开发最好的环境/个人引流推广怎么做
  • 更改wordpress用户名/卢镇seo网站优化排名
  • 个人如何做网站/企业网站管理系统
  • 莱州网站建设青岛华夏商务网/seo优化主要工作内容
  • 郑州公司网站制作/以服务营销出名的企业
  • 遂昌建设局网站/0元做游戏代理
  • 哪个网站做招聘海报比较好/网络推广公司简介
  • 湿地公园网站建设/域名注册免费
  • 做明星网站/电子商务网站建设方案
  • 做网站和做微信小程序/百度关键词规划师
  • 搬家公司网站制作/百度百家
  • 绍兴网站建设服务/南宁网站seo外包
  • 动漫做暧视频在线观看网站/十大网站管理系统
  • 绥芬河市建设局网站/app数据分析软件
  • 软装设计素材网站/爱站关键词
  • 站长工具亚洲高清/百度站长工具域名查询
  • 网站开发网站制作报价单/软件推广
  • 池州做网站的公司/武汉大学人民医院精神卫生中心
  • 做外贸有哪些好的网站有哪些/百度搜索热度排名
  • 惠州网站网站建设/建站企业网站
  • h5 app开发 网站建设/提高搜索引擎检索效果的方法
  • 绿色环境保护网站模板/seo短视频网页入口引流免费
  • 毕业设计做网站怎么样/苏州网站制作公司
  • 做一个15页的网站怎么做/网站优化招聘
  • 做wordpress挣钱/北京关键词优化报价
  • 【图像处理基石】如何使用deepseek进行图像质量的分析?
  • Redis——常用指令汇总指南(三)(哈希类型)
  • web前端React和Vue框架与库安全实践
  • PHP面向对象编程与数据库操作完全指南-下
  • 【整数转罗马数字】
  • Docker 镜像打包为 ZIP 文件便于分享和转发