Centric connect.engage.succeed

Application Insights Rocks!

Geschreven door Dick van Straaten - 23 augustus 2016

Dick van Straaten
Vandaag de dag houd je bij het ontwikkelen van weboplossingen rekening met een diversiteit aan schermgroottes, apparaten, locaties en snelheid. De reden hiervoor is de mobile-first- en progressive enhancement-benadering. Je bouwt snelle en duurzame oplossingen die door hun gelaagdheid na oplevering gemakkelijk onderhouden, doorontwikkeld en gemigreerd kunnen worden. Gaat het om een cloudoplossing? Dan moet je ook nog eens rekening houden met de factor schaalbaarheid. Al die kenmerken blijven gedurende de hele levenscyclus van de oplossing gelden.

Eenmaal in productie blijkt soms dat bepaalde doelstellingen niet gehaald zijn: de website functioneert niet naar verwachting of de performance-eisen komen niet overeen. Wat hiervan precies de oorzaak is, is lang niet altijd direct duidelijk. Ligt het aan de website of de infrastructuur? Voor ontwikkelaars en beheerders is het vaak een speurtocht door gedifferentieerde logs en gebruikersmeldingen, waarvan de relatie niet direct duidelijk is.

Visual Studio Application Insights biedt uitkomst. Via realtime monitoring alerts en een centraal dashboard kun je (prestatie)problemen in weboplossingen in productie gemakkelijk (pro)actief detecteren en analyseren.

Integratie

Voor het gebruik van Application Insights heb je een gratis Azure-account nodig. Bij het starten van een nieuw project in Visual Studio kan Application Insights dan automatisch voor het project worden geactiveerd.

Scherm Application Insights

Bij bestaande projecten kan de functionaliteit gemakkelijk via de NuGet Manager worden toegevoegd.

Custom Events

Iedere ontwikkelaar kent het wel: een eindgebruiker meldt een issue, maar kan je niet voorzien van de informatie om een issue te reproduceren. Welke browser wordt er gebruikt, welke foutmelding verschijnt er, hoe is er naar deze melding genavigeerd en wat heeft de gebruiker ingevoerd?

Application Insights heeft standaard de antwoorden op de eerste drie vragen. Via het dashboard kan de route tot de foutmelding en de stack trace worden onderzocht. De melding zelf kan helaas niet altijd gereproduceerd worden.

Naast alle standaard functionaliteit biedt de Application Insights library de TelemetryClient, waarmee custom events en bijbehorende data geregistreerd worden. Dit biedt mogelijkheden voor ontwikkelaars en DevOps-teams. Mij heeft het geïnspireerd een library te maken, waarmee ik ook gebruikersinput kan meesturen.

Custom Library

Gebruikersinput wordt in een gelaagde oplossing als object geregistreerd. Via een class extension wordt het object via Reflection omgezet in een KeyValuePair Dictionary.

public static class ObjectExtensions
    {
        /// <summary>
        /// Return Object Properties and Values as Dictionary
        /// </summary>
        /// <param name="source"></param>
        /// <param name="bindingAttr"></param>
        /// <returns></returns>
        public static IDictionary<string, object> AsObjectDictionary(this object source,
            BindingFlags bindingAttr = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance)
        {
            if (!Enum.IsDefined(typeof(BindingFlags), bindingAttr))
                throw new ArgumentOutOfRangeException(nameof(bindingAttr));
            return source.GetType().GetProperties(bindingAttr).ToDictionary
            (
                propInfo => propInfo.Name,
                propInfo => propInfo.GetValue(source, null)
            );
        }

        /// <summary>
        /// Return Parent (and Child) Object(s) Properties and Values as Dictionary
        /// </summary>
        /// <param name="source"></param>
        /// <param name="bindingAttr"></param>
        /// <returns></returns>
        public static IDictionary<string, string> AsStringDictionary(this object source,
            BindingFlags bindingAttr = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance)
        {
            return GetPropertInfos(source, null);
        }

        private static IDictionary<string, string> GetPropertInfos<T>(T someObject, string parent = null) where T : class
        {
            var propertInfos = new Dictionary<string, string>();

            var objectType = someObject.GetType();
            var propertyInfoList = objectType.GetTypeInfo().DeclaredProperties;

            foreach (var propertyInfo in propertyInfoList)
            {
                // Skip indexer parameters, they don't have a value
                if (propertyInfo.GetIndexParameters().Length > 0)
                    continue;

                if (propertyInfo.PropertyType.Namespace != "System")
                {
                    // Recursive Call for Class Properties 
                    if (propertyInfo.GetValue(someObject) != null)
                        propertInfos = propertInfos.Union(GetPropertInfos(propertyInfo.GetValue(someObject), objectType.Name))
                                                   .ToDictionary(p => p.Key, p => p.Value);
                }
                else
                {
                    var value = propertyInfo.GetValue(someObject);
                    var stringValue = value?.ToString() ?? "";
                    var name = ${body}quot;{objectType.Name}.{propertyInfo.Name}";

                    propertInfos.Add(IsNullOrWhiteSpace(parent) ? name : ${body}quot;{parent}.{name}", stringValue);
                }
            }

            return propertInfos;
        }
    }

Door de object properties en values als KeyValuePair te vertalen, worden ze door Application Insights geïndexeerd. Zo kan ik in het dashboard specifieke properties en/of values zoeken.

Via mijn InsightsClient breid ik de functionaliteit van de TelemetryClient uit. Ik kan met deze client custom events en/of exceptions met het bijbehorende object registreren bij Application Insights.

public class InsightsClient
    {
        private TelemetryClient _currentTelemetryClient;

        /// <summary>
        /// Gets a reference to the current TelemetryClient.
        /// </summary>
        public TelemetryClient CurrentTelemetryClient
        {
            get
            {
                if (_currentTelemetryClient == null)
                {
                    _currentTelemetryClient = new TelemetryClient();
                    _currentTelemetryClient.Context.InstrumentationKey = InsightsClientConfiguration.Current.InstrumentationKey;
                }

                return _currentTelemetryClient;
            }
        }

        private static InsightsClient _current;

        /// <summary>
        /// Gets a reference to the current InsightsClient.
        /// </summary>

        public static InsightsClient Current => _current ?? (_current = new InsightsClient());

        /// <summary>
        /// Register Custom Event with associoted Class properties and values
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="eventName"></param>
        /// <param name="someObject"></param>
        public void RegisterObjectEvent<T>(string eventName, T someObject) where T : class
        {
            var properties = someObject.AsStringDictionary();

            CurrentTelemetryClient.TrackEvent(eventName, properties);
        }

        /// <summary>
        /// Register an Exception with associoted Class properties and values
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="exception"></param>
        /// <param name="someObject"></param>
        public void RegisterObjectException<T>(Exception exception, T someObject) where T : class
        {
            var properties = someObject.AsStringDictionary();

            CurrentTelemetryClient.TrackException(exception, properties);
        }

        /// <summary>
        /// Trace a Class with properties and values with <see cref="SeverityLevel"/> Verbose
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="message"></param>
        /// <param name="someObject"></param>
        public void RegisterObjectTrace<T>(string message, T someObject) where T : class
        {
            RegisterObjectTrace(message, SeverityLevel.Verbose, someObject);
        }

        /// <summary>
        /// Trace a Class with properties and values
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="message"></param>
        /// <param name="severityLevel">Verbose, Information, Warning, Error, Critical</param>
        /// <param name="someObject"></param>
        public void RegisterObjectTrace<T>(string message, SeverityLevel severityLevel, T someObject) where T : class
        {
            var properties = someObject.AsStringDictionary();

            CurrentTelemetryClient.TrackTrace("", SeverityLevel.Critical, properties);
        }
    }

Door de (pro)actieve realtime detectie (vaak al voordat een eindgebruiker een issue meldt) kan ik nu mogelijke en onverwachte exceptions met gebruikersinput registreren en issues reconstrueren. Application Insights Rocks!

var person = new Person() { FirstName = "Dick", LastName = "van Straaten", EmailAddress = "dick.van.straaten@centric.eu" };
            InsightsClient.Current.RegisterObjectEvent("Craft: Custom Event Test", person);
            InsightsClient.Current.RegisterObjectException(new NotImplementedException(), person);

Craft Custom Events

Craft Custom Properties

De volledige library is beschikbaar op https://github.com/CentricStraaten/Bengkel.Library.ApplicationInsights.

Meer informatie over Application Insights op https://azure.microsoft.com/nl-nl/services/application-insights/.

Over Dick

Dick van Straaten is Craft Expert van Team Azure Development binnen Craft, hét groeiprogramma voor IT'ers (powered by Centric). Wil je zijn blog volgen? Schrijf je in voor de Craft-update.

     
Schrijf een reactie
  • Captcha image
  • Verzenden