背景:
随着公司的项目不断的完善,功能越来越复杂,服务也越来越多(微服务),公司迫切需要对整个系统的每一个程序的运行情况进行监控,并且能够实现对自动记录不同服务间的程序调用的交互日志,以及通一个服务或者项目中某一次执行情况的跟踪监控
根据log4net的现有功能满足不了实际需求,所以需要以log4net为基础进行分装完善,现在分装出了一个基础的版本,如有不妥之处,多多指点
功能简介: 该组件是在log4net的基础上,进行了一定的扩展封装实现的自动记录交互日志功能 该组件的封装的目的是解决一下几个工作中的实际问题 1、对记录的日志内容格式完善 2、微服务项目中,程序自动记录不同服务间的调用关系,以及出参、入参、执行时间等 3、同一项目中,不同方法及其层之间的调用关系等信息 4、其最终目的就是,实现对系统的一个整体监控
主要封装扩展功能点: 1、通过对log4net进行扩展,能够自定义了一些日志格式颜色内容等 2、通过代理+特性的方式,实现程序自动记录不同服务间,以及同一程序间的相互调用的交互日志 3、采用队列的方式实现异步落地日志到磁盘文件
主要核心代码示例,具体的详细代码,我已经上传至githut开源项目中,如有需要可以下载了解
github源码地址:https://github.com/xuyuanhong0902/XYH.Log4Net.Extend.git
代理实现自动记录方法调用的详细日志
///
/// <summary>
/// 重写代理实现.
/// </summary>
/// <param name="msg">代理函数</param>
/// <returns>返回结果</returns>
public override IMessage Invoke(IMessage methodInvoke)
{
//// 方法开始执行时间
DateTime executeStartTime = System.DateTime.Now;
//// 方法执行结束时间
DateTime executeEndTime = System.DateTime.Now;
IMessage message = null;
IMethodCallMessage call = methodInvoke as IMethodCallMessage;
object[] customAttributeArray = call.MethodBase.GetCustomAttributes(false);
call.MethodBase.GetCustomAttributes(false);
try
{
// 前处理.
List<IAopAction> proActionList = this.InitAopAction(customAttributeArray, AdviceType.Before);
//// 方法执行开始记录日志
if (proActionList != null && proActionList.Count > 0 )
{
foreach (IAopAction item in proActionList)
{
IMessage preMessage = item.PreProcess(methodInvoke, base.GetUnwrappedServer());
if (preMessage != null)
{
message = preMessage;
}
}
if (message != null)
{
return message;
}
}
message = Proessed(methodInvoke);
// 后处理.
proActionList = this.InitAopAction(customAttributeArray, AdviceType.Around);
//// 方法执行结束时间
executeEndTime = System.DateTime.Now;
//// 方法执行结束记录日志
if (proActionList != null && proActionList.Count > 0)
{
foreach (IAopAction item in proActionList)
{
item.PostProcess(methodInvoke, message, base.GetUnwrappedServer(), executeStartTime, executeEndTime);
}
}
}
catch (Exception ex)
{
//// 方法执行结束时间
executeEndTime = System.DateTime.Now;
// 异常处理.吃掉异常,不影响主业务
List<IAopAction> proActionList = this.InitAopAction(customAttributeArray, AdviceType.Around);
if (proActionList != null && proActionList.Count > 0)
{
foreach (IAopAction item in proActionList)
{
item.ExceptionProcess(ex, methodInvoke, base.GetUnwrappedServer(), executeStartTime, executeEndTime);
}
}
}
return message;
}
/// <summary>
/// 处理方法执行.
/// </summary>
/// <param name="methodInvoke">代理目标方法</param>
/// <returns>代理结果</returns>
public virtual IMessage Proessed(IMessage methodInvoke)
{
IMessage message;
if (methodInvoke is IConstructionCallMessage)
{
message = this.ProcessConstruct(methodInvoke);
}
else
{
message = this.ProcessInvoke(methodInvoke);
}
return message;
}
/// <summary>
/// 普通代理方法执行.
/// </summary>
/// <param name="methodInvoke">代理目标方法</param>
/// <returns>代理结果</returns>
public virtual IMessage ProcessInvoke(IMessage methodInvoke)
{
IMethodCallMessage callMsg = methodInvoke as IMethodCallMessage;
object[] args = callMsg.Args; //方法参数
object o = callMsg.MethodBase.Invoke(base.GetUnwrappedServer(), args); //调用 原型类的 方法
return new ReturnMessage(o, args, args.Length, callMsg.LogicalCallContext, callMsg); // 返回类型 Message
}
/// <summary>
/// 构造函数代理方法执行.
/// </summary>
/// <param name="methodInvoke">代理目标方法</param>
/// <returns>代理结果</returns>
public virtual IMessage ProcessConstruct(IMessage methodInvoke)
{
IConstructionCallMessage constructCallMsg = methodInvoke as IConstructionCallMessage;
IConstructionReturnMessage constructionReturnMessage = this.InitializeServerObject((IConstructionCallMessage)methodInvoke);
RealProxy.SetStubData(this, constructionReturnMessage.ReturnValue);
return constructionReturnMessage;
}
/// <summary>
/// 代理包装业务处理.
/// </summary>
/// <param name="customAttributeArray">代理属性</param>
/// <param name="adviceType">处理类型</param>
/// <returns>结果.</returns>
public virtual List<IAopAction> InitAopAction(object[] customAttributeArray, AdviceType adviceType)
{
List<IAopAction> actionList = new List<IAopAction>();
if (customAttributeArray != null && customAttributeArray.Length > 0)
{
foreach (Attribute item in customAttributeArray)
{
XYHMethodAttribute methodAdviceAttribute = item as XYHMethodAttribute;
if (methodAdviceAttribute != null && (methodAdviceAttribute.AdviceType == adviceType))
{
if (methodAdviceAttribute.ProcessType == ProcessType.None)
{
continue;
}
if (methodAdviceAttribute.ProcessType == ProcessType.Log)
{
actionList.Add(new LogAopActionImpl());
continue;
}
}
}
}
return actionList;
}
}
类注解
///
public override MarshalByRefObject CreateInstance(Type serverType)
{
XYHAopProxy realProxy = new XYHAopProxy(serverType);
return realProxy.GetTransparentProxy() as MarshalByRefObject;
}
}
队列实现异步日志落地到磁盘文件
namespace XYH.Log4Net.Extend { ///
/// <summary>
/// 信号
/// </summary>
private readonly ManualResetEvent extendLogMre;
/// <summary>
/// 日志
/// </summary>
private static ExtendLogQueue _flashLog = new ExtendLogQueue();
/// <summary>
/// 构造函数
/// </summary>
private ExtendLogQueue()
{
extendLogQue = new ConcurrentQueue<LogMessage>();
extendLogMre = new ManualResetEvent(false);
}
/// <summary>
/// 单例实例
/// </summary>
/// <returns></returns>
public static ExtendLogQueue Instance()
{
return _flashLog;
}
/// <summary>
/// 另一个线程记录日志,只在程序初始化时调用一次
/// </summary>
public void Register()
{
Thread t = new Thread(new ThreadStart(WriteLogDispatch));
t.IsBackground = false;
t.Start();
}
/// <summary>
/// 从队列中写日志至磁盘
/// </summary>
private void WriteLogDispatch()
{
while (true)
{
//// 如果队列中还有待写日志,那么直接调用写日志
if (extendLogQue.Count > 0)
{
//// 根据队列写日志
WriteLog();
// 重新设置信号
extendLogMre.Reset();
}
//// 如果没有,那么等待信号通知
extendLogMre.WaitOne();
}
}
/// <summary>
/// 具体调用log4日志组件实现
/// </summary>
private void WriteLog()
{
LogMessage msg;
// 判断是否有内容需要如磁盘 从列队中获取内容,并删除列队中的内容
while (extendLogQue.Count > 0 && extendLogQue.TryDequeue(out msg))
{
new LogHandlerImpl(LogHandlerManager.GetILogger(msg.LogSerialNumber)).WriteLog(msg);
}
}
/// <summary>
/// 日志入队列
/// </summary>
/// <param name="message">日志文本</param>
/// <param name="level">等级</param>
/// <param name="ex">Exception</param>
public void EnqueueMessage(LogMessage logMessage)
{
//// 日志入队列
extendLogQue.Enqueue(logMessage);
// 通知线程往磁盘中写日志
extendLogMre.Set();
}
}
} 自定义扩展log4net日志格式内容
namespace XYH.Log4Net.Extend { ///
//// 方法名称
this.AddConverter("MethodName", typeof(LogMethodNamePatternConvert));
//// 方法入参
this.AddConverter("MethodParam", typeof(LogMethodParamConvert));
//// 方法出参
this.AddConverter("MethodResult", typeof(LogMethodResultConvert));
//// 程序名称
this.AddConverter("LogProjectName", typeof(LogProjectNamePatternConvert));
//// IP 地 址
this.AddConverter("LogIpAddress", typeof(LogServiceIpPatternConvert));
//// 日志编号
this.AddConverter("LogUniqueCode", typeof(LogUniquePatternConvert));
//// 日志序列号
this.AddConverter("LogSerialNumber", typeof(LogSerialNumberPatternConvert));
//// 调用路径
this.AddConverter("InvokeName", typeof(LogInvokeNamePatternConvert));
//// 执行开始时间
this.AddConverter("ExecuteStartTime", typeof(ExecuteStartTimePatternConvert));
//// 执行结束时间
this.AddConverter("ExecuteEndTime", typeof(ExecuteEndTimePatternConvert));
//// 执行时间
this.AddConverter("ExecuteTime", typeof(ExecuteTimePatternConvert));
}
}
}
使用说明: 第一步:需要dll文件引用 需要引用两个dell文件: jeson序列化:Newtonsoft.Json.dll log4net组件:log4net.dll log3net扩展组件:XYH.Log4Net.Extend.dll
第二步:log4配置文件配置 主要配置日志的存储地址,日志文件存储格式、内容等 下面,给一个参考配置文件,具体的配置可以根据实际需要自由配置,其配置方式很log4net本身的配置文件一样,在此不多说
第三步:在Global.asax文件中注册消息队列 protected void Application_Start() { AreaRegistration.RegisterAllAreas(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles);
////注册日志队列 ExtendLogQueue.Instance().Register(); }
第四步:在Global.asax文件中生成处理日志序列号 ///
第五步:在需要自动记录日志的方法类上加上对应的注解
//// 在需要自动记录日志的类上加上 XYHAop注解 [XYHAop] public class Class2: calssAdd { //// 需要记录自动记录交互日志的方法注解 ProcessType.Log
//// 同时该类还必须继承ContextBoundObject
[XYHMethod(ProcessType.Log)] public int AddNum(int num1, int num2) { } //// 需要记录自动记录交互日志的方法注解 ProcessType.None,其实不加注解也不会记录日志 [XYHMethod(ProcessType.None)] public int SubNum(int num1, int num2) { } }
第六步:完成上面五步已经能够实现自动记录交互日志了,
但是在实际使用中我们也会手动记录一些日志,本插件也支持手动记录日志的同样扩展效果
目前支持以下6中手动记录日志的重载方法
///
/// <summary>
/// 添加日志.
/// </summary>
/// <param name="message">日志信息对象</param>
/// <param name="level">日志信息级别</param>
public static void WriteLog(object message, LogLevel level)
{
new MessageIntoQueue().WriteLog(message, level);
}
/// <summary>
/// 添加日志.
/// </summary>
/// <param name="message">日志信息对象</param>
/// <param name="level">日志信息级别</param>
/// <param name="exception">异常信息对象</param>
public static void WriteLog(object message, Exception exception)
{
new MessageIntoQueue().WriteLog(message, exception);
}
/// <summary>
/// 添加日志.
/// </summary>
/// <param name="message">日志信息对象</param>
/// <param name="methodName">方法名</param>
/// <param name="methodParam">方法入参</param>
/// <param name="methodResult">方法请求结果</param>
public static void WriteLog(object message, string methodName, object methodParam, object methodResult)
{
new MessageIntoQueue().WriteLog(message, methodName, methodParam, methodResult);
}
/// <summary>
/// 添加日志.
/// </summary>
/// <param name="message">日志信息对象</param>
/// <param name="methodName">方法名</param>
/// <param name="methodParam">方法入参</param>
/// <param name="methodResult">方法请求结果</param>
/// <param name="level">日志记录级别</param>
public static void WriteLog(object message, string methodName, object methodParam, object methodResult, LogLevel level)
{
new MessageIntoQueue().WriteLog(message, methodName, methodParam, methodResult, level);
}
/// <summary>
/// 添加日志
/// </summary>
/// <param name="extendLogInfor">具体的日志消息model</param>
public static void WriteLog(LogMessage extendLogInfor)
{
new MessageIntoQueue().WriteLog(extendLogInfor);
}
}
}
object message = "一个参数日志记录单元测试"; // TODO: 初始化为适当的值 XYHLogOperator.WriteLog(message);
如有问题,欢迎QQ随时交流 QQ:1315597862
实际日志效果实例: 日志编号:BDCA8E9FFD834C6EA8FE612E4C21F815 日志序列:D8EE83631535475DB4E5CD086EAB5238 机器名称:BL261P0EJ35SFKG IP 地 址:192.168.1.198 开始时间:2019-06-09 16:10:03 4409 结束时间:2019-06-09 16:10:54 8546 执行时间:51.4136955秒 程序名称:LogOperationTest.Class2, LogOperationTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null 方法名称:AddNum 方法入参:[1,11] 方法出参:(null) 日志信息: 日志时间:2019-06-09 16:10:54,951 日志级别:ERROR 异常堆栈:System.FormatException: 输入字符串的格式不正确。 在 System.Number.StringToNumber(String str, NumberStyles options, NumberBuffer& number, NumberFormatInfo info, Boolean parseDecimal) 在 System.Number.ParseInt32(String s, NumberStyles style, NumberFormatInfo info) 在 System.Convert.ToInt32(String value) 在 LogOperationTest.Class2.AddNum(Int32 num1, Int32 num2) 位置 C:\Users\Administrator\Documents\WeChat Files\xu15908150902\FileStorage\File\2019-05\公共服务\LogOperationService\LogOperationTest\Class2.cs:行号 23
今天有一个伙伴在博客上问,用改组件,怎么去捕捉记录全局未处理的异常日志。 其实方法很简单,实现步骤如下: 重新Global.asax中的Application_Error方法 ///
Exception baseException = lastException.GetBaseException();
if (baseException == null)
{
return;
}
//// 记录异常日志
XYHLogOperator.WriteLog("全局异常捕获", baseException);
if (baseException is HttpRequestValidationException)
{
Response.Write("请不要攻击我,我很脆弱的!!!");
}
else
{
//// 跳转至指定的错误页面
//// 此处可根据实际需要进行改造,比如有的是直接提供接口的服务,那么这样重定向到一个统一的错误页面,肯定就不怎么合适
Response.Redirect("/pagError.html");
}
}
finally
{
//// 同过配置文件,盘端是否需要把未处理的异常呈现到客户端
if (System.Configuration.ConfigurationManager.AppSettings["ShowErrorOnPage"] == null)
{
Server.ClearError();
}
}
}