6

CRM 2011 内部部署。

我有一个用 C# 编写的插件。它可能会抛出异常或以其他方式在生产中表现不佳。

发生这种情况时,我想捕获有关状态和最近代码执行的信息,以帮助我分析问题。

理想情况下,我想要以下内容:

  1. 如果代码决定我应该知道某个问题,那么我希望它能够尽快告诉我存在问题,而无需我查看是否发生了问题。

  2. 我希望我可以轻松访问有关问题的信息。我不想 RDP 到另一台机器并搜索文件。

  3. 我不希望日志记录对性能产生太大影响。

我想我正在寻找这样的东西:

  • 在内存中保留最后 n 行日志。
  • 向日志中添加一行的单个函数调用。
  • 导致记录错误的单个函数调用。
  • 当出现错误时,给我发电子邮件说有问题。这包含摘要信息。
  • 该电子邮件包含一个指向网页的链接,该链接向我显示了完整的错误详细信息。堆栈跟踪、错误消息、日期、时间、用户等,最后 n 行日志。
  • 如果某个地方也有一个网页向我显示所有错误,包括过滤、排序等,那就太好了。

我对 CRM 还很陌生,但我以前开发过这样的系统。由于 CRM 已经存在多年,我希望它可以使用它。

4

3 回答 3

6

为了支持您的愿望清单,我将创建这些实体:

插件日志

  • 包含您希望为成功完成的插件调用保留的任何信息
  • 与插件异常类有关。这样您就可以查看发生异常之前发生的情况

插件异常

  • 包含有关异常的任何特殊信息(用户、上下文、堆栈跟踪)

现在让我们来看看你的愿望清单:

  • 在内存中保留最后 n 行日志。
    • 不确定是否要记录特定的插件类,或 DLL 中定义的所有插件类,我将假设一个特定的插件类:
    • 为插件创建一个静态 ConcurrentQueue
  • 向日志中添加一行的单个函数调用。
    • 创建一个函数,在内存中创建一个 PluginLog 实体(而不在 CRM 数据库中创建它)并将其添加到队列中。
    • 如果它的长度> n,则出队。
  • 导致记录错误的单个函数调用。
    • 同样,这是您需要创建的东西。基本上我会在 CRM 中创建一个 PLuginException 实体,然后将所有项目从队列中取出,填充插件异常 ID,并将其保存到 CRM
  • 当出现错误时,给我发电子邮件说有问题。这包含摘要信息。
    • 只要执行插件的应用程序域上下文具有所需的权限(不确定它是否在 CRM Online 中),这应该是微不足道的。
  • 该电子邮件包含一个指向网页的链接,该链接向我显示了完整的错误详细信息。堆栈跟踪、错误消息、日期、时间、用户等,最后 n 行日志。
    • 您可以创建指向已创建 PluginException 实体的链接,并将其与所有其他相关信息一起包含在电子邮件中。
  • 如果某个地方也有一个网页向我显示所有错误,包括过滤、排序等,那就太好了。
    • 高级查找救援

编辑

为了帮助您入门,这是我目前用来从插件上下文中检索所有信息,并将其转换为插入到异常中的文本的方法:

    #region GetPluginInfo

    private Exception GetPluginExecutionInfoForLog(IServiceProvider serviceProvider, Exception ex)
    {
        if(ex.GetType() == typeof(InvalidPluginExecutionException)){ return ex; }

        try
        {
            var context = serviceProvider.GetContext();

            ex = new InvalidPluginExecutionException(
                String.Format("Error During Plugin Execution: {0}**** Context Values ****{0}{1}", 
                    Environment.NewLine, GetPluginExecutionInfo(context)), ex);
        }
        catch (Exception childEx)
        {
            OnError(childEx);
        }
        return ex;
    }

    protected String GetPluginExecutionInfo(IPluginExecutionContext context)
    {
        var lines = new List<String>();
        var target = GetTarget<Entity>(context);

        lines.Add("MessageName: " + context.MessageName);
        lines.Add("PrimaryEntityName: " + context.PrimaryEntityName);
        lines.Add("PrimaryEntityId: " + context.PrimaryEntityId);
        lines.Add("BusinessUnitId: " + context.BusinessUnitId);
        lines.Add("CorrelationId: " + context.CorrelationId);
        lines.Add("Depth: " + context.Depth);
        lines.Add("Has Parent Context: " + (context.ParentContext != null));
        lines.Add("InitiatingUserId: " + context.InitiatingUserId);
        AddParameters(lines, context.InputParameters, "Input Parameters");
        lines.Add("IsInTransaction: " + context.IsInTransaction);
        lines.Add("IsolationMode: " + context.IsolationMode);
        lines.Add("Mode: " + context.Mode);
        lines.Add("OperationCreatedOn: " + context.OperationCreatedOn);
        lines.Add("OperationId: " + context.OperationId);
        lines.Add("Organization: " + context.OrganizationName + "(" + context.OrganizationId + ")");
        AddParameters(lines, context.OutputParameters, "Output Parameters");
        AddEntityReference(lines, context.OwningExtension, "OwningExtension");
        AddEntityImages(lines, context.PostEntityImages, "Post Entity Images");
        AddEntityImages(lines, context.PreEntityImages, "Pre Entity Images");
        lines.Add("SecondaryEntityName: " + context.SecondaryEntityName);
        AddParameters(lines, context.SharedVariables, "Shared Variables");
        lines.Add("Stage: " + context.Stage);
        lines.Add("UserId: " + context.UserId);

        if (target == null || target.Attributes.Count == 0)
        {
            lines.Add("Target: Empty ");
        }
        else
        {
            lines.Add("* Target " + target.ToEntityReference().GetNameId() + " *");
            foreach (var att in target.Attributes)
            {
                lines.Add("    Entity[" + att.Key + "]: " + GetAttributeValue(att.Value));
            }
        }

        lines.Add("* App Config Values *");
        foreach (var key in ConfigurationManager.AppSettings.AllKeys)
        {
            lines.Add("    [" + key + "]: " + ConfigurationManager.AppSettings[key]);
        }

        return String.Join(Environment.NewLine, lines);
    }

    private static string GetAttributeValue(object value)
    {
        if(value == null){
            return "Null";
        }
        var type = value.GetType();
        if (type == typeof(OptionSetValue))
        {
            return ((OptionSetValue)value).Value.ToString();
        }
        else if (type == typeof(EntityReference))
        {
            return ((EntityReference)value).GetNameId();
        }
        else
        {
            return value.ToString();
        }
    }

    private static void AddEntityReference(List<string> nameValuePairs, EntityReference entity, string name)
    {
        if (entity != null)
        {
            nameValuePairs.Add(name + ": " + entity.GetNameId());
        }
    }

    private static void AddEntityImages(List<string> nameValuePairs, EntityImageCollection images, string name)
    {
        if (images != null && images.Count > 0)
        {
            nameValuePairs.Add("** " + name + " **");
            foreach (var image in images)
            {
                if (image.Value == null || image.Value.Attributes.Count == 0)
                {
                    nameValuePairs.Add("    Image[" + image.Key + "] " + image.Value.ToEntityReference().GetNameId() + ": Empty");
                }
                else
                {
                    nameValuePairs.Add("*   Image[" + image.Key + "] " + image.Value.ToEntityReference().GetNameId() + "   *");
                    foreach (var att in image.Value.Attributes)
                    {
                        nameValuePairs.Add("        Entity[" + att.Key + "]: " + GetAttributeValue(att.Value));
                    }
                }
            }
        }
        else
        {
            nameValuePairs.Add(name + ": Empty");
        }
    }

    private static void AddParameters(List<string> nameValuePairs, ParameterCollection parameters, string name)
    {
        if (parameters != null && parameters.Count > 0)
        {
            nameValuePairs.Add("* " + name + " *");
            foreach (var param in parameters)
            {
                nameValuePairs.Add("    Param[" + param.Key + "]: " + param.Value);
            }
        }
        else
        {
            nameValuePairs.Add(name + ": Empty");
        }
    }

    #endregion // GetPluginInfo
于 2013-09-27T13:11:09.420 回答
0

我在http://mscrmtech.com/mscrm-2011-useful-itracingservice-addiion-when-creating-plugin-assemblies/找到了一种非常快速的方法

它记录在这里(几乎没有):http: //msdn.microsoft.com/en-us/library/microsoft.xrm.sdk.itracingservice.aspx

所以我可以

    public void Execute(IServiceProvider serviceProvider)
    {
        ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
        if (tracingService == null)
            throw new InvalidPluginExecutionException("Failed to retrieve the tracing service.");
        tracingService.Trace("CRMSTClasses.Main_Code.Execute - testing tracing");

        throw new InvalidPluginExecutionException("Test exception");

然后我可以从错误对话框中下载的日志文件包括:

[CRMSTClasses: CRMSTClasses.Main_Code.Receiver]
[cb42fcb0-0717-e311-9097-0050569274a2: CRMSTClasses.Main_Code.Receiver: Create of incident]
CRMSTClasses.Main_Code.Execute - testing tracing
于 2013-09-30T10:38:05.187 回答
0

在 Dynamics 的最新版本中,插件跟踪日志仅通过用户调用预构建的跟踪服务来捕获大量此类信息。它没有按要求启用主动通知,但它具有许多其他不错的功能。当你准备好升级时(如果有的话),我建议你看看它。

于 2018-07-13T14:10:51.637 回答