Skip to content

Instantly share code, notes, and snippets.

@ahmedalejo
Last active November 17, 2020 12:16
Show Gist options
  • Save ahmedalejo/f8957e3127b29f89fc0b42531b592080 to your computer and use it in GitHub Desktop.
Save ahmedalejo/f8957e3127b29f89fc0b42531b592080 to your computer and use it in GitHub Desktop.
A Xamarin.Insights Method Decorator using Fody´s MethodDecoratorEx plugin
using static Duration;
public class LogginViewModel
{
[ReportDuration(LongerThan = 1000)]
[ReportDuration("User Login", LongerThan = 1000)]
[ReportSlowExecution(LongerThan = FiveSeconds)]
async Task<bool> LoginAsync() { ... }
}
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