3.2 IOrganizationService - Operaciones CRUD

Domina las operaciones Create, Retrieve, Update y Delete

Cuando algo sale mal en un plugin en producción, los logs son tu única ventana para entender qué pasó. El ITracingService es la herramienta que Dataverse te proporciona para dejar rastros de la ejecución. Saber usarlo bien marca la diferencia entre diagnósticos rápidos y horas de frustración.

Objetivos de aprendizaje

  • Implementar logging efectivo y estructurado con ITracingService
  • Aplicar patrones de trazabilidad que facilitan el debugging
  • Conocer las diferentes formas de acceder a los logs
  • Evitar errores comunes en el logging de plugins

El problema que resuelve ITracingService

Los plugins se ejecutan en servidores de Microsoft a los que no tienes acceso. No puedes abrir Visual Studio y poner un breakpoint en producción. No puedes ver la consola. No puedes añadir un archivo de log al sistema de archivos.

ITracingService es la solución. Te permite escribir mensajes durante la ejecución del plugin que luego puedes recuperar de varias formas. Si el plugin falla, todos los traces se incluyen en el mensaje de error. Si quieres analizar ejecuciones exitosas, puedes habilitar Plugin Trace Logs.


ITracingService trace = (ITracingService)
    serviceProvider.GetService(typeof(ITracingService));

trace.Trace("Este mensaje quedará registrado");
trace.Trace("Plugin ejecutándose para: {0}", context.PrimaryEntityName);

Lo primero que debes obtener

En cualquier plugin, lo primero que hago es obtener el servicio de tracing. Antes incluso del contexto. La razón es simple: si algo falla al obtener el contexto, quiero poder loguearlo. Si no tengo el trace service, no puedo dejar ningún rastro del error.


public void Execute(IServiceProvider serviceProvider)
{
    // SIEMPRE primero
    ITracingService trace = (ITracingService)
        serviceProvider.GetService(typeof(ITracingService));
    
    trace.Trace("Plugin iniciado");
    
    try
    {
        IPluginExecutionContext context = (IPluginExecutionContext)
            serviceProvider.GetService(typeof(IPluginExecutionContext));
        
        trace.Trace($"Contexto obtenido: {context.MessageName} en {context.PrimaryEntityName}");
        
        // Resto de la lógica...
    }
    catch (Exception ex)
    {
        trace.Trace($"ERROR: {ex.Message}");
        trace.Trace(ex.StackTrace);
        throw;
    }
}

Estructura tus logs para que sean útiles

He visto logs de plugins que dicen cosas como "aquí 1", "aquí 2", "entró al if". Esto no ayuda cuando tienes que diagnosticar un problema en producción tres meses después.

Los buenos logs cuentan una historia. Tienen estructura, contexto, y datos relevantes.

Patrón de logging estructurado


public class ValidateAccountPlugin : IPlugin
{
    private const string PluginName = nameof(ValidateAccountPlugin);
    
    public void Execute(IServiceProvider serviceProvider)
    {
        ITracingService trace = (ITracingService)
            serviceProvider.GetService(typeof(ITracingService));
        
        trace.Trace($"=== {PluginName} INICIO ===");
        
        try
        {
            IPluginExecutionContext context = (IPluginExecutionContext)
                serviceProvider.GetService(typeof(IPluginExecutionContext));
            
            // Log del contexto
            trace.Trace($"Mensaje: {context.MessageName}");
            trace.Trace($"Entidad: {context.PrimaryEntityName}");
            trace.Trace($"Stage: {context.Stage}");
            trace.Trace($"Depth: {context.Depth}");
            trace.Trace($"Usuario: {context.InitiatingUserId}");
            
            // Log de decisiones de negocio
            Entity target = (Entity)context.InputParameters["Target"];
            string nombre = target.GetAttributeValue("name");
            trace.Trace($"Procesando cuenta: {nombre}");
            
            if (string.IsNullOrEmpty(nombre))
            {
                trace.Trace("DECISIÓN: Nombre vacío, rechazando operación");
                throw new InvalidPluginExecutionException("El nombre es obligatorio");
            }
            
            trace.Trace("DECISIÓN: Validación pasada");
            trace.Trace($"=== {PluginName} FIN ===");
        }
        catch (InvalidPluginExecutionException)
        {
            throw; // Re-lanzar sin modificar
        }
        catch (Exception ex)
        {
            trace.Trace($"ERROR INESPERADO: {ex.GetType().Name}");
            trace.Trace($"Mensaje: {ex.Message}");
            trace.Trace($"Stack: {ex.StackTrace}");
            throw new InvalidPluginExecutionException(
                $"Error en {PluginName}: {ex.Message}", ex);
        }
    }
}

Qué loguear y qué no

Siempre loguea:

  • Inicio y fin del plugin (te permite ver ejecuciones completas vs parciales)
  • Información del contexto (mensaje, entidad, usuario)
  • Decisiones de negocio importantes ("validación pasada", "registro denegado porque...")
  • Valores clave que usas en la lógica
  • Errores con mensaje y stack trace

