Wilson Vargas

Avizoro: crash reporting y observabilidad self-hosted para tu app .NET MAUI

Avizoro es open source y self-hosted: crashes, ANRs, sesiones y rendimiento de tu app .NET MAUI con una línea, en tu propio servidor.

Avizoro: crash reporting y observabilidad self-hosted para tu app .NET MAUI

¿Qué haces cuando un usuario te escribe "la app se cerró sola" y eso es todo lo que sabes? Sin un stack trace, sin saber en qué pantalla estaba ni qué versión tenía instalada, lo único que queda es adivinar. Desde que empecé con Xamarin en 2017, la respuesta a esa pregunta casi siempre pasó por un servicio de terceros que se quedaba con los datos de mis usuarios.

Hoy quiero contarte sobre Avizoro, un proyecto open source (licencia MIT) en el que vengo trabajando: una plataforma de crash reporting y observabilidad para .NET que corre en tu propio servidor. Se integra en tu app .NET MAUI con una línea y te muestra crashes, ANRs, sesiones y rendimiento en un dashboard en tiempo real.

Vamos a ver qué captura, cómo levantar el servidor con Docker, cómo conectarlo a tu app y, sobre todo, cuándo te conviene y cuándo no.

¿Qué cambió y por qué importa?

Durante años, la respuesta por defecto para una app Xamarin fue Visual Studio App Center. Eso se acabó. Según la página oficial de retiro, App Center se retiró el 31 de marzo de 2025, y lo único que sigue vivo es Analytics & Diagnostics, con fecha de caducidad.

Línea de tiempo: el retiro de App Center y las versiones de Avizoro
Fecha Qué pasó
31 de marzo de 2025 Se retira App Center (Build, Test, Distribution). Analytics & Diagnostics queda con prórroga.
15 de abril de 2026 Azure Monitor para móviles entra en Public Preview como camino de migración.
19 de agosto de 2026 Sale la versión 1.0.0 de Avizoro (todavía con el nombre IntelligenceKit).
12 de octubre de 2026 Avizoro 2.0.0: nuevo nombre y la session timeline.
Fin de marzo de 2027 Termina el soporte de Analytics & Diagnostics en App Center.

Para diagnósticos, Microsoft recomienda servicios comerciales (Datadog, Dynatrace, New Relic) o el nuevo Azure Monitor para móviles, que al momento de escribir esto sigue en preview. Todos son servicios en la nube de alguien más.

A mi parecer, faltaba una opción más: algo ligero, pensado para .NET MAUI y que puedas hospedar tú mismo. Esa es la idea detrás de Avizoro:

Importante: Avizoro antes se publicaba como IntelligenceKit. Si vienes de la 1.x, la 2.0.0 cambia los IDs de los paquetes, los namespaces y los nombres de los tipos, pero el comportamiento es el mismo: la migración es un buscar y reemplazar. El detalle está en el CHANGELOG.

¿Cómo funciona por dentro?

Avizoro tiene tres piezas: el SDK que vive en tu app, el servidor que recibe y agrupa los eventos, y el dashboard (Blazor WebAssembly) que los muestra en vivo con SignalR.

Arquitectura de Avizoro: de la app al dashboard

Lo que más me gusta del diseño es la cola offline. Cuando tu app se cae, el proceso está a punto de morir y no es momento de hacer una llamada de red. Por eso el crash se escribe primero en una base SQLite local (avizoro.db3, dentro del AppDataDirectory) y se envía en el siguiente arranque. Lo mismo pasa si el usuario está sin conexión, o si el servidor responde 429 por el límite de peticiones: el evento se reintenta después y no se pierde.

Del lado del servidor, los eventos con el mismo tipo de excepción y el mismo primer frame de tu código se agrupan en un solo issue. Los frames de System.* y Microsoft.* se saltan, así que un NullReferenceException dentro de LINQ se agrupa por el método tuyo que llamó a LINQ, no por LINQ.

Paso 1: prueba el demo en dos minutos

Antes de tocar tu app, mira cómo se ve. El repo trae un stack de demo con dos semanas de datos de ejemplo (versiones, una regresión, ANRs, endpoints lentos y feedback de usuarios) en modo solo lectura.

Necesitas Docker. Si estás en Windows y todavía no lo tienes, te conté cómo instalarlo en Cómo instalar Docker en Windows 11 (WSL) sin Docker Desktop.

