Logging is an often frustrating topic when it comes to Dynamics CRM. How do we manage error logging or even debug and verbose/information logging? The answer from Microsoft is simple and standard: use the ITracingService to write logging information. If your requirements are a little more complex, say you need to track your code execution on a very granular level even if no error occurs, then the ITracingService gives you very little options …
- In CRM Online, you cannot activate and review the CRM log files (it’s only available with on premise installations)
- In CRM Online, you can view the details that are related to the job execution (works for Asynchronous plugins and custom WF); For synchronous plugins, you only see the logs if an error is thrown by downloading the log file – there is no permanent record of the log
- If you enable Tracing in CRM (on premise), there is plenty of system written logs in the CRM trace that makes the log file difficult to read and to find your application specific logs, even if you are using the very nice CRM Log Viewer from Stunnware
If you want to have a custom logging module for CRM code, there are a few things to take into consideration:
- What kind of information do you need to log?
- Where do you want/need to write it?
- How and when do you need to access it?
- Do you need to control the logging level at a given time? (write error messages only, verbose, info etc.)
- Who will write the logs? Does that user have permission to write in your chosen location?
Here are a couple of options available to create logs from your CRM code. It is up to the development/architecture team to decide what they want to use that fits their need.
Custom External Logging
You can create logging utilities to write logs in locations other than CRM. For example, you could write logs in the Windows Event Viewer, in a text file or even a custom database. There are also third party tools that help you do that as well (NLog, Log4Net…) if you don’t want to start from scratch. The problem here is that if you are writing logs from plugins or workflow activities that are executed as CRM users, chances are these users won’t have permission to write in your designated location. One option is to simply give access to “Everyone” to these locations but this usually goes against most Enterprises’ IT policies. There are probably ways to set up your log writers to impersonate an admin or user with permissions but I have personally never done it.
- Tailored logging engine built for your needs and in a location of your choice
- Doesn’t work for CRM Online (or not easily)
- Requires system users to have permission to write the logs in the selected location
Custom CRM Entity
Another way to do this is to create one (or many) custom entity to capture your logs. Your CRM custom code can just create new rows in the table(s) to log the required information/error.
- Works for all CRM Installation;
- Security is simple to manage;
- Logging information is available directly in CRM;
- Custom entity can be customized to capture specific logging fields
- Depending on the amount of logging required, your log table(s) may grow really quickly. Think about having a mechanism to clear all unnecessary or outdated logs on a regular basis
- If you are writing logs in a failing transaction, the entire transaction will be rolled back including creation of logging records. Check out a workaround for this issue here.
These are just a few thoughts on logging and as always in technology, there are probably plenty of other things we can do around logging in CRM. Thought this could be helpful. Feel free to reach out if you want more details or guidance on the topic.
As developers, we are often asked to write plugins to calculate durations. A common example would be that the business wants to calculate how long an Opportunity was opened for. From a business perspective, it makes more sense to count only the business days, which is easy to do in C# (there are plenty of algorithms out there to filter out the weekends from a time interval). It makes even more sense to also exclude the holidays which are defined in the Business Closures calendar in CRM.
It is a nice feature and it integrates well with the out of the box Service module in the sense that you can configure it to prevent users from scheduling service activities on holidays.
From a development perspective however, it’s not that easy to deal with the Business Closures calendar for a few different reasons:
- There is no clear documentation how to deal with them programmatically
- In the Business Closures Calendar, we know they are stored in the related list of Calendar Rules, but the calendar rule entity doesn’t support Retrieve Multiple queries which makes the dates uneasy to retrieve
- You can only retrieve the business closures by getting the business closure calendar, the latter supports Retrieve Multiple
You may get confused with the ExpandCalendarRequest that is out there. It is used to retrieve the calendar rules from a user’s calendar. The idea being to retrieve a set of time blocks with appointment information for a specific user (these are also CalendarRules). If you try to retrieve the rules for the business closures calendar, you get a rather obscure error: “Invalid time zone code”.
After spending some time on that route, I reverted back to the original recommendation by many on forums and blogs and I managed to make it work with the code below. The idea is to retrieve the business closure calendar ID of the organization and then return its list of CalendarRule. From there, it’s easy to use to result to do what you need.
private IEnumerable<Entity> GetBusinessClosureCalendarRules(IExecutionContext context, IOrganizationService service)
// Get Organization Business Closure Calendar Id
Entity org = service.Retrieve("organization", context.OrganizationId, new Microsoft.Xrm.Sdk.Query.ColumnSet("businessclosurecalendarid"));
QueryExpression q = new QueryExpression("calendar");
q.ColumnSet = new ColumnSet(true);
q.Criteria = new FilterExpression();
q.Criteria.AddCondition(new ConditionExpression("calendarid", ConditionOperator.Equal, org["businessclosurecalendarid"].ToString()));
Entity businessClosureCalendar = service.RetrieveMultiple(q).Entities;
if (businessClosureCalendar != null)
With all this in place, you now know how to retrieve your business closures dates. My next step is to see if it is possible to restrict dates on the calendar rules that are retrieved. In the code above, the calendar is retrieved with all its business closure dates regardless of the year. If your CRM has been running a few years, it’s possible you have a lot of records in there and you may want to optimize your code by doing some fine tuning (get business closure dates from a specific date range). I’ll update this post after I get it to work if it ever happens J.
I was recently asked by a client to have a custom .NET page displayed on an entity form. Because of the content of the page (it’s a heavy, long and large!), we were all in agreement that using an IFRAME or a web resource in a Tab or Section on the main form was not going to be pretty. The best user experience is to have the page displayed when users click on a navigation link on the left menu.
Sounds easy, right? Not so fast! In our case, the custom web page displays content that is specific to the record it’s opened from. That means the page needs to know the ID of the record. That sounds easy as well, right? You are thinking simply pass the record ID as parameter in the query string… Yes, it works with an IFRAME and for a Web Resource, you can change the URL programmatically when the form loads or when a field value is changed. For a Navigation Link, not so fast! There is no supported way to change the URL programmatically!
This had me do some research to see what we could do to get around that limitation. Unfortunately, it has to be a non-supported approach.
You could attach to the On Click event of the navigation link or set the URL on load of the form. Regardless, problem is accessing the IFRAME in which your page resides. There are a few ways to do this as you can see here and here. I prefer solution 1 because you use the ID of the navigation URL (it is transported as part of the CRM solution and remains the same in all environments).
I want to say I hope Microsoft adds this functionality in a future update but with the new process driven UI forms that are coming in the Orion release, I don’t know if all this will still be relevant in a near future. We’ll see!