using AspectInjector.Broker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Serilog.Context;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Vista.Biz.AOP
{
/// <summary>
/// 掛在類別上的AOP。將針對類別內的 Method 加掛 Catch-And-Log 。
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
[Injection(typeof(CatchAndLogAspect))]
public class CatchAndLogAttribute : Attribute
{
public string Title { get; init; }
}
/// <summary>
/// 抬頭屬性,將被用於 CatchAndLogAspect。
/// </summary>
public class LogTitle : Attribute
{
public string Title { get; init; }
public LogTitle(string Title)
{
this.Title = Title;
}
}
[Aspect(Scope.Global)]
public class CatchAndLogAspect
{
readonly ILogger<CatchAndLogAspect> _logger;
public CatchAndLogAspect()
{
using (var serviceScope = ServiceActivator.GetScope())
{
var services = serviceScope.ServiceProvider;
_logger = services.GetRequiredService<ILogger<CatchAndLogAspect>>();
}
}
/// <summary>
/// 一般不需要[Before]。
/// </summary>
//[Advice(Kind.Before, Targets = Target.Method)] // you can have also After (async-aware), and Around(Wrap/Instead) kinds
//public void Begore([Argument(Source.Name)] string name,
// [Argument(Source.Triggers)] Attribute[] triggers,
// [Argument(Source.Type)] Type cls,
// [Argument(Source.Metadata)] System.Reflection.MethodBase meta,
// [Argument(Source.Arguments)] object[] args)
//{
// var attr1 = triggers.FirstOrDefault(c => c is CatchAndLogAttribute) as CatchAndLogAttribute;
// var attr2 = meta.GetCustomAttributes(typeof(LogTitle), false).FirstOrDefault() as LogTitle;
//
// if (name == "ShowMeTheMoney")
// {
// System.Diagnostics.Debugger.Break();
// }
//
// //※ log before
// using (LogContext.PushProperty("ClassName", cls.Name))
// using (LogContext.PushProperty("MethodName", name))
// {
// _logger.Log(LogLevel.Debug, $"[Before] {cls.Name}.{name} {attr1?.Title} {attr2?.Title} => args[{args.Length}]");
// }
//}
///// <summary>
///// 經測試正常完成才會觸發此函式。 --- 合併到[Around]以簡化。
///// </summary>
//[Advice(Kind.After, Targets = Target.Method)]
//public void After([Argument(Source.Name)] string name,
// [Argument(Source.Triggers)] Attribute[] triggers,
// [Argument(Source.Type)] Type cls,
// [Argument(Source.Metadata)] System.Reflection.MethodBase meta,
// [Argument(Source.Arguments)] object[] args,
// [Argument(Source.ReturnType)] Type retType,
// [Argument(Source.ReturnValue)] object retValue)
//{
// var attr1 = triggers.FirstOrDefault(c => c is CatchAndLogAttribute) as CatchAndLogAttribute;
// var attr2 = meta.GetCustomAttributes(typeof(LogTitle), false).FirstOrDefault() as LogTitle;
//
// //※ log after:經測試正常完成才會觸發此函式。
// using (LogContext.PushProperty("ClassName", cls.Name))
// using (LogContext.PushProperty("MethodName", name))
// using (LogContext.PushProperty("E_Method_Label", "After"))
// using (LogContext.PushProperty("E_Method_Args", args))
// using (LogContext.PushProperty("E_Method_ReturnValue", retValue))
// using (LogContext.PushProperty("E_Method_ReturnType", retType))
// {
// /// Log → [After] {method-name} = (args) => {result};
// _logger.Log(LogLevel.Information, $"[After] {attr1?.Title ?? cls.Name}.{attr2?.Title ?? name} = " +
// $"({String.Join(",", args.Select(c => c.ToString()))}) => " +
// $"{retValue}:{retType.Name}");
// }
//}
[System.Diagnostics.DebuggerHidden]
[Advice(Kind.Around, Targets = Target.Method)]
public object Around([Argument(Source.Name)] string name,
[Argument(Source.Triggers)] Attribute[] triggers,
[Argument(Source.Type)] Type cls,
[Argument(Source.Metadata)] System.Reflection.MethodBase meta,
[Argument(Source.Target)] Func<object[], object> func,
[Argument(Source.Instance)] object instance,
[Argument(Source.Arguments)] object[] args,
[Argument(Source.ReturnType)] Type retType)
{
var attr1 = triggers.FirstOrDefault(c => c is CatchAndLogAttribute) as CatchAndLogAttribute;
var attr2 = meta.GetCustomAttributes(typeof(LogTitle), false).FirstOrDefault() as LogTitle;
var page = instance?.GetType().GetCustomAttributes(typeof(PageAttribute), false).FirstOrDefault() as PageAttribute;
using (LogContext.PushProperty("ClassName", cls.Name))
using (LogContext.PushProperty("MethodName", name))
{
try
{
///※ 可以在這裡 catch, retry, authenticate, ....
//※ around begin
_logger.Log(LogLevel.Debug, $"[Around BEGIN] {attr1?.Title ?? cls.Name}.{attr2?.Title ?? name} " +
$"args[{args.Length}]:{{{String.Join(",", args.Select(c => c.ToString()))}}}");
var ret = func.Invoke(args);
////※ around end
//_logger.Log(LogLevel.Debug, $"[Around END] {attr1?.Title ?? cls.Name}.{attr2?.Title ?? name}");
using (LogContext.PushProperty("E_Method_Label", "Around DONE"))
using (LogContext.PushProperty("E_Method_Args", args))
using (LogContext.PushProperty("E_Method_ReturnValue", ret)) // retValue))
using (LogContext.PushProperty("E_Method_ReturnType", retType)) // retType))
{
/// Log → [After] {method-name} = (args) => {result};
_logger.Log(LogLevel.Information, $"[Around DONE] {attr1?.Title ?? $"@page:{page.FuncName}" ?? cls.Name}.{attr2?.Title ?? name} = " +
$"({String.Join(",", args.Select(c => c.ToString()))}) => " +
$"{ret}:{retType.Name}");
}
return ret; //※ 可以改變處理結果。
}
catch (Exception ex)
{
//※ catch exception
_logger.Log(LogLevel.Critical, $"[Around CATCH] => {attr1?.Title ?? cls.Name}.{attr2?.Title ?? name} => Exception:{ex.Message}");
throw;
}
//finally //<--- 簡化無效益的訊息。
//{
// //※ around leave
// _logger.Log(LogLevel.Debug, $"[Around LEAVE] {attr1?.Title ?? cls.Name}.{attr2?.Title ?? name}");
//}
}
}
}
}