To support your wish list, I'd create these Entities:
PluginLog
- Contains what ever information you'd like to retain for plugin calls that completed successfully
- Is related to a Plugin Exception Class. This way you could look up what happened before
an exception occurred
PluginException
- Contains any special information regarding the exception (user, context, stack trace)
Now let's go through your wish list:
- Retention of last n lines of log in memory.
- Not sure if you want to log the particular plugin class, or all plugins classes defined in the DLL, I'll assume a particular plugin class:
- Create a static ConcurrentQueue for the plugin
- A single function call to add a line to the log.
- Create a single function that creates a PluginLog entity in memory (without creating it in the CRM database) and adds it to the queue.
- If it's length > n, dequeue.
- A single function call to cause an error to be recorded.
- Again, this is something that you'll need to create. Basically I'd create a PLuginException Entity in CRM, then dequeue all the items off of the Queue, populating the Plugin Exception Id, and saving it to CRM
- When there's an error, email to me saying there was a problem. This contains summary info.
- As long as the App Domain context of the executing plugin has the rights needed (not sure if it does in CRM Online) this should be trivial.
- The email contains a link to a web page showing me full error details. Stack trace, error message, date, time, user etc, last n lines of log.
- You can create a link to the PluginException Entity Created, and include it in the e-mail along with all other pertinent information.
- It'd be nice if there was also a web page somewhere showing me all errors, with filtering, sorting and so on.
- Advanced Find to the rescue
Edit
To help get you started, this is what I currently use to retrieve all the information from the plugin context, and convert it into text that gets inserted into an Exception:
#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