Evita loguear:

  • Datos sensibles (contraseñas, tokens, datos personales de clientes)
  • Cada línea de código ejecutada (demasiado ruido)
  • El contenido completo de entidades grandes (usa campos específicos)

Dónde aparecen los traces

Los mensajes que escribes con ITracingService pueden verse de varias formas dependiendo del escenario:

En errores de plugin

Cuando un plugin falla y lanza InvalidPluginExecutionException, el usuario ve un cuadro de error. Si hace clic en "Download Log File", obtiene un archivo con todos los traces acumulados durante la ejecución.

Este es el escenario más común. Los traces te permiten reconstruir qué hizo el plugin hasta el punto del fallo.

Plugin Trace Logs en el sistema

Dataverse puede guardar los traces de todas las ejecuciones, no solo las fallidas. Esto se configura en Settings → Administration → System Settings → Customization tab → "Enable logging to plug-in trace log".

Las opciones son:

  • Off: Solo errores muestran traces (comportamiento por defecto)
  • Exception: Guarda traces de plugins que fallan
  • All: Guarda traces de todas las ejecuciones

Con "All" activado, puedes ir a Settings → Plugin Trace Logs y ver el historial de ejecuciones con sus traces. Esto es muy útil para debugging, pero consume espacio en la base de datos. Úsalo temporalmente durante troubleshooting, no lo dejes activo permanentemente en producción.

Plugin Profiler

El Plugin Profiler del Plugin Registration Tool te permite capturar una ejecución en vivo y luego reproducirla localmente en Visual Studio con debugging. Los traces aparecen en la ventana de output durante la reproducción.

Este es el método más potente para debugging porque puedes poner breakpoints y examinar variables. Lo cubriremos en detalle en el módulo de debugging.


Creando un helper de logging reutilizable

En proyectos con muchos plugins, centralizo la lógica de logging en una clase helper. Esto garantiza consistencia y facilita cambios globales:


public static class TraceHelper
{
    public static void LogContext(ITracingService trace, IPluginExecutionContext context)
    {
        trace.Trace("--- Contexto de ejecución ---");
        trace.Trace($"Mensaje: {context.MessageName}");
        trace.Trace($"Entidad: {context.PrimaryEntityName}");
        trace.Trace($"Id: {context.PrimaryEntityId}");
        trace.Trace($"Stage: {context.Stage}");
        trace.Trace($"Depth: {context.Depth}");
        trace.Trace($"Usuario iniciador: {context.InitiatingUserId}");
    }
    
    public static void LogEntity(ITracingService trace, Entity entity, string label)
    {
        trace.Trace($"--- {label} ({entity.LogicalName}) ---");
        trace.Trace($"Id: {entity.Id}");
        trace.Trace($"Atributos: {entity.Attributes.Count}");
        
        foreach (var attr in entity.Attributes)
        {
            string valor = FormatValue(attr.Value);
            trace.Trace($"  {attr.Key}: {valor}");
        }
    }
    
    private static string FormatValue(object value)
    {
        return value switch
        {
            null => "(null)",
            EntityReference er => $"Ref({er.LogicalName}/{er.Id})",
            OptionSetValue osv => $"Option({osv.Value})",
            Money m => $"${m.Value}",
            DateTime dt => dt.ToString("yyyy-MM-dd HH:mm:ss"),
            _ => value.ToString()
        };
    }
}

Usando este helper, el código de tus plugins se simplifica:


public void Execute(IServiceProvider serviceProvider)
{
    var trace = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
    var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
    
    TraceHelper.LogContext(trace, context);
    
    Entity target = (Entity)context.InputParameters["Target"];
    TraceHelper.LogEntity(trace, target, "Target");
    
    // Tu lógica aquí...
}

Errores comunes en logging

A lo largo de los años he visto varios antipatrones que deberías evitar:

No capturar el trace service primero. Si obtienes el contexto antes del trace service y hay un error, no tendrás forma de loguearlo.

Olvidar el stack trace en excepciones. El mensaje de error solo te dice qué pasó, el stack trace te dice dónde. Siempre loguea ambos.

Asumir que el trace siempre existe. En teoría siempre debería existir, pero es buena práctica verificar que no sea null antes de usarlo.

Generar excepciones en el logging. Si tu código de logging puede fallar (por ejemplo, acceder a un campo que no existe), el error de logging ocultará el error real. Usa try-catch alrededor del código de logging si es necesario.


Puntos clave

  • Obtén ITracingService antes que cualquier otro servicio
  • Estructura tus logs con inicio, contexto, decisiones y fin
  • Siempre loguea errores con mensaje y stack trace
  • Los traces aparecen en errores, Plugin Trace Logs, y Plugin Profiler
  • Habilita Plugin Trace Logs temporalmente para troubleshooting, no permanentemente
  • Considera crear un helper de logging para consistencia entre plugins

Para profundizar

Inicia sesión e inscríbete para guardar tu progreso.
En este curso
¿Te ha resultado útil?