Bislang habe ich meistens die klassischen Debugmöglichkeiten für Windows Azure Applikationen genutzt, wie z.B. ein TextWriterTraceListener, der in eine Textdatei schreibt.
Die Textdatei kann anschließend, via Remote Desktop Connection (siehe Remote Desktop Connections für Windows Azure Rollen aktivieren), eingesehen werden.
Dieser Ansatz hat natürlich mehrere Nachteile:
- Die Textdatei wird bei der Neuerstellung der Windows Azure Instanz gelöscht.
- Bei mehreren Instanzen müssen ggf. mehrere Textdateien, in unterschiedlichen VMs, durchsucht werden.
- Persistente Aktivierung bzw. Deaktivierung des Trace Listeners, ist nur durch eine Neuinstallation der Rolle umsetzbar.
- Auch bei temporärer Aktivierung bzw. Deaktivierung des Trace Listeners, muss man sich auf jede Rolleninstanz aufschalten und die Web.Config bearbeiten.
- …
Auf der Suche nach Alternativen, bin ich u.a. auf den TableStorageTraceListener gestoßen, der im Windows Azure Platform Training Kit enthalten ist.
Der TableStorageTraceListener
Zur Demonstration des TableStorageTraceListener's, verwende ich ein Cloud Projekt mit einer ASP.NET Web Forms Rolle.
Als Erstes habe ich die AzureDiagnostics Klassenbibliothek zur Solution und eine Referenz zum Web Projekt hinzugefügt.
Danach könnte man natürlich den Trace Listener, mit diesem Eintrag in der Web.Config, hinzufügen:
<system.diagnostics> <trace autoflush="true" indentsize="2"> <listeners> <add name="TableStorageTraceListener" type="AzureDiagnostics.TableStorageTraceListener, AzureDiagnostics" /> </listeners> </trace> </system.diagnostics>
Allerdings fand ich den Ansatz, der im Training Kit verfolgt wird, ansprechender:
Hierbei wird Code zum Registrieren bzw. Entfernen des Trace Listeners in der Global.asax hinzugefügt.
Als Schalter dient eine Azure Rolleneinstellung.
Mittels der erste Methode, kann der Trace Listener "von Außen" ein- bzw. ausgeschaltet werden:
private static void ConfigureTraceListener() { bool enableTraceListener; string enableTraceListenerSetting = RoleEnvironment.GetConfigurationSettingValue("EnableTableStorageTraceListener"); if (bool.TryParse(enableTraceListenerSetting, out enableTraceListener)) { if (enableTraceListener) { var listener = new AzureDiagnostics.TableStorageTraceListener( "Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString") { Name = "TableStorageTraceListener" }; System.Diagnostics.Trace.Listeners.Add(listener); System.Diagnostics.Trace.AutoFlush = true; } else { System.Diagnostics.Trace.Listeners.Remove("TableStorageTraceListener"); } } }
In folgendem Event Handler, wird sichergestellt, dass die Rolle nicht neu startet, wenn die EnableTableStorageTraceListener Rolleneinstellung geändert wird:
private static void RoleEnvironmentChanging(object sender, RoleEnvironmentChangingEventArgs e) { // Für alle Änderungen der Rollenkonfiguration // mit Ausnahme von "EnableTableStorageTraceListener" ... if (e.Changes.OfType() .Any(change => change.ConfigurationSettingName != "EnableTableStorageTraceListener")) { // ... wird die Rolleninstanz neu gestartet. e.Cancel = true; } }
Im nächsten Event Handler, wird die ConfigureTraceListener Methode aufgerufen, wenn die EnableTableStorageTraceListener Rolleneinstellung geändert wurde:
private static void RoleEnvironmentChanged(object sender, RoleEnvironmentChangedEventArgs e) { // Bei Änderungen an der "EnableTableStorageTraceListener" Konfiguration // wird der TraceListener hinzugefügt bzw. entfernt. if (e.Changes.OfType() .Any(change => change.ConfigurationSettingName == "EnableTableStorageTraceListener")) { ConfigureTraceListener(); } }
Anschließend muss das Ganze im Application_Start Event Handler eingebunden werden:
void Application_Start(object sender, EventArgs e) { CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSetter) => { configSetter(RoleEnvironment.GetConfigurationSettingValue(configName)); }); ConfigureTraceListener(); RoleEnvironment.Changing += RoleEnvironmentChanging; RoleEnvironment.Changed += RoleEnvironmentChanged; }
Jetzt noch die Rolleneinstellung im Cloud Projekt angelegen und fertig:
Zum Testen, habe ich die Default.aspx, sowie den Application_Error Event Handler der Global.asax, mit verschiedenen Trace Aufrufen bestückt:
Auszug aus dem Code-Behind der Default.aspx:
protected void btnTraceInformation_Click(object sender, EventArgs e) { System.Diagnostics.Trace.TraceInformation(txtTraceInformation.Text); txtTraceInformation.Text = ""; } protected void btnTraceWarning_Click(object sender, EventArgs e) { System.Diagnostics.Trace.TraceWarning(txtTraceWarning.Text); txtTraceWarning.Text = ""; } protected void btnTraceError_Click(object sender, EventArgs e) { System.Diagnostics.Trace.TraceError(txtTraceError.Text); txtTraceError.Text = ""; } protected void btnUnhandledException_Click(object sender, EventArgs e) { throw new Exception(txtUnhandledException.Text); }
Auszug aus dem Code-Behind der Global.asax:
void Application_Error(object sender, EventArgs e) { var lastError = Server.GetLastError(); System.Diagnostics.Trace.TraceError(lastError.Message); }
Das Trace Log auslesen
Um das Table Storage Trace Log auszulesen, kann man eines der zahlreichen Azure Storage Tools verwenden, wie z.B. den Azure Storage Explorer…
…oder sich einen eigenen Rich- bzw. Web-Client schreiben, wie z.B. die ViewLog.aspx des Demoprojektes…
Änderungen am TableStorageTraceListener
Nachdem ich eine Weile mit dem TableStorageTraceListener rumgespielt hatte, fiel mir auch schon ein Erweiterungsbedarf auf.
Der, mit dem Training Kit mitgelieferte, Trace Listener loggt weder Rollen- noch Instanzinformationen, was die Fehlersuche bei verschiedenen Rollen, und/oder mehreren Instanzen von Azure Rollen, erschwert.
Um die Datenanalyse zu vereinfachen, habe ich die LogEntry Entität und den TableStorageTraceListener, um die Eigenschaften RoleName und RoleId, erweitert:
Auszug aus der LogEntry.cs:
public class LogEntry : TableServiceEntity { public long EventTickCount { get; set; } public int Level { get; set; } public int EventId { get; set; } public int Pid { get; set; } public string Tid { get; set; } public string RoleName { get; set; } public string RoleId { get; set; } public string Message { get; set; } }
Auszug aus der TableStorageTraceListener.cs:
private void AppendEntry(int id, TraceEventType eventType, TraceEventCache eventCache) { // ... LogEntry entry = new LogEntry() { PartitionKey = string.Format("{0:D10}", eventCache.Timestamp >> 30), RowKey = string.Format("{0:D19}", eventCache.Timestamp), EventTickCount = eventCache.Timestamp, Level = (int)eventType, EventId = id, Pid = eventCache.ProcessId, Tid = eventCache.ThreadId, RoleName = RoleEnvironment.CurrentRoleInstance.Role.Name, RoleId = RoleEnvironment.CurrentRoleInstance.Id, Message = message }; // ... }
Download der Beispielanwendung:
Weitere Informationen unter: |