What if your code is brilliant, your website gets thousands or millions of visits every week, yet you are still very interested in the kinds of warning and errors occurring on the site? What if you manage the hosting and support of hundreds of websites, and the sheer volume of individual error emails is too awkward to manage, let alone the daunting task of manually trawling through every site’s Event Log to review the warnings?
Every time something happens in Kentico, it logs an event. Many of these are classified as “Information” or “Warning”, and you’ll only see them if you go looking in the event log. The next level up is “Error”, which triggers a notification email every time. This is great, because it logs an Error even if it’s an unhandled exception (bad code that crashes the site!).
Thankfully, in recent versions of Kentico, a 404 response (page not found) from your website is no longer always considered an Error (just a Warning), so you don’t get an email every time a dodgy robot tries to hit an incorrect URL on your site. Obviously we should aim to have no errors at all in our Event Log, but there are inevitably a number of errors that will occur no matter how good your code is, especially on large/high-traffic sites. For example, a penetration test (or hacking attempt) is likely to trigger a large number of errors to do with invalid URLs or unsafe form submissions, even if your site is successfully defending against the attacks.
By default, we get no notification of warnings, and an individual notification for every error.
To solve this on some of our high-volume sites, I put together a Kentico scheduled task that generates what I call a Kentico Event Log Digest, and emails it once a week. The output looks something like this:
Top 5 Errors: No errors. Good job. :) Number of errors: 0 Top 5 Warnings: 2 instances of ENDAPP Message: HostingEnvironment initiated shutdown HostingEnvironment caused shutdown Shutdown stack: at System.Environment.GetStackTrace(Exception e, Boolean needFileInfo) at System.Environment.get_StackTrace() at System.Web.Hosting.HostingEnvironment.InitiateShutdownInternal() at System.Web.Hosting.PipelineRuntime.StopProcessing() Call stack: at CMS.EventLog.EventLogProvider.LogApplicationEnd() at CMS.EventLog.EventLogHandlers.LogApplicationEnd(Object sender, EventArgs e) at CMS.Base.AbstractHandler.CallEventHandler[TArgs](EventHandler`1 h, TArgs e) at CMS.Base.AbstractHandler.Raise[TArgs](String partName, List`1 list, TArgs e, Boolean important) at CMS.Base.SimpleHandler`2.RaiseExecute(TArgs e) at CMS.Base.SimpleHandler`2.RaiseExecute(TArgs e) at CMS.Base.SimpleHandler`2.StartEvent(TArgs e) at System.RuntimeMethodHandle.InvokeMethod(Object target, Object arguments, Signature sig, Boolean constructor) at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object parameters, Object arguments) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object parameters, CultureInfo culture) at System.Reflection.MethodBase.Invoke(Object obj, Object parameters) at System.Web.HttpApplication.InvokeMethodWithAssert(MethodInfo method, Int32 paramCount, Object eventSource, EventArgs eventArgs) at System.Web.HttpApplication.ProcessSpecialRequest(HttpContext context, MethodInfo method, Int32 paramCount, Object eventSource, EventArgs eventArgs, HttpSessionState session) at System.Web.HttpApplicationFactory.Dispose() at System.Web.HttpRuntime.Dispose() at System.Web.HttpRuntime.ReleaseResourcesAndUnloadAppDomain(Object state) at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() at System.Threading.ThreadPoolWorkQueue.Dispatch() Number of warnings: 2
That’s obviously a pretty good result - any developer or support tech would be happy with that. But the idea is occasionally you would see something more like this:
Top 5 Warnings: 45 instances of PAGENOTFOUND
In the above example, the developer or support tech would not have received any emails about these warnings, but this should prompt them to look into why the number of 404s on the site is climbing. Maybe it’s just a misbehaving crawler, but maybe it’s something that needs to be fixed, such as a typo in a hyperlink URL.
What is it doing?
It’s written as a Scheduled Task in Kentico, which means writing a new C# class (custom code) that implements the ITask interface, then configuring it in the Scheduled Tasks app inside Kentico’s admin interface.
The detail of writing scheduled tasks is outside the scope of this post, but Kentico has good documentation on how to do it here: Scheduling Custom Tasks
My task does the following:
- Get all the event log “Error” entries from the past week using the EventLogProvider.GetAllEvents() method
- Group them by event Code and Description - I use GetEnumerable() and then Linq makes the rest easy
- Sort by descending quantity (the number of occurrences of each type of error)
- Take the top 5 items (ignore the long tail)
It then does the same with “Warning” type logs, and puts the two lists with some summary statistics into an HTML email template.
Why is it better?
Individual exception emails can be difficult for many developers to understand, let alone a hosting or tech support person! The digest allows someone, at a glance, to see whether there are a large number of errors or warnings occurring, and decide whether to escalate the issue. Our tech support staff will be notified immediately via other monitoring tools if a situation is so critical as to have brought a site offline, so actioning individual emails is not so critical from an ops/hosting position.
This is not ideal for every scenario, of course. For your own personal site, it’s still a good idea to have individual errors sent to you, so you can quickly respond to an error.
Can I have it?
At the moment, it’s just a class in a couple of our projects, and isn’t packaged up for the marketplace. For that, I just need to configure it to use Custom Settings, and of course improve my documentation! If there’s enough interest (let me know in the comments) I will definitely go ahead and do that. For now, if you’re desperate, just get in touch with me and I’ll be happy to share the C# code with you.
Want to know more?
We have a whole team of experts who would love to talk to you.Get in touch
Want more? Here are some other blog posts you might be interested in.