git clone https://github.com/avizoro/avizoro
cd avizoro
AVIZORO_READ_TOKEN=demo docker compose -f docker-compose.yml -f docker-compose.demo.yml up -d

En PowerShell, la variable de entorno se define aparte:

$env:AVIZORO_READ_TOKEN = "demo"
docker compose -f docker-compose.yml -f docker-compose.demo.yml up -d

Abre http://localhost:8080 y entra con el token demo.

Paso 2: levanta tu propio servidor

Cuando quieras usarlo de verdad, el mismo docker-compose.yml levanta PostgreSQL, el servidor y el dashboard:

cp .env.example .env      # edita AVIZORO_READ_TOKEN con un valor largo y aleatorio
docker compose up -d

El servidor crea y migra el esquema solo al arrancar. Y no estás amarrado a PostgreSQL:

Base de datos ¿Cuándo usarla?
SQLite (por defecto) Pruebas locales o equipos pequeños. Cero configuración.
PostgreSQL Lo que usa el stack de Docker Compose. Mi opción para producción.
SQL Server Si tu equipo ya lo opera.
Ojo: en Docker el servidor corre en modo Production, y ahí la lectura es fail-closed: si no defines AVIZORO_READ_TOKEN, el dashboard y la API de consulta quedan bloqueados en vez de servir datos sin protección. Y en producción, ponlo siempre detrás de HTTPS.

Si no quieres administrar una VM, el repo incluye plantillas para Render, Azure App Service y Railway en deploy/README.md.

Paso 3: crea un proyecto y copia el DSN

Entra al dashboard con tu token de administrador y ve a Projects → New project. La página te muestra el DSN del proyecto, que es una sola cadena con todo lo que el SDK necesita:

http://{projectKey}@{host}:{port}/{projectId}

También puedes crearlo desde un script:

curl -X POST http://localhost:7099/admin/projects \
  -H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" \
  -d '{ "projectId": "mi-app", "name": "Mi App" }'
Importante: la projectKey no es un secreto. Es un identificador público que viaja dentro de tu app y solo sirve para enrutar eventos. El servidor acepta eventos únicamente de proyectos registrados y aplica un límite de peticiones por IP (300 cada 60 segundos por defecto). Lo que sí es secreto es el token de administrador y la read key de cada proyecto.

Paso 4: agrega el SDK a tu app .NET MAUI

Instala el paquete (trae Avizoro.Core como dependencia):

dotnet add package Avizoro.Maui

Y en MauiProgram.cs:

using Avizoro.Maui.Extensions;

public static class MauiProgram
{
#if ANDROID
    // 10.0.2.2 es el alias del emulador de Android para tu máquina.
    private const string Dsn = "http://projectKey@10.0.2.2:7099/mi-app";
#else
    // El simulador de iOS, Mac Catalyst y Windows llegan directo a localhost.
    private const string Dsn = "http://projectKey@localhost:7099/mi-app";
#endif

    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .UseAvizoro(Dsn);

        return builder.Build();
    }
}

Eso es todo. Esa llamada registra la captura de crashes, la cola offline, las sesiones, la detección de ANR, los breadcrumbs de navegación, los tiempos de carga de cada página y la integración con ILogger. El nombre y la versión de la app se detectan solos.

Ojo: el emulador de Android no llega a tu máquina con localhost; usa 10.0.2.2. Además, como en local el DSN usa http, Android necesita permiso para tráfico sin cifrar. El sample del repo lo resuelve con android:usesCleartextTraffic="true" en el AndroidManifest.xml. Úsalo solo para desarrollo: en producción tu DSN debe ser https.
Lo que hace una línea y lo que puedes activar

Lo que es opt-in

Algunas funciones vienen apagadas porque envían más datos o pueden tocar información personal. Las activas tú:

builder
    .UseMauiApp<App>()
    .UseAvizoro(Dsn, o =>
    {
        o.Environment = "staging";

        // Captura reducida de la última pantalla, adjunta al crash.
        o.EnableScreenCapture = true;
        o.ScreenCaptureExcludedPages.Add("LoginPage");

        // En el siguiente arranque le pregunta al usuario qué estaba haciendo.
        o.EnableCrashFeedbackPrompt = true;
        o.CrashFeedbackTitle = "La app se cerró inesperadamente";
        o.CrashFeedbackMessage = "¿Qué estabas haciendo? (opcional)";
        o.CrashFeedbackAccept = "Enviar";
        o.CrashFeedbackCancel = "Omitir";

        // Graba toda la sesión como una línea de tiempo.
        o.EnableSessionTimeline = true;
    });

