En esta ocasión vamos con un post un poco más técnico, y echaremos un vistazo a cómo podemos obtener información muy valiosa conectando nuestro App Center con Application Insights y explotando los datos a través de Kusto Query Language.

App Center
Seguro que casi todos, en mayor o menor medida, utilizamos el SDK de App Center para dejar registros de nuestras aplicaciones Xamarin Forms, como analíticas, errores o crashes. Sin detenernos mucho en esta parte, vamos a dar una pincelada a cómo configurar nuestra aplicación para empezar a dejar trazas. El primer paso será dar de alta nuestra aplicación en el App Center.

Como podemos ver, desde la propia aplicación que acabamos de dar de alta, nos aparece bastante información sobre cómo empezar a configurarlo en nuestro proyecto. A continuación, abrimos la solución e instalamos los paquetes Microsoft.AppCenter.Analytics y Microsoft.AppCenter.Crashes.
Hecho esto (en la documentación oficial está muy bien explicado), vamos a empezar a configurar y dejar trazas en nuestra aplicación. Para este ejemplo vamos a partir de una aplicación muy sencilla que contiene una página principal, con un par de botones que dirigen a sendas pantallas y, en una de ellas, hay un botón que podemos pulsar.

¡Ya tenemos todo listo para empezar a utilizar App Center en nuestra aplicación Xamarin Forms!
Ahora vamos a inicializar el App Center con la clave que tenemos visible desde la web. Lo podemos hacer por ejemplo en el App.xaml.cs.
namespace AppCenterMonitoring
{
using Microsoft.AppCenter;
using Microsoft.AppCenter.Analytics;
using Microsoft.AppCenter.Crashes;
using Xamarin.Forms;
public partial class App : Application
{
public App()
{
InitializeComponent();
AppCenter.Start("ios=13119ebd-5527-4bfa-ba52-718f594daa94;", typeof(Analytics), typeof(Crashes));
AppCenter.SetUserId("Dummy user");
MainPage = new NavigationPage(new MainPage());
}
protected override void OnStart()
{
}
protected override void OnSleep()
{
}
protected override void OnResume()
{
}
}
}
Listo. Ya podemos empezar a dejar trazas de eventos y errores en nuestra aplicación. Uno de los eventos que nos pueden interesar es el OnAppearing de cada una de nuestras páginas, de esta manera podremos observar por dónde está navegando el usuario.
namespace AppCenterMonitoring
{
using System.Collections.Generic;
using Microsoft.AppCenter.Analytics;
using Xamarin.Forms;
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
protected override void OnAppearing()
{
base.OnAppearing();
Analytics.TrackEvent(nameof(OnAppearing), new Dictionary<string, string> { { "UserId", "Dummy user" }, { "Page", "MainPage" } });
}
private async void ButtonGoToPage1_Clicked(System.Object sender, System.EventArgs e)
{
await Navigation.PushAsync(new Page1());
}
private async void ButtonGoToPage2_Clicked(System.Object sender, System.EventArgs e)
{
await Navigation.PushAsync(new Page2());
}
}
}
Muy sencillo, estamos registrando un evento llamado OnAppearing y le añadimos un par de propiedades, el nombre del usuario y la página a la que hace referencia. Por supuesto, es más que posible que te interese esconder la interacción con las clases del SDK del App Center detrás de una interfaz o tener una página base donde registrar el evento OnAppearing, entre otros, pero lo vamos a dejar así por simplificar. Si has estado atento, también te darás cuenta de que habíamos añadido el UserId al inicializar el App Center y ahora también lo estamos registrando como propiedad en el evento… esto es porque el UserId sólo se añade en los registros de errores y crashes, no en los evento. De todas maneras hay una issue en GitHub donde solicitan que se añada también a los eventos, así que es posible que cuando leas esto ya esté añadido 🙂.
Vamos a añadir también un evento cuando el usuario pulse el botón que hay disponible en la segunda página.
Analytics.TrackEvent(nameof(ButtonClickMe_Clicked), new Dictionary<string, string> { { "UserId", "Dummy user" } });
Y, si se produce alguna excepción al ejecutar la acción del botón, la registramos como error y como evento.
Crashes.TrackError(exception, new Dictionary<string, string> { { "UserId", "Dummy user" } });
Analytics.TrackEvent("Error", new Dictionary<string, string> { { "UserId", "Other Dummy user" }, { "Method", nameof(ButtonClickMe_Clicked) } });
Si te estás preguntando para qué lo registramos como evento, la explicación la verás un poco más abajo.
¡Ya tenemos todo listo para poner nuestra aplicación en producción!
Nuestra aplicación ya está siendo utilizada por cientos de usuarios y nos llega un aviso de que algunos usuarios han estado experimentado errores al pulsar el botón de la segunda pantalla 😱 . Decimos, no te preocupes que ahora mismo te digo de dónde viene el error 😎, nos dirigimos al App Center y, efectivamente, localizamos el error.

Además de que la excepción que se ha generado nos dice más bien poco, hemos conseguido saber que a un tal Dummy user le ha ocurrido, a qué hora le ha ocurrido y algunos pocos datos más 😱. Pero lo que nos gustaría saber es qué ha hecho durante la sesión para que le llegase a ocurrir el error, porque parece tenemos otro usuario llamado Other Dummy user que también ha pulsado el botón pero no tiene ningún registro de error.

Es el momento de conectar nuestro App Center con Application Insights.
Application Insights + Kusto
Es muy sencillo conectar nuestro App Center con Application Insights, tan solo tenemos que dirigirnos, dentro de App Center, a la sección Settings/Export. Creamos una exportación nueva de Application Insights, seleccionaremos la suscripción de Azure a la que asociarla y por último la habilitaremos.

En unos instantes ya tenemos todos los datos del App Center disponibles en Azure como un recurso de tipo Application Insights.
Es hora de buscar qué ha ocurrido para que Dummy user haya tenido un error al pulsar en el botón. Para ello, nos dirigimos a la sección de Logs y se nos despliega una ventana para empezar a hacer queries. ¿Cómo vamos a hacer las queries? Las consultas se realizarán a través de Kusto Query Language, que, como bien dice su nombre, es un language para realizar consultas con cierto aire a SQL. Vamos a realizar una primera consulta muy sencilla en la que visualizaremos todos los customEvents en las últimas 24 horas.

Podemos ver que, entre otros eventos, nos aparecen los eventos personalizados que habíamos creado (OnAppearing y ButtonClickMe_Clicked) y, si desplegamos el elemento, podemos ver más información de un evento en concreto. Por ejemplo, si en un evento OnAppearing desplegamos su propiedad CustomDimensions/Properties nos va a aparecer la información de aquellas propiedades personalizadas que habíamos incluido al dejar la traza del evento.

Una de las cosas interesantes que nos proporciona el panel de consultas es el añadir directamente filtros a nuestra consulta a golpe de click. Por ejemplo, si queremos filtrar todos los eventos que incorporen la propiedad UserId como Dummy user, bastante con pulsar los tres puntos al lado de la propiedad UserId y seleccionar la opción Include Dummy user.

Automáticamente, al incluir la propiedad con ese valor, nos va a generar la siguiente consulta:
customEvents
| where parse_json(tostring(customDimensions.Properties)).UserId == "Dummy user"
En esta consulta se filtrarán todos los customEvents donde la propiedad personalizada UserId sea Dummy user. Aquí hay que tener un poco de cuidado con el rendimiento, si nos fijamos, se va a parsear un campo para cada uno de los registros de customEvents y si no hemos aplicado filtros previos y tenemos un montón de datos podría tardar mucho.
Con Kusto podemos hacer consultas tan complejas como queramos, pero para nuestro caso de uso, no es demasiado complicado. Si recordamos, estábamos buscando un error que le ocurría a Dummy user al pulsar el botón de nuestra app.
Unas líneas más arriba dejamos pendiente la explicación de por qué registrábamos como eventos también los errores y es debido a que en los eventos que estamos consultando, los errores se registran como HandledErrorLog y tienen dos problemas, por una parte no añaden bien el identificador de la sesión y por otra no registran las propiedades personalizadas que añadimos (hay una issue en GitHub abierta).

Como fuimos previsores y añadimos como solución alternativa a este problema el registrar los errores como eventos, vamos a olvidarnos un poco de los eventos HandledErrorLog y vamos a centrarnos en los eventos Error.
En la siguiente consulta queremos buscar las sesiones en las que el usuario Dummy user ha tenido un error al pulsar en el botón.
customEvents
| where name == "Error"
| where parse_json(tostring(customDimensions.Properties)).UserId == "Other Dummy user"
| where parse_json(tostring(customDimensions.Properties)).Method == "ButtonClickMe_Clicked"
| summarize min(timestamp) by session_Id
| order by min_timestamp
Y el resultado nos indica que ha habido una sesión con identificador 70ab83c1-c510-4877-ac3f-603ce5795a4f que contiene el error que buscamos.

Estamos ya casi acabando de identificar el error, ahora sólo queda ver todos los eventos producidos en esa sesión.
customEvents
| where session_Id == "70ab83c1-c510-4877-ac3f-603ce5795a4f"

El error ha ocurrido en dos ocasiones al pulsar 4 veces en el botón 🧐, ¿tendrá algo que ver? Vamos a revisar el código de nuestra aplicación.
private void ButtonClickMe_Clicked(System.Object sender, System.EventArgs e)
{
Analytics.TrackEvent(nameof(ButtonClickMe_Clicked), new Dictionary<string, string> { { "UserId", "Dummy user" } });
try
{
if (count == 3)
{
throw new System.Exception();
}
count++;
LabelCount.Text = count.ToString();
}
catch (System.Exception exception)
{
Crashes.TrackError(exception, new Dictionary<string, string> { { "UserId", "Dummy user" } });
Analytics.TrackEvent("Error", new Dictionary<string, string> { { "UserId", "Dummy user" }, { "Method", nameof(ButtonClickMe_Clicked) } });
}
}
Pues parece que alguien ha puesto un contador que hace que se genere una excepción la cuarta vez que se pulsa en el botón 😅.
Conclusión
Más allá de lo sencillo del ejemplo, lo importante es ver cómo se puede obtener información muy valiosa para descubrir errores de nuestras aplicaciones en producción. Si tenemos un poco de ojo poniendo trazas en nuestro código, es posible encontrar errores que no somos capaces de reproducir.
El objetivo no es llenar todo el código de trazas, sino encontrar un equilibrio entre la información que nos va a aportar y la cantidad de eventos que generaremos. Normalmente basta con tener algunos eventos en partes más o menos generales (navegación de páginas, peticiones al backend…) y en algunos puntos críticos de la aplicación que vayamos identificando. Algunas veces nos ocurrirá, normalmente en aplicaciones grandes, que nos reportan un error y nos resulta muy complicado identificar el error con las trazas de las que disponemos y necesitaremos añadirlas a posteriori al generar una nueva versión para poder identificar ese y otros errores cuando vuelvan a ocurrir.
En cualquier caso, tan solo hemos visto la punta del iceberg de lo podemos hacer con Application Insights y en concreto con Kusto. A partir de aquí se abren muchísimas posibilidades para obtener información muy interesante tanto desde la parte de desarrollo, como desde la parte de negocio para explotar los registros.