Azure Diagnostics
See MSDN, ScottGu sdk 2.0. NB SDK 2.0 makes this easier.
Instrument the code
Write lots of Trace.TraceEror. For instance, in MVC's Views/Shared/Error.cshtml (destination of [HandleError])
@model System.Web.Mvc.HandleErrorInfo
@{
ViewBag.Title = "Error";
}
<hgroup class="title">
<h1 class="error">Error.</h1>
<h2 class="error">An error occurred while processing your request.</h2>
</hgroup>
@{
var sb = new System.Text.StringBuilder();
sb.AppendLine(Request.RawUrl + " " + Request.UserAgent);
sb.AppendLine(Model.ControllerName + "." + Model.ActionName);
sb.AppendLine(Model.Exception.ToString());
var s = sb.ToString();
System.Diagnostics.Trace.TraceError(s);
}
ServiceDefinition.csdef
Must have Imports/Import[@moduleName="Diagnostics"]
<?xml version="1.0" encoding="utf-8" >
<ServiceDefinition name="Cloud" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition" schemaVersion="2012-10.1.8">
<WebRole name="MartinWeb" vmsize="Small">
<Sites>
<Site name="Web">
<Bindings>
<Binding name="Endpoint1" endpointName="Endpoint1" />
</Bindings>
</Site>
</Sites>
<Endpoints>
<InputEndpoint name="Endpoint1" protocol="http" port="80" />
</Endpoints>
<Imports>
<Import moduleName="Diagnostics" />
</Imports>
</WebRole>
</ServiceDefinition>
ServiceConfiguration.Cloud.csfg
Link Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString to your storage account
<?xml version="1.0" encoding="utf-8" >
<ServiceConfiguration serviceName="Cloud" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration" osFamily="3" osVersion="*" schemaVersion="2012-10.1.8">
<Role name="MartinWeb">
<Instances count="1" />
<ConfigurationSettings>
<Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" value="DefaultEndpointsProtocol=https;AccountName=mystorage;AccountKey=zzz" />
</ConfigurationSettings>
<Certificates>
</Certificates>
</Role>
</ServiceConfiguration>
Configure
Use WebRole.OnStart or just add a diagnostics.wadcfg to the web project with Copy to Output Directory: Copy always
. In SDK 2.0 the Role's Configuration tab has an "Enable Diagnostics" checkbox which writes this file out.
<DiagnosticMonitorConfiguration xmlns="http://schemas.microsoft.com/ServiceHosting/2010/10/DiagnosticsConfiguration"
configurationChangePollInterval="PT1M"
overallQuotaInMB="4096">
<Logs bufferQuotaInMB="1024"
scheduledTransferLogLevelFilter="Verbose"
scheduledTransferPeriod="PT1M" />
<WindowsEventLog bufferQuotaInMB="512"
scheduledTransferLogLevelFilter="Warning"
scheduledTransferPeriod="PT1M">
<DataSource name="System!*" />
<DataSource name="Application!*" />
</WindowsEventLog>
</DiagnosticMonitorConfiguration>
- The useful diagnostics are:
- Logs (tracing, writes to Azure Table Storage WADLogsTable)
- WindowsEventLog (event log, writes to WADWindowsEventLogsTable)
- Minimum lag is 1 minute (
P1M
) - Levels are Verbose (5), Information (4), Warning (3), Error (2), Critical (1) - use with
Trace.TraceError("message")
. - You can set a maximum MB, after which old rows are purged.
Add the trace listener
If you don't use the emulator in Development, and/or use websites for TST/ACC instead of WebRoles, you can just use Web.Release.config with xdt. NB: check the version number if the SDK has changed!
<system.diagnostics xdt:Transform="Insert">
<trace>
<listeners>
<add
type="Microsoft.WindowsAzure.Diagnostics.DiagnosticMonitorTraceListener, Microsoft.WindowsAzure.Diagnostics, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
name="AzureDiagnostics">
</add>
</listeners>
</trace>
</system.diagnostics>
Reading the logs
You can read the WADLogsTable table in VS2012 Server Explorer or third party tools- but I haven't seen a good and clear report page yet. A developer report page is easy to do.
WadLog Table Entity
using System;
using Microsoft.WindowsAzure.Storage.Table;
namespace MartinWeb.Cloud
{
public class WadLog : TableEntity
{
public WadLog()
{
EventTickCount = DateTime.UtcNow.Ticks;
PartitionKey = "0" + EventTickCount;
//DeploymentId(Guid)___Role___RoleInstance1___0000000001652031489___WADLogsLocalQuery
RowKey = string.Format("{0}___Role___RoleInstance1___{1:20}___WADLogsLocalQuery", Guid.NewGuid(), EventTickCount);
}
public long EventTickCount { get; set; }
public string DeploymentId { get; set; }
public string Role { get; set; }
public string RoleInstance { get; set; }
public int Level { get; set; }
public int EventId { get; set; }
public int Pid { get; set; }
public int Tid { get; set; }
public string Message { get; set; }
public DateTime EventDateTime
{
get { return new DateTime(EventTickCount); }
}
}
}
WadLogReader
using System;
using System.Collections.Generic;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Table;
namespace MartinWeb.Cloud
{
public class WadLogReader
{
private readonly string _connectionString;
public WadLogReader()
{
_connectionString =
CloudConfigurationManager.GetSetting("Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString");
}
private CloudTable FindTable()
{
var storageAccount = CloudStorageAccount.Parse(_connectionString);
var tableClient = storageAccount.CreateCloudTableClient();
var table = tableClient.GetTableReference("WADLogsTable");
table.CreateIfNotExists();
return table;
}
public IEnumerable<WadLog> Query(DateTime start, DateTime? end = null)
{
var partitionStart = start.Ticks.ToString("D19");
var table = FindTable();
var filter =
TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.GreaterThanOrEqual, partitionStart);
if (end.HasValue)
{
string partitionEnd = end.Value.Ticks.ToString("D19");
var to =
TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.LessThanOrEqual, partitionEnd);
filter = TableQuery.CombineFilters(filter, TableOperators.And, to);
}
var query = new TableQuery<WadLog>()
.Where(filter);
return table.ExecuteQuery(query);
}
}
}
Controller Action
public ActionResult Index()
{
var logs = new WadLogReader();
var now = DateTime.UtcNow;
var result = logs.Query(now.Date.AddDays(-5));
return View(result);
}
View
@model IEnumerable<MartinWeb.Cloud.WadLog>
@{
ViewBag.Title = "Index";
}
<h2>Trace logs</h2>
<table>
<thead>
<tr>
<th>Occurred</th>
<th>Level</th>
<th>Message</th>
</tr>
</thead>
@foreach (var log in Model)
{
<tr>
<td>@log.EventDateTime.ToString("dd/MM/yyyy HH:mm")</td>
<td>@log.Level</td>
<td><pre style="max-width:400px;white-space: pre-wrap">@log.Message</pre></td>
</tr>
}
</table>