// Breadcrumbs y tiempos de cada llamada HTTP de este cliente.
builder.Services.AddHttpClient("api")
    .AddAvizoroHandler();

La session timeline es la novedad de la 2.0.0. Normalmente los breadcrumbs viajan con un error, así que una sesión sin errores no deja rastro. Con esta opción, el SDK graba la sesión completa (pantallas, acciones, llamadas HTTP, logs y capturas) y el dashboard la muestra como una línea de tiempo que puedes ver en vivo mientras la app corre.

Paso 5: reporta tus propios eventos

Los crashes se capturan solos. Para lo demás, inyecta IAvizoro:

using Avizoro.Core.Services;

public class CheckoutViewModel(IAvizoro avizoro)
{
    public async Task PayAsync()
    {
        avizoro.SetUser("user-42");            // opcional: un id anónimo
        avizoro.SetTag("plan", "pro");
        avizoro.AddBreadcrumb("Tocó Pagar");   // viaja con el siguiente evento

        try
        {
            // ... tu lógica de pago
        }
        catch (Exception ex)
        {
            await avizoro.TrackExceptionAsync(ex);
        }
    }
}

El evento aparece de inmediato en Events y, agrupado, en Issues.

¿Qué captura exactamente?

Qué Cómo ¿Activo por defecto?
Crashes fatales Excepciones administradas en todas las plataformas y excepciones Java en Android ✅
ANR Un watchdog detecta el hilo de UI bloqueado más de 5 s (AnrThreshold) ✅
Sesiones Una por cada periodo en primer plano; de ahí salen crash-free sessions y crash-free users ✅
Rendimiento Arranque en frío, carga de cada página y HTTP (con AddAvizoroHandler) ✅
Breadcrumbs Navegación, ILogger, HTTP y ciclo de vida ✅
Limpieza de PII Enmascara correos, tokens, JWT y números de tarjeta ✅
Última pantalla Captura reducida adjunta al crash ❌ opt-in
Feedback tras un crash Pregunta en el siguiente arranque ❌ opt-in
Session timeline La sesión completa, en vivo ❌ opt-in

Y del lado del dashboard tienes ciclo de vida de los issues (resolver, ignorar, asignar), detección de regresiones (un issue resuelto que vuelve a aparecer), salud por versión, y alertas por webhook, Slack, Teams, Discord o correo.

Stack traces legibles en Release

En Release pierdes archivo y número de línea, y en Android R8 ofusca los frames de Java. Avizoro trae un target de MSBuild que sube los símbolos después de compilar:

<PropertyGroup Condition="'$(Configuration)' == 'Release'">
  <AvizoroUploadSymbols>true</AvizoroUploadSymbols>
  <AvizoroServerUrl>https://avizoro.example.com</AvizoroServerUrl>
  <AvizoroProjectId>mi-app</AvizoroProjectId>
</PropertyGroup>

El token de administrador va en la variable de entorno AVIZORO_ADMIN_TOKEN, nunca en el repositorio. Si la subida falla, verás un warning, pero el build no se rompe.

¿Cuándo usar Avizoro y cuándo no?

No todo es color de rosa, y prefiero decírtelo yo 😅.

¿Cuándo te conviene Avizoro?

✅ Úsalo si:

❌ Evítalo (por ahora) si:

Checklist

Conclusión

Ya viste:

Mi recomendación personal: empieza por el demo. En dos minutos sabes si lo que muestra el dashboard es lo que necesitas, sin tocar tu app. Si te convence, conéctalo primero a tu build de staging y deja la session timeline para cuando ya tengas claro qué datos quieres guardar.

El proyecto es open source y está recién salido del horno, así que el feedback vale oro. ¿Lo probaste? ¿Te falta algo para poder usarlo en tu app? Escríbeme en X (Twitter) o abre un issue en el repositorio.

Nos leemos pronto 😊


Referencias:

Compartir

Posts relacionados

Comentarios