This is the first part of the Sitefinity series. For your convenience you can find other parts using the links below (or by guessing the address):
Part 1 — Capturing logs
Part 2 — Dependency Injection
Part 3 — Changing connection string
Part 4 — Turning on Redis
Let’s assume that we have properly configured Sitefinity instance to work in Azure (which includes using Azure SQL database and Azure Redis instance). Now there is a question: how do we capture logs and send them to Azure Storage?
By default Sitefinity logs everything to files in App_Data\Sitefinity\Logs
directory. We can easily use Azure logging facilities (which can be enabled in Azure Portal in section Diagnostic logs) but we need to log using Trace
class. In order to do that, we need to implement custom listener and configure Sitefinity to use it. Let’s go.
Implementation
First, in Sitefinity we need to register for bootstrapping event when actual logging configuration is enabled. In Global.asax.cs
add the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
using Telerik.Sitefinity.Abstractions; using Telerik.Sitefinity.Data; namespace Cms { using System; public class Global : System.Web.HttpApplication { protected void Application_Start(object sender, EventArgs e) { ObjectFactory.Initialized += ConfigInitialize; } private void ConfigInitialize(object s, ExecutedEventArgs args) { if (args.CommandName == "ConfigureLogging") { LoggingConfig.ReplaceBuiltInTraceListenersWithCustom(args); } } } } |
Sitefinity uses logging classes from Microsoft Enterprise Library (Entlib) to manage logs. We can extract configuration in runtime and replace it using custom listeners:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
using System.Linq; using Telerik.Microsoft.Practices.EnterpriseLibrary.Common.Configuration; using Telerik.Microsoft.Practices.EnterpriseLibrary.Logging.Configuration; using Telerik.Sitefinity.Data; using CustomTraceListenerData = Cms.Logging.CustomTraceListenerData; namespace Cms { public static class LoggingConfig { public static void ReplaceBuiltInTraceListenersWithCustom(ExecutedEventArgs args) { var traceListeners = GetSitefinityTraceListeners(args); var listenerNames = traceListeners.Select(t => t.Name).ToArray(); foreach (var name in listenerNames) { traceListeners.Remove(name); var listenerAdapter = new CustomTraceListenerData(name); traceListeners.Add(listenerAdapter); } } private static TraceListenerDataCollection GetSitefinityTraceListeners(ExecutedEventArgs args) { var builder = args.Data as ConfigurationSourceBuilder; return ((LoggingSettings)builder.Get("loggingConfiguration")).TraceListeners; } } } |
We extract listeners from Sitefinity internals. It is named collection describing how to create concrete loggers. There are loggers for errors (which by default log to Error.log file), debug, trace, etc. Since we would like to redirect all logs to Azure Storage, we need to remove all existing configurations and inject ours. To do that, we iterate over all loggers, remove them one by one and create custom loggers with same names.
Actual logger looks as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
using System; using System.Diagnostics; using System.Linq.Expressions; using Telerik.Microsoft.Practices.EnterpriseLibrary.Logging.Configuration; namespace Cms.Logging { public class CustomTraceListenerData : TraceListenerData { public CustomTraceListenerData(string name) : base( name, typeof (CustomTraceListener), TraceOptions.Callstack | TraceOptions.DateTime | TraceOptions.ProcessId | TraceOptions.ThreadId | TraceOptions.Timestamp | TraceOptions.LogicalOperationStack, SourceLevels.All) { ListenerDataType = typeof (CustomTraceListener); } protected override Expression<Func<TraceListener>> GetCreationExpression() { return () => new CustomTraceListener(); } } } using System.Diagnostics; namespace Cms.Logging { public class CustomTraceListener : TraceListener { public override void Write(string message) { Trace.Write(message); } public override void WriteLine(string message) { Trace.WriteLine(message); } } } |
In configuration class we want to log everything on all levels. Actual logger only redirects messages to Trace
class.
Now, we need to configure logging to Azure Storage. First, we enable it in Azure Portal. Next, we need to add trace listener for Azure. We add the following to web.config
:
1 2 3 4 5 6 7 8 9 10 |
< system.diagnostics> < trace> < listeners> < add type="Microsoft.WindowsAzure.Diagnostics.DiagnosticMonitorTraceListener, Microsoft.WindowsAzure.Diagnostics,Version=2.8.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" name="AzureDiagnostics"> < filter type="" /> < /add> < add name="FileDiagnostics" type="System.Diagnostics.TextWriterTraceListener" initializeData="App_Data/Sitefinity/Logs/Log.txt" /> < /listeners> < /trace> < /system.diagnostics> |
We add to listeners: one for Azure and another to log to drive. The latter is useful for running application locally, it redirects all logs (errors, SQL changes, etc.) to one file.
You might also need to add the following to web.config
1 2 3 4 5 |
< compilation debug="true" targetFramework="4.7" numRecompilesBeforeAppRestart="2000"> < assemblies> < add assembly="System.Runtime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" /> < /assemblies> < /compilation> |
Now you can also configure Log Analytics and have Sitefinity logs in OMS.
Tested with Sitefinity version: 10.0.6411.0.