Last active
November 17, 2020 12:16
-
-
Save ahmedalejo/f8957e3127b29f89fc0b42531b592080 to your computer and use it in GitHub Desktop.
A Xamarin.Insights Method Decorator using Fody´s MethodDecoratorEx plugin
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using static Duration; | |
public class LogginViewModel | |
{ | |
[ReportDuration(LongerThan = 1000)] | |
[ReportDuration("User Login", LongerThan = 1000)] | |
[ReportSlowExecution(LongerThan = FiveSeconds)] | |
async Task<bool> LoginAsync() { ... } | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Reflection; | |
[module: MethodDecorator] | |
/// <summary> | |
/// <see cref="https://github.com/Fody/MethodDecorator"/> | |
/// <seealso cref="https://github.com/alexeysuvorov/MethodDecorator"/> | |
/// </summary> | |
public interface IMethodDecorator | |
{ | |
void Init(object instance, MethodBase method, object[] args); | |
void OnEntry(); | |
void OnExit(); | |
void OnException(Exception exception); | |
} | |
[AttributeUsage( | |
AttributeTargets.Module | | |
AttributeTargets.Method | | |
AttributeTargets.Assembly | | |
AttributeTargets.Constructor )] | |
public class MethodDecoratorAttribute : Attribute, IMethodDecorator | |
{ | |
private Lazy<IEnumerable<PropertyInfo>> _properties; | |
public MethodBase DecoratedMethod { get; private set; } | |
public MethodDecoratorAttribute() | |
{ | |
this._properties = new Lazy<IEnumerable<PropertyInfo>>(()=> | |
this.GetType() | |
.GetRuntimeProperties() | |
.Where(p => p.CanRead && p.CanWrite)); | |
} | |
public string MethodDescription { get; private set; } | |
public virtual void Init(object instance, MethodBase method, object[] args) | |
{ | |
this.UpdateFromInstance(method); | |
// | |
this.MethodDescription = method.DeclaringType.Name + "." + method.Name; | |
} | |
public virtual void OnEntry() { } | |
public virtual void OnExit() { } | |
public virtual void OnException(Exception exception) { } | |
private void UpdateFromInstance(MethodBase method) | |
{ | |
this.DecoratedMethod = method; | |
var declaredAttribute = method.GetCustomAttribute(this.GetType()) | |
foreach (var property in this._properties.Value) | |
property.SetValue(this, property.GetValue(declaredAttribute)); | |
} | |
} | |
/* | |
#for those that use **Xamarin.Insights**, Here is a mouth watering solution: | |
1. `ReportEventAttribute` -> Calls Xamarin.Insights.Track(...) if Insights is initialized | |
``` csharp | |
[ReportEvent("LoginAsync")] // or simply [ReportEvent] to use method name | |
async Task<bool> LoginAsync() | |
{ | |
... | |
} | |
``` | |
1. `ReportDurationAttribute` -> Calls Xamarin.TrackTime(...) if Insights is initialized on method call start and only disposes on end if the duration was greater than ReportDurationAttribute.LongerThan which defaults to 0 milliseconds | |
``` csharp | |
[ReportDuration("LoginAsync", LongerThan = 1000)] | |
// or simply [ReportDuration(1000)] to use method name | |
async Task<bool> LoginAsync() | |
{ | |
... | |
} | |
``` | |
1. `ReportSlowExecutionAttribute` -> Calls Xamarin.TrackTime(...) if Insights is initialized on method call start and only disposes on end if the duration was longer than ReportSlowExecutionAttribute.LongerThan which defaults to 1000 \* 5 milliseconds(5 seconds) | |
``` csharp | |
[ReportSlowExecution("LoginAsync", LongerThan = Duration.FiveSeconds)] | |
// or [ReportDuration(Duration.FiveSeconds)] | |
async Task<bool> LoginAsync() | |
{ | |
... | |
} | |
The complete code follows: | |
*/ | |
using System; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using Xamarin; | |
namespace Shared.Core.Analytics | |
{ | |
using Common; | |
public class ReportEventAttribute : InsightsAttribute | |
{ | |
public ReportEventAttribute() { } | |
public ReportEventAttribute(string Description): base(Description){ } | |
public override void HandleEntry() { this.Track(); } | |
public override void HandleExit() { } | |
} | |
public class ReportDurationAttribute : InsightsAttribute | |
{ | |
public ReportDurationAttribute() { } | |
public ReportDurationAttribute(string Description): base(Description){ } | |
public ReportDurationAttribute(double LongerThan) | |
{ | |
this.LongerThan = LongerThan; | |
} | |
private IDisposable _trackerDisposable; | |
private Stopwatch _watch; | |
public double LongerThan { get; set; } | |
public override void HandleEntry() | |
{ | |
this._watch = Stopwatch.StartNew(); | |
var detail = this.LongerThan > 0 | |
? new Dictionary<string, string> { { "Message", "Operation took longer than " + TimeSpan.FromMilliseconds(this.LongerThan) } } | |
: null; | |
this._trackerDisposable = this.TrackTime(extraData: detail); | |
} | |
public override void HandleExit() | |
{ | |
if (this._watch.ElapsedMilliseconds > LongerThan) | |
this._trackerDisposable.Dispose(); | |
} | |
} | |
/// <summary> | |
/// Defaults to reporting method/constructor execution time greater than <see cref="Duration.FiveSeconds"/> | |
/// in milliseconds | |
/// </summary> | |
public class ReportSlowExecution : ReportDurationAttribute | |
{ | |
public ReportSlowExecution() | |
{ | |
this.LongerThan = Duration.FiveSeconds; | |
} | |
public ReportSlowExecution(string Description): base(Description){ } | |
} | |
public abstract class InsightsAttribute : MethodDecoratorAttribute | |
{ | |
public InsightsAttribute() { } | |
public InsightsAttribute(string Description) | |
{ | |
this._description = Description; | |
} | |
private string _description; | |
public string Description | |
{ | |
get { return this._description ?? this.MethodDescription; } | |
set { this._description = value; } | |
} | |
public override void OnEntry() | |
{ | |
if (!Insights.IsInitialized) | |
return; | |
this.HandleEntry(); | |
} | |
public abstract void HandleEntry(); | |
public override void OnExit() | |
{ | |
if (!Insights.IsInitialized) | |
return; | |
this.HandleExit(); | |
} | |
public abstract void HandleExit(); | |
public override void OnException(Exception exception) | |
{ | |
if (!Insights.IsInitialized) | |
return; | |
this.HandleException(exception); | |
} | |
public virtual void HandleException(Exception exception) | |
{ | |
this.Track(identifier: this.Description + "- Error"); | |
Insights.Report(exception, value: this.Description); | |
} | |
public virtual void Track(string identifier = null, IDictionary<string, object> extraData = null) | |
{ | |
Insights.Track(identifier ?? this.Description, table: extraData); | |
} | |
public virtual IDisposable TrackTime(string identifier = null, IDictionary<string, string> extraData = null) | |
{ | |
return Insights.TrackTime(identifier ?? this.Description, table: extraData); | |
} | |
} | |
/// <summary> | |
/// Millisecond based durations | |
/// </summary> | |
public static class Duration | |
{ | |
public const double OneSecond = 1000; | |
public const double FiveSeconds = OneSecond * 5; | |
public const double OneMinute = OneSecond * 60; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment