Gaurav Mantri's Personal Blog.

Windows Azure SDK 2.0 For .Net – Taking A Second Look At Windows Azure Diagnostics

A few days ago I wrote a post about newly released Windows Azure SDK 2.0 for .Net. You can read that post here: https://gauravmantri.com/2013/04/30/introducing-windows-azure-sdk-2-0-for-net/. In that post I briefly talked about the improvements in the latest SDK with regards to Windows Azure Diagnostics (WAD). In this post, we’ll talk about WAD in more details. We’ll talk about improvements in SDK as well as changes in the Service Management API to facilitate these changes.

So let’s start.

Changes to Windows Azure Diagnostics

First let’s talk about changes done to Windows Azure Diagnostics.

Windows Azure Diagnostics is now an Extension

Windows Azure Diagnostics (WAD) module is now an extension to your cloud service. I wrote a blog post on extensions which you can read here: https://gauravmantri.com/2013/05/06/windows-azure-cloud-services-extensions-and-service-management-api-fun-with-remote-desktop/. What that means is that you can enable / disable diagnostics functionality on the fly. If you’ve been working with cloud services, I think you’ll greatly appreciate this enhancement. In the cloud services which used previous version (say 1.8) of the SDK, enabling diagnostics was a pain. One would need to remember a lot of things to get the diagnostics working and if one forgets to do those things, the only option to make diagnostics working was to go back to your code and make the changes and redeploy your application. NOT ANYMORE! Even if you forget to enable diagnostics when you first deploy your cloud service, you can enable the diagnostics on the fly using the extensions mechanism. Furthermore, you don’t really need to write any code in your role’s OnStart() method to enable the diagnostics.

We’ll see an example of how you can do this later in this post.

Configuring Windows Azure Diagnostics has never been easier

With the latest SDK, it’s extremely easy to configure Windows Azure Diagnostics (WAD). Visual Studio provides you with an easy to use user interface to configure the diagnostics. To configure WAD, open up your cloud service configuration by double clicking on the role’s name and go to “Configuration” tab. Under the “Diagnostics” section, you’ll see various configuration options as shown in the screenshot below:

clip_image002

If you’ve been using previous versions of SDK, you’re used to seeing this kind of screen:

clip_image004

As you can see from the screenshots, not only you get to choose whether the diagnostics should be enabled or disabled you also get a simple to use user interface to configure the diagnostics. Let’s talk about these options.

Errors only

When you choose “Errors Only” option, WAD extension transfers only the log entries with log level type “error” and “critical” to storage. A few things to keep in mind:

  • This option will only enable following log types – trace and event logs.
  • This option will not do anything with the performance counters. So no performance counters data will be transferred to storage.
  • As said earlier, only “error” and “critical” log entries will be transferred to storage.
  • By default the logs will be transferred to storage every minute or in other words the scheduled transfer period is one minute.

Following code segment shows what kind of trace log entries will be transferred if this option is chosen:

            Trace.WriteLine("This is verbose trace entry", "Verbose");//Will NOT be transferred.
            Trace.WriteLine("This is information trace entry", "Information");//Will NOT be transferred.
            Trace.WriteLine("This is warning trace entry", "Warning");//Will NOT be transferred.
            Trace.WriteLine("This is error trace entry", "Error");//Will BE transferred.
            Trace.WriteLine("This is critical trace entry", "Critical");//Will BE transferred.

All information

When you choose “All information” option, WAD extension transfers all log entries to storage. A few things to keep in mind:

  • This option will only enable following log types – trace and event logs.
  • This option will not do anything with the performance counters. So no performance counters data will be transferred to storage.
  • As said earlier, all log entries will be transferred to storage. This would include the following log level types: Verbose, Information, Warning, Error and Critical.
  • By default the logs will be transferred to storage every minute or in other words the scheduled transfer period is one minute.

Following code segment shows what kind of trace log entries will be transferred if this option is chosen:

            Trace.WriteLine("This is verbose trace entry", "Verbose");// Will BE transferred.
            Trace.WriteLine("This is information trace entry", "Information");// Will BE transferred.
            Trace.WriteLine("This is warning trace entry", "Warning");// Will BE transferred.
            Trace.WriteLine("This is error trace entry", "Error");//Will BE transferred.
            Trace.WriteLine("This is critical trace entry", "Critical");//Will BE transferred.

Custom plan

Custom plan is where things get interesting. Here you get full flexibility for configuring WAD. This is where you’re in complete control of what is captured and what gets transferred to storage. Here’re a few screenshots of the window where you can customize the diagnostics:

Trace Logs
clip_image002[5]

Event Logs
clip_image004[5]

Performance Counters
clip_image006

My guess is that most of the time developers would use these screens to configure diagnostics.

Diagnostics.wadcfg file

If you look closely in your solution explorer, you’ll notice a diagnostics.wadcfg file beneath your role name. You can read more about this file here: http://msdn.microsoft.com/en-us/library/windowsazure/hh411551.aspx.

clip_image002[7]

Essentially when you configure diagnostics using one of the options above, Visual Studio automatically updates this file. This is not a new concept and has been around for quite some time. What’s new is that now Visual Studio creates this file for you. So if you want to tweak some settings after Visual Studio has updated the file, just modify the XML file manually and you should be good to go.

Good Bye Old Storage Client Library

SDK 1.8 came with a new version of storage client library (2.0.0.0) however Windows Azure Diagnostics (WAD) was not updated to make use of this library. It was still using older version of storage client library. Needless to say, it was a big mess. Not only you would need to remember to add reference to both versions of the library but also take into consideration the different namespaces of older and newer versions of storage client library. With the release of SDK 2.0, WAD library is updated to make use of the latest version of storage client library which is 2.0.5.1 at the time SDK 2.0 was made live.

Diagnostics Extension

In this section we’ll talk about diagnostics extension and how it can be used to enable/disable or change diagnostics on the fly. I would recommend going through my blog post on extensions first because we’ll make use of code from that post. You can read that post here: https://gauravmantri.com/2013/05/06/windows-azure-cloud-services-extensions-and-service-management-api-fun-with-remote-desktop/.

For fun sake, we’ll create a cloud service (a simple worker role will do) and choose not to enable diagnostics. When we do that, out diagnostics.wadcfg file looks something like this:

<?xml version="1.0" encoding="utf-8"?>
<DiagnosticMonitorConfiguration configurationChangePollInterval="PT1M" 
                                overallQuotaInMB="4096" 
                                xmlns="http://schemas.microsoft.com/ServiceHosting/2010/10/DiagnosticsConfiguration">
  <DiagnosticInfrastructureLogs />
  <Directories>
    <IISLogs container="wad-iis-logfiles" />
    <CrashDumps container="wad-crash-dumps" />
  </Directories>
  <Logs bufferQuotaInMB="1024" scheduledTransferPeriod="PT0M" scheduledTransferLogLevelFilter="Error" />
  <WindowsEventLog bufferQuotaInMB="1024" scheduledTransferPeriod="PT0M" scheduledTransferLogLevelFilter="Error">
    <DataSource name="Application!*" />
  </WindowsEventLog>
</DiagnosticMonitorConfiguration>

This is how my worker role’s code looks like:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Threading;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.Diagnostics;
using Microsoft.WindowsAzure.ServiceRuntime;
using Microsoft.WindowsAzure.Storage;

namespace WorkerRole1
{
    public class WorkerRole : RoleEntryPoint
    {
        public override void Run()
        {
            // This is a sample worker implementation. Replace with your logic.
            Trace.TraceInformation("WorkerRole1 entry point called", "Information");

            while (true)
            {
                Thread.Sleep(10000);
                Trace.WriteLine("This is verbose trace entry", "Verbose");//Will NOT be transferred.
                Trace.WriteLine("This is information trace entry", "Information");//Will NOT be transferred.
                Trace.WriteLine("This is warning trace entry", "Warning");//Will BE transferred.
                Trace.WriteLine("This is error trace entry", "Error");//Will BE transferred.
                Trace.WriteLine("This is critical trace entry", "Critical");//Will BE transferred.
            }
        }

        public override bool OnStart()
        {
            // Set the maximum number of concurrent connections 
            ServicePointManager.DefaultConnectionLimit = 12;

            // For information on handling configuration changes
            // see the MSDN topic at http://go.microsoft.com/fwlink/?LinkId=166357.

            return base.OnStart();
        }
    }
}

Nothing special really. One thing you’ll notice in the OnStart() method is that I’ve not included initialization code for diagnostics. In prior versions of the SDK, I would need to add the following 2 lines of code for diagnostics to work:

            DiagnosticMonitorConfiguration config = DiagnosticMonitor.GetDefaultInitialConfiguration();
            DiagnosticMonitor.Start("Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString", config); 

And this is how my configuration file looks like. If you notice, there’s no diagnostics connection string there.

<?xml version="1.0" encoding="utf-8"?>
<ServiceConfiguration serviceName="WindowsAzure6" 
                      xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration" 
                      osFamily="3" osVersion="*" schemaVersion="2013-03.2.0">
  <Role name="WorkerRole1">
    <Instances count="1" />
    <ConfigurationSettings>
    </ConfigurationSettings>
  </Role>
</ServiceConfiguration>

Let’s publish this cloud service. To ensure Visual Studio is not doing anything “funky”, we’ll just package the file and upload it using Azure Management Studio. If you want, you can upload it through the portal as well.

Now it’s time to get a bit dramatic Smile. For the sake of adding drama to the whole equation, let’s assume that you’re working with a remote team with significant time difference so that you don’t really talk with your remote team in real time but using emails and stuff. The remote team is responsible for the code and deployment and in all their wisdom, they just deployed the service with code as above i.e. with no diagnostics enabled but you don’t know that. Suddenly you start getting calls from your users that the application is running slow and they are not able to do anything there. On top of that, your boss is breathing down your neck to find the problem. Your first reaction is to fire your favorite tools to see diagnostics data to figure out what’s going on but then you realize that your “smart ass remote team Smile” forgot to enable diagnostics. Following sections will help you get out of the murky water and make you an office superhero!!! Let’s begin.

Step 1: Get the details of diagnostics extension

Using the code from my previous post, you get the details of diagnostics extension so that you can fetch public and private configuration schema for diagnostics:

            var allExtensions = ListAvailableExtensions(subscriptionId, cert);
            var diagnostics = allExtensions.FirstOrDefault(e => e.Type == "Diagnostics");

Public Configuration Schema:

<?xml version="1.0" encoding="utf-8"?>
<xs:schema id="DiagnosticsConfigSchema"    targetNamespace="http://schemas.microsoft.com/ServiceHosting/2010/10/DiagnosticsConfiguration"    elementFormDefault="qualified"    xmlns="http://schemas.microsoft.com/ServiceHosting/2010/10/DiagnosticsConfiguration"    xmlns:mstns="http://tempuri.org/XMLSchema.xsd"    xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:simpleType name="PositiveDuration">
    <xs:restriction base="xs:duration">
      <xs:minInclusive value="PT0S" />
    </xs:restriction>
  </xs:simpleType>
  <xs:simpleType name="LogLevel">
    <xs:restriction base="xs:string">
      <xs:enumeration value="Undefined" />
      <xs:enumeration value="Verbose" />
      <xs:enumeration value="Information" />
      <xs:enumeration value="Warning" />
      <xs:enumeration value="Error" />
      <xs:enumeration value="Critical" />
    </xs:restriction>
  </xs:simpleType>
  <xs:simpleType name="ContainerName">
    <xs:restriction base="xs:string">
      <xs:pattern value="[a-z0-9][a-z0-9\-]{1,61}[a-z0-9]" />
    </xs:restriction>
  </xs:simpleType>
  <xs:simpleType name="AbsolutePathWithEnvironmentExpansion">
    <xs:restriction base="xs:string">
      <xs:pattern value="([a-zA-Z]:\\)?([^&lt;&gt;:&quot;/|?*]+)*(\\)?" />
    </xs:restriction>
  </xs:simpleType>
  <xs:simpleType name="RelativePathWithEnvironmentExpansion">
    <xs:restriction base="xs:string">
      <xs:pattern value="([^&lt;&gt;:&quot;/\\|?*]+)(\\([^&lt;&gt;:&quot;/\\|?*]+))*(\\)?" />
    </xs:restriction>
  </xs:simpleType>
  <xs:simpleType name="PerformanceCounterPath">
    <xs:restriction base="xs:string">
      <xs:pattern value="\\[^\\()/#*]+(\(([^\\()#*]+|[^\\()#*]+/[^\\()#*]+|[^\\()#*]+#[^\\()#*]+|[^\\()#*]+/[^\\()#*]+#[^\\()#*]+|\*)\))?\\[^\\()*]+" />
    </xs:restriction>
  </xs:simpleType>
  <xs:simpleType name="NamedElementNameString">
    <xs:restriction base="xs:string">
      <xs:pattern value="^[a-zA-Z_][^\\\/\:\*\?\&quot;\&lt;\&gt;\|]*(?&lt;![\.\s])$" />
    </xs:restriction>
  </xs:simpleType>
  <xs:attributeGroup name="BasicConfiguration">
    <xs:attribute name="bufferQuotaInMB" type="xs:unsignedInt" use="optional" default="0">
      <xs:annotation>
        <xs:documentation>          The maximum amount of file system storage allocated for the specified data.        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="scheduledTransferPeriod" type="PositiveDuration" use="optional" default="PT0S">
      <xs:annotation>
        <xs:documentation>          The interval between scheduled transfers for this data, rounded up to the nearest minute.        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:attributeGroup>
  <xs:attributeGroup name="LogLevel">
    <xs:attribute name="scheduledTransferLogLevelFilter" type="LogLevel" use="optional" default="Undefined">
      <xs:annotation>
        <xs:documentation>          The minimum log severity to transfer.        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:attributeGroup>
  <xs:attributeGroup name="DirectoryAttributes">
    <xs:attribute name="container" type="ContainerName" use="required">
      <xs:annotation>
        <xs:documentation>          The name of the container where the content of the directory is to be transferred.        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="directoryQuotaInMB" type="xs:unsignedInt" use="optional" default="0">
      <xs:annotation>
        <xs:documentation>          The maximum size of the directory in megabytes.        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:attributeGroup>
  <xs:complexType name="LogsBase">
    <xs:attributeGroup ref="BasicConfiguration" />
  </xs:complexType>
  <xs:complexType name="DiagnosticInfrastructureLogs">
    <xs:complexContent>
      <xs:extension base="LogsBase">
        <xs:attributeGroup ref="LogLevel" />
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="DirectoryBase" />
  <xs:complexType name="DirectoryAbsolute">
    <xs:complexContent>
      <xs:extension base="DirectoryBase">
        <xs:attribute name="path" type="AbsolutePathWithEnvironmentExpansion" use="required">
          <xs:annotation>
            <xs:documentation>              The absolute path to the directory to monitor.            </xs:documentation>
          </xs:annotation>
        </xs:attribute>
        <xs:attribute name="expandEnvironment" type="xs:boolean" use="required">
          <xs:annotation>
            <xs:documentation>              If true, then environment variables in the path will be expanded.            </xs:documentation>
          </xs:annotation>
        </xs:attribute>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="DirectoryLocalResource">
    <xs:complexContent>
      <xs:extension base="DirectoryBase">
        <xs:attribute name="relativePath" type="RelativePathWithEnvironmentExpansion" use="required">
          <xs:annotation>
            <xs:documentation>              The path relative to the local resource to monitor.            </xs:documentation>
          </xs:annotation>
        </xs:attribute>
        <xs:attribute name="name" type="NamedElementNameString" use="required">
          <xs:annotation>
            <xs:documentation>              The local resource that contains the directory to monitor.            </xs:documentation>
          </xs:annotation>
        </xs:attribute>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="DirectoryConfiguration">
    <xs:choice>
      <xs:element name="Absolute" type="DirectoryAbsolute">
        <xs:annotation>
          <xs:documentation>            The absolute path to the directory to monitor.          </xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="LocalResource" type="DirectoryLocalResource">
        <xs:annotation>
          <xs:documentation>            The path relative to a local resource to monitor.          </xs:documentation>
        </xs:annotation>
      </xs:element>
    </xs:choice>
    <xs:attributeGroup ref="DirectoryAttributes" />
  </xs:complexType>
  <xs:complexType name="SpecialLogDirectory">
    <xs:attributeGroup ref="DirectoryAttributes" />
  </xs:complexType>
  <xs:complexType name="DataSources">
    <xs:sequence maxOccurs="unbounded">
      <xs:element name="DirectoryConfiguration" type="DirectoryConfiguration" maxOccurs="unbounded">
        <xs:annotation>
          <xs:documentation>            The directory of log files to monitor.          </xs:documentation>
        </xs:annotation>
      </xs:element>
    </xs:sequence>
  </xs:complexType>
  <xs:complexType name="Directories">
    <xs:complexContent>
      <xs:extension base="LogsBase">
        <xs:all>
          <xs:element name="IISLogs" type="SpecialLogDirectory" minOccurs="0">
            <xs:annotation>
              <xs:documentation>                The IIS log directory.              </xs:documentation>
            </xs:annotation>
          </xs:element>
          <xs:element name="FailedRequestLogs" type="SpecialLogDirectory" minOccurs="0">
            <xs:annotation>
              <xs:documentation>                The failed request log directory.              </xs:documentation>
            </xs:annotation>
          </xs:element>
          <xs:element name="CrashDumps" type="SpecialLogDirectory" minOccurs="0">
            <xs:annotation>
              <xs:documentation>                The crash dump directory.              </xs:documentation>
            </xs:annotation>
          </xs:element>
          <xs:element name="DataSources" type="DataSources" minOccurs="0">
            <xs:annotation>
              <xs:documentation>                Additional log directories.              </xs:documentation>
            </xs:annotation>
          </xs:element>
        </xs:all>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="Logs">
    <xs:complexContent>
      <xs:extension base="LogsBase">
        <xs:attributeGroup ref="LogLevel" />
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="PerformanceCounterConfiguration">
    <xs:attribute name="counterSpecifier" type="PerformanceCounterPath" use="required">
      <xs:annotation>
        <xs:documentation>          The path to the performance counter to collect.        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="sampleRate" type="PositiveDuration" use="required">
      <xs:annotation>
        <xs:documentation>          The rate at which the performance counter should be sampled.        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType name="PerformanceCounters">
    <xs:complexContent>
      <xs:extension base="LogsBase">
        <xs:sequence maxOccurs="unbounded">
          <xs:element name="PerformanceCounterConfiguration" type="PerformanceCounterConfiguration">
            <xs:annotation>
              <xs:documentation>                The performance counter to collect.              </xs:documentation>
            </xs:annotation>
          </xs:element>
        </xs:sequence>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="DataSource">
    <xs:attribute name="name" type="xs:string" use="required">
      <xs:annotation>
        <xs:documentation>          An XPath expression specifying the logs to collect.        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType name="WindowsEventLog">
    <xs:complexContent>
      <xs:extension base="LogsBase">
        <xs:sequence maxOccurs="unbounded">
          <xs:element name="DataSource" type="DataSource">
            <xs:annotation>
              <xs:documentation>                The event log to monitor.              </xs:documentation>
            </xs:annotation>
          </xs:element>
        </xs:sequence>
        <xs:attributeGroup ref="LogLevel" />
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="DiagnosticMonitorConfiguration">
    <xs:all>
      <xs:element name="DiagnosticInfrastructureLogs" type="DiagnosticInfrastructureLogs" minOccurs="0">
        <xs:annotation>
          <xs:documentation>            Configures the logs generated by the underlying diagnostics infrastructure. The diagnostic infrastructure logs are useful for troubleshooting the diagnostics system itself.          </xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="Directories" type="Directories" minOccurs="0">
        <xs:annotation>
          <xs:documentation>            Describes the configuration of a directory to which file-based logs are written.          </xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="Logs" type="Logs" minOccurs="0">
        <xs:annotation>
          <xs:documentation>            Configures basic Windows Azure logs.          </xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="PerformanceCounters" type="PerformanceCounters" minOccurs="0">
        <xs:annotation>
          <xs:documentation>            Configures performance counter collection.          </xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element name="WindowsEventLog" type="WindowsEventLog" minOccurs="0">
        <xs:annotation>
          <xs:documentation>            Configures Windows event log collection.          </xs:documentation>
        </xs:annotation>
      </xs:element>
    </xs:all>
    <xs:attribute name="configurationChangePollInterval" type="PositiveDuration" use="optional" default="PT1M">
      <xs:annotation>
        <xs:documentation>          The interval at which the diagnostic monitor polls for configuration changes.        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
    <xs:attribute name="overallQuotaInMB" type="xs:unsignedInt" use="optional" default="4000">
      <xs:annotation>
        <xs:documentation>          The total amount of file system storage allocated for all logging buffers.        </xs:documentation>
      </xs:annotation>
    </xs:attribute>
  </xs:complexType>
  <xs:complexType name="StorageAccount">
    <xs:all>
      <xs:element name="Name" type="xs:string" minOccurs="0" maxOccurs="1" />
      <xs:element name="DefaultEndpointsProtocol" type="xs:string" minOccurs="0" maxOccurs="1" default="https" />
      <xs:element name="ConnectionQualifiers" type="xs:string" minOccurs="0" maxOccurs="1" />
    </xs:all>
  </xs:complexType>
  <xs:element name="PublicConfig">
    <xs:complexType>
      <xs:all>
        <xs:element name="WadCfg">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="DiagnosticMonitorConfiguration" type="DiagnosticMonitorConfiguration" minOccurs="0" maxOccurs="1"/>
            </xs:sequence>
          </xs:complexType>
        </xs:element>
        <xs:element name="StorageAccount" type="StorageAccount" minOccurs="1" />
      </xs:all>
    </xs:complexType>
  </xs:element>
</xs:schema>

Private Configuration Schema:

<?xml version="1.0" encoding="utf-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="PrivateConfig">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="StorageKey" type="xs:string" />
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

Based on these, you can generate XML template for public and private configurations.

Public Configuration XML Template:

<?xml version="1.0" encoding="utf-8"?>
<PublicConfig xmlns="http://schemas.microsoft.com/ServiceHosting/2010/10/DiagnosticsConfiguration">
  <StorageAccount>
    <ConnectionQualifiers>ConnectionQualifiers1</ConnectionQualifiers>
    <DefaultEndpointsProtocol>https</DefaultEndpointsProtocol>
    <Name>Name1</Name>
  </StorageAccount>
  <WadCfg>
    <DiagnosticMonitorConfiguration configurationChangePollInterval="PT1M" overallQuotaInMB="4000">
      <WindowsEventLog scheduledTransferLogLevelFilter="Undefined" bufferQuotaInMB="0" scheduledTransferPeriod="PT0S">
        <DataSource name="name1" />
        <DataSource name="name2" />
        <DataSource name="name3" />
      </WindowsEventLog>
      <PerformanceCounters bufferQuotaInMB="0" scheduledTransferPeriod="PT0S">
        <PerformanceCounterConfiguration counterSpecifier="counterSpecifier1" sampleRate="PT0S" />
        <PerformanceCounterConfiguration counterSpecifier="counterSpecifier2" sampleRate="P10675199DT2H48M5.477S" />
        <PerformanceCounterConfiguration counterSpecifier="counterSpecifier3" sampleRate="P365D" />
      </PerformanceCounters>
      <Logs scheduledTransferLogLevelFilter="Undefined" bufferQuotaInMB="0" scheduledTransferPeriod="PT0S" />
      <Directories bufferQuotaInMB="0" scheduledTransferPeriod="PT0S">
        <DataSources>
          <DirectoryConfiguration container="container1" directoryQuotaInMB="0">
            <Absolute path="path1" expandEnvironment="true" />
          </DirectoryConfiguration>
          <DirectoryConfiguration container="container2" directoryQuotaInMB="0">
            <Absolute path="path2" expandEnvironment="false" />
          </DirectoryConfiguration>
          <DirectoryConfiguration container="container3" directoryQuotaInMB="4294967295">
            <Absolute path="path3" expandEnvironment="true" />
          </DirectoryConfiguration>
          <DirectoryConfiguration container="container4" directoryQuotaInMB="1">
            <Absolute path="path4" expandEnvironment="false" />
          </DirectoryConfiguration>
          <DirectoryConfiguration container="container5" directoryQuotaInMB="4294967294">
            <Absolute path="path5" expandEnvironment="true" />
          </DirectoryConfiguration>
          <DirectoryConfiguration container="container6" directoryQuotaInMB="2">
            <Absolute path="path6" expandEnvironment="false" />
          </DirectoryConfiguration>
          <DirectoryConfiguration container="container7" directoryQuotaInMB="4294967293">
            <Absolute path="path7" expandEnvironment="true" />
          </DirectoryConfiguration>
          <DirectoryConfiguration container="container8" directoryQuotaInMB="3">
            <Absolute path="path8" expandEnvironment="false" />
          </DirectoryConfiguration>
          <DirectoryConfiguration container="container9" directoryQuotaInMB="4294967292">
            <Absolute path="path9" expandEnvironment="true" />
          </DirectoryConfiguration>
        </DataSources>
        <CrashDumps container="container1" directoryQuotaInMB="0" />
        <FailedRequestLogs container="container1" directoryQuotaInMB="0" />
        <IISLogs container="container1" directoryQuotaInMB="0" />
      </Directories>
      <DiagnosticInfrastructureLogs scheduledTransferLogLevelFilter="Undefined" bufferQuotaInMB="0" scheduledTransferPeriod="PT0S" />
    </DiagnosticMonitorConfiguration>
  </WadCfg>
</PublicConfig>

Private Configuration XML Template:

<?xml version="1.0" encoding="utf-8"?>
<PrivateConfig>
  <StorageKey>StorageKey1</StorageKey>
</PrivateConfig>

Step 2: Create XML files

Using the templates from the previous steps, we create public and private configuration XML files. Let’s assume that we’re only interested in capturing all trace log entries, “Application” event logs and 2 performance counters – “\Processor(_Total)\% Processor Time” and “\Memory\Available Mbytes”. Based on this, this is how our XML files look like:

Public Configuration XML:

<?xml version="1.0" encoding="utf-8"?>
<PublicConfig xmlns="http://schemas.microsoft.com/ServiceHosting/2010/10/DiagnosticsConfiguration">
  <StorageAccount>
    <ConnectionQualifiers/>
    <DefaultEndpointsProtocol>https</DefaultEndpointsProtocol>
    <Name>[your storage account name]</Name>
  </StorageAccount>
  <WadCfg>
    <DiagnosticMonitorConfiguration configurationChangePollInterval="PT1M" overallQuotaInMB="4096">
      <DiagnosticInfrastructureLogs />
      <Directories>
        <IISLogs container="wad-iis-logfiles" />
        <CrashDumps container="wad-crash-dumps" />
      </Directories>
      <Logs bufferQuotaInMB="1024" scheduledTransferPeriod="PT1M" scheduledTransferLogLevelFilter="Verbose" />
      <PerformanceCounters bufferQuotaInMB="1024" scheduledTransferPeriod="PT1M">
        <PerformanceCounterConfiguration counterSpecifier="\Processor(_Total)\% Processor Time" sampleRate="PT1S" />
        <PerformanceCounterConfiguration counterSpecifier="\Memory\Available MBytes" sampleRate="PT1S" />
      </PerformanceCounters>
      <WindowsEventLog bufferQuotaInMB="1024" scheduledTransferPeriod="PT1M" scheduledTransferLogLevelFilter="Error">
        <DataSource name="Application!*" />
      </WindowsEventLog>
    </DiagnosticMonitorConfiguration>
  </WadCfg>
</PublicConfig>

Private Configuration XML:

<?xml version="1.0" encoding="utf-8"?>
<PrivateConfig>
  <StorageKey>[your storage account key]</StorageKey>
</PrivateConfig>

Step 3: Add WAD Extension

To add WAD extension, we’ll use public and private key configuration for the extension and add that extension to the cloud service.

            string publicConfiguration = @"<?xml version=""1.0"" encoding=""utf-8""?>
                <PublicConfig xmlns=""http://schemas.microsoft.com/ServiceHosting/2010/10/DiagnosticsConfiguration"">
                  <StorageAccount>
                    <ConnectionQualifiers/>
                    <DefaultEndpointsProtocol>https</DefaultEndpointsProtocol>
                    <Name>[your storage account name]</Name>
                  </StorageAccount>
                  <WadCfg>
                    <DiagnosticMonitorConfiguration configurationChangePollInterval=""PT1M"" overallQuotaInMB=""4096"">
                      <DiagnosticInfrastructureLogs />
                      <Directories>
                        <IISLogs container=""wad-iis-logfiles"" />
                        <CrashDumps container=""wad-crash-dumps"" />
                      </Directories>
                      <Logs bufferQuotaInMB=""1024"" scheduledTransferPeriod=""PT1M"" scheduledTransferLogLevelFilter=""Verbose"" />
                      <PerformanceCounters bufferQuotaInMB=""1024"" scheduledTransferPeriod=""PT1M"">
                        <PerformanceCounterConfiguration counterSpecifier=""\Processor(_Total)\% Processor Time"" sampleRate=""PT1S"" />
                        <PerformanceCounterConfiguration counterSpecifier=""\Memory\Available MBytes"" sampleRate=""PT1S"" />
                      </PerformanceCounters>
                      <WindowsEventLog bufferQuotaInMB=""1024"" scheduledTransferPeriod=""PT1M"" scheduledTransferLogLevelFilter=""Error"">
                        <DataSource name=""Application!*"" />
                      </WindowsEventLog>
                    </DiagnosticMonitorConfiguration>
                  </WadCfg>
                </PublicConfig>";

            string privateConfiguration = @"<?xml version=""1.0"" encoding=""utf-8""?>
                <PrivateConfig>
                  <StorageKey>[your storage account key]</StorageKey>
                </PrivateConfig>";

            AzureExtension wadExtension = new AzureExtension()
            {
                ProviderNamespace = "Microsoft.Windows.Azure.Extensions",
                Type = "Diagnostics",
                Id = "DiagnosticsExtensionWhichWillSaveMyButt",
                PublicConfiguration = publicConfiguration,
                PrivateConfiguration = privateConfiguration,
            };

            AddExtension(subscriptionId, cert, cloudServiceName, wadExtension);

Step 4: Update Configuration

Once the extension is added, next step would be to update the cloud service configuration by performing “Change Deployment Configuration” operation. For this, first we may need to fetch the current configuration. To do so, we’ll perform “Get Hosted Service Properties” operation and try to get both production and staging configuration settings. Here’s the sample code to get the configuration settings:

        private static string[] GetCloudServiceDeploymentConfigurations(string subscriptionId, X509Certificate2 cert, string cloudServiceName)
        {
            string[] deploymentConfigurations = new string[2];//We'll try to get both production and staging deployment configurations. The 1st element will always be production and the 2nd will be staging.
            string uri = string.Format("https://management.core.windows.net/{0}/services/hostedservices/{1}?embed-detail=true", subscriptionId, cloudServiceName);
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
            request.Method = "GET";
            request.Headers.Add("x-ms-version", "2013-03-01");
            request.ClientCertificates.Add(cert);
            using (HttpWebResponse resp = (HttpWebResponse)request.GetResponse())
            {
                using (var streamReader = new StreamReader(resp.GetResponseStream()))
                {
                    string result = streamReader.ReadToEnd();
                    XElement cloudServiceProperties = XElement.Parse(result);
                    var deployments = cloudServiceProperties.Elements(XName.Get("Deployments", "http://schemas.microsoft.com/windowsazure"));
                    foreach (var deployment in deployments.Elements())
                    {
                        var slotElement = deployment.Element(XName.Get("DeploymentSlot", "http://schemas.microsoft.com/windowsazure"));
                        var configElement = deployment.Element(XName.Get("Configuration", "http://schemas.microsoft.com/windowsazure"));
                        var deploymentSlot = slotElement.Value;
                        var configurationSettings = Encoding.UTF8.GetString(Convert.FromBase64String(configElement.Value));
                        switch (deploymentSlot.ToUpper())
                        {
                            case "PRODUCTION":
                                deploymentConfigurations[0] = configurationSettings;
                                break;
                            case "STAGING":
                                deploymentConfigurations[1] = configurationSettings;
                                break;
                        }
                    }
                }
            }
            return deploymentConfigurations;
        }

Now that we’ve got the configurations for both production and staging slots, let’s apply the changes. Here’s the sample code for change deployment configuration operation:

        private static void ChangeDeploymentConfiguration(string subscriptionId, X509Certificate2 cert, string cloudServiceName, string slot, string configuration)
        {
            try
            {
                string uri = string.Format("https://management.core.windows.net/{0}/services/hostedservices/{1}/deploymentslots/{2}/?comp=config", subscriptionId, cloudServiceName, slot);
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
                request.Method = "POST";
                request.ContentType = "application/xml";
                request.Headers.Add("x-ms-version", "2013-03-01");
                request.ClientCertificates.Add(cert);
                byte[] content = Encoding.UTF8.GetBytes(configuration);
                request.ContentLength = content.Length;
                using (var requestStream = request.GetRequestStream())
                {
                    requestStream.Write(content, 0, content.Length);
                }

                using (HttpWebResponse resp = (HttpWebResponse)request.GetResponse())
                {
                }
            }
            catch (WebException webEx)
            {
                using (var streamReader = new StreamReader(webEx.Response.GetResponseStream()))
                {
                    string result = streamReader.ReadToEnd();
                    Console.WriteLine(result);
                }
            }

        }

And this is how you call it:

            string updateConfigurationFormat = @"<?xml version=""1.0"" encoding=""utf-8""?>
                <ChangeConfiguration xmlns=""http://schemas.microsoft.com/windowsazure"">
                  <Configuration>{0}</Configuration>
                <ExtensionConfiguration>
                    <AllRoles>
                      <Extension>
                        <Id>{1}</Id>
                      </Extension>
                    </AllRoles>
                 </ExtensionConfiguration>
                </ChangeConfiguration>";

            var productionConfig = configurations[0];
            if (!string.IsNullOrWhiteSpace(productionConfig))
            {
                var productionConfigurationSetting = string.Format(updateConfigurationFormat, Convert.ToBase64String(Encoding.UTF8.GetBytes(productionConfig)), wadExtension.Id);
                ChangeDeploymentConfiguration(subscriptionId, cert, cloudServiceName, "Production", productionConfigurationSetting);
            }

Putting it all together nicely in just one function:

        private static void AddDiagnosticsExtension(string subscriptionId, X509Certificate2 cert, string cloudServiceName)
        {
            string publicConfiguration = @"<?xml version=""1.0"" encoding=""utf-8""?>
                <PublicConfig xmlns=""http://schemas.microsoft.com/ServiceHosting/2010/10/DiagnosticsConfiguration"">
                  <StorageAccount>
                    <ConnectionQualifiers/>
                    <DefaultEndpointsProtocol>https</DefaultEndpointsProtocol>
                    <Name>[your storage account name]</Name>
                  </StorageAccount>
                  <WadCfg>
                    <DiagnosticMonitorConfiguration configurationChangePollInterval=""PT1M"" overallQuotaInMB=""4096"">
                      <DiagnosticInfrastructureLogs />
                      <Directories>
                        <IISLogs container=""wad-iis-logfiles"" />
                        <CrashDumps container=""wad-crash-dumps"" />
                      </Directories>
                      <Logs bufferQuotaInMB=""1024"" scheduledTransferPeriod=""PT1M"" scheduledTransferLogLevelFilter=""Verbose"" />
                      <PerformanceCounters bufferQuotaInMB=""1024"" scheduledTransferPeriod=""PT1M"">
                        <PerformanceCounterConfiguration counterSpecifier=""\Processor(_Total)\% Processor Time"" sampleRate=""PT1S"" />
                        <PerformanceCounterConfiguration counterSpecifier=""\Memory\Available MBytes"" sampleRate=""PT1S"" />
                      </PerformanceCounters>
                      <WindowsEventLog bufferQuotaInMB=""1024"" scheduledTransferPeriod=""PT1M"" scheduledTransferLogLevelFilter=""Error"">
                        <DataSource name=""Application!*"" />
                      </WindowsEventLog>
                    </DiagnosticMonitorConfiguration>
                  </WadCfg>
                </PublicConfig>";

            string privateConfiguration = @"<?xml version=""1.0"" encoding=""utf-8""?>
                <PrivateConfig>
                  <StorageKey>[your storage account key]</StorageKey>
                </PrivateConfig>";

            string updateConfigurationFormat = @"<?xml version=""1.0"" encoding=""utf-8""?>
                <ChangeConfiguration xmlns=""http://schemas.microsoft.com/windowsazure"">
                  <Configuration>{0}</Configuration>
                <ExtensionConfiguration>
                    <AllRoles>
                      <Extension>
                        <Id>{1}</Id>
                      </Extension>
                    </AllRoles>
                 </ExtensionConfiguration>
                </ChangeConfiguration>";

            AzureExtension wadExtension = new AzureExtension()
            {
                ProviderNamespace = "Microsoft.Windows.Azure.Extensions",
                Type = "Diagnostics",
                Id = "DiagnosticsExtensionWhichWillSaveMyButt",
                PublicConfiguration = publicConfiguration,
                PrivateConfiguration = privateConfiguration,
            };

            AddExtension(subscriptionId, cert, cloudServiceName, wadExtension);

            var configurations = GetCloudServiceDeploymentConfigurations(subscriptionId, cert, cloudServiceName);
            var productionConfig = configurations[0];
            if (!string.IsNullOrWhiteSpace(productionConfig))
            {
                var productionConfigurationSetting = string.Format(updateConfigurationFormat, Convert.ToBase64String(Encoding.UTF8.GetBytes(productionConfig)), wadExtension.Id);
                ChangeDeploymentConfiguration(subscriptionId, cert, cloudServiceName, "Production", productionConfigurationSetting);
            }
            var stagingConfig = configurations[1];
            if (!string.IsNullOrWhiteSpace(stagingConfig))
            {
                var stagingConfigurationSetting = string.Format(updateConfigurationFormat, Convert.ToBase64String(Encoding.UTF8.GetBytes(stagingConfig)), wadExtension.Id);
                ChangeDeploymentConfiguration(subscriptionId, cert, cloudServiceName, "Staging", stagingConfigurationSetting);
            }
        }

That’s pretty much it!!! Once the operation is complete, you should be able to see the diagnostics data for your cloud service in some time.

clip_image002[9]

Summary

I strongly believe that changes done to diagnostics in SDK 2.0 are pretty significant. From my personal experience working with various users of Cerebrata Azure Diagnostics Manager, I do believe that it’s a boon to developers. Now it is very easy and straight forward to configure the diagnostics and change it on the fly. That’s it for this post. As always, if you find any issues with the post please let me know and I’ll fix it ASAP.

Happy Coding!


[This is the latest product I'm working on]