static void

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.

Important: the configuration is stored as blobs in wad-control-container, so if it exists delete the blob first

<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>

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>