Skip to content

Commit

Permalink
Improvements to TraceWriter pipeline (adding TraceEvent, FunctionInvo…
Browse files Browse the repository at this point in the history
…cationException)
  • Loading branch information
mathewc committed Oct 6, 2015
1 parent cfc875a commit 53337ce
Show file tree
Hide file tree
Showing 18 changed files with 352 additions and 84 deletions.
19 changes: 10 additions & 9 deletions src/Microsoft.Azure.WebJobs.Host/CompositeTraceWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,28 +32,29 @@ public CompositeTraceWriter(TraceWriter traceWriter, TextWriter textWriter)
_innerTextWriter = textWriter;
}

public override void Trace(TraceLevel level, string source, string message, Exception ex)
public override void Trace(TraceEvent traceEvent)
{
InvokeTraceWriters(level, source, message, ex);
InvokeTextWriter(level, source, message, ex);
InvokeTraceWriters(traceEvent);
InvokeTextWriter(traceEvent);
}

protected virtual void InvokeTraceWriters(TraceLevel level, string source, string message, Exception ex)
protected virtual void InvokeTraceWriters(TraceEvent traceEvent)
{
foreach (TraceWriter traceWriter in _innerTraceWriters)
{
// filter based on level before delegating
if (traceWriter.Level >= level)
if (traceWriter.Level >= traceEvent.Level)
{
traceWriter.Trace(level, source, message, ex);
traceWriter.Trace(traceEvent);
}
}
}

protected virtual void InvokeTextWriter(TraceLevel level, string source, string message, Exception ex)
protected virtual void InvokeTextWriter(TraceEvent traceEvent)
{
if (_innerTextWriter != null)
{
string message = traceEvent.Message;
if (!string.IsNullOrEmpty(message) &&
message.EndsWith("\r\n", StringComparison.OrdinalIgnoreCase))
{
Expand All @@ -63,9 +64,9 @@ protected virtual void InvokeTextWriter(TraceLevel level, string source, string
}

_innerTextWriter.WriteLine(message);
if (ex != null)
if (traceEvent.Exception != null)
{
_innerTextWriter.WriteLine(ex.ToDetails());
_innerTextWriter.WriteLine(traceEvent.Exception.ToDetails());
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions src/Microsoft.Azure.WebJobs.Host/ConsoleTraceWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ public ConsoleTraceWriter(JobHostTraceConfiguration traceConfig, TextWriter cons
_traceConfig = traceConfig;
}

protected override void InvokeTextWriter(TraceLevel level, string source, string message, Exception ex)
protected override void InvokeTextWriter(TraceEvent traceEvent)
{
if (MapTraceLevel(source, level) <= _traceConfig.ConsoleLevel)
if (MapTraceLevel(traceEvent.Source, traceEvent.Level) <= _traceConfig.ConsoleLevel)
{
// For Errors/Warnings we change the Console color
// for visibility
var holdColor = Console.ForegroundColor;
bool changedColor = false;
switch (level)
switch (traceEvent.Level)
{
case TraceLevel.Error:
Console.ForegroundColor = ConsoleColor.Red;
Expand All @@ -41,7 +41,7 @@ protected override void InvokeTextWriter(TraceLevel level, string source, string
break;
}

base.InvokeTextWriter(level, source, message, ex);
base.InvokeTextWriter(traceEvent);

if (changedColor)
{
Expand Down
12 changes: 6 additions & 6 deletions src/Microsoft.Azure.WebJobs.Host/Executors/FunctionExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,7 @@ public async Task<IDelayedException> TryExecuteAsync(IFunctionInstance instance,
logCompletedCancellationToken = cancellationToken;
}

await _functionInstanceLogger.LogFunctionCompletedAsync(completedMessage,
logCompletedCancellationToken);
await _functionInstanceLogger.LogFunctionCompletedAsync(completedMessage, logCompletedCancellationToken);

if (loggedStartedEvent)
{
Expand Down Expand Up @@ -146,8 +145,7 @@ private async Task<string> ExecuteWithLogMessageAsync(IFunctionInstance instance

try
{
await ExecuteWithOutputLogsAsync(instance, parameters, traceWriter, outputDefinition,
parameterLogCollector, cancellationToken);
await ExecuteWithOutputLogsAsync(instance, parameters, traceWriter, outputDefinition, parameterLogCollector, cancellationToken);
exceptionInfo = null;
}
catch (OperationCanceledException exception)
Expand All @@ -156,8 +154,10 @@ await ExecuteWithOutputLogsAsync(instance, parameters, traceWriter, outputDefini
}
catch (Exception exception)
{
traceWriter.Error("Exception while executing:", exception, TraceSource.Execution);
exceptionInfo = ExceptionDispatchInfo.Capture(exception);
string errorMessage = string.Format("Exception while executing function: {0}", instance.FunctionDescriptor.ShortName);
FunctionInvocationException functionException = new FunctionInvocationException(errorMessage, instance.Id, instance.FunctionDescriptor.FullName, exception);
traceWriter.Error(errorMessage, functionException, TraceSource.Execution);
exceptionInfo = ExceptionDispatchInfo.Capture(functionException);
}
}

Expand Down
85 changes: 85 additions & 0 deletions src/Microsoft.Azure.WebJobs.Host/FunctionInvocationException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Runtime.Serialization;

namespace Microsoft.Azure.WebJobs.Host
{
/// <summary>
/// Exception thrown when a job function invocation fails.
/// </summary>
[Serializable]
public class FunctionInvocationException : Exception
{
/// <inheritdoc/>
public FunctionInvocationException() : base()
{
}

/// <inheritdoc/>
public FunctionInvocationException(string message) : base(message)
{
}

/// <inheritdoc/>
public FunctionInvocationException(string message, Exception innerException) : base(message, innerException)
{
}

/// <summary>
/// Constructs a new instance.
/// </summary>
/// <param name="info">The <see cref="SerializationInfo"/>.</param>
/// <param name="context">The <see cref="StreamingContext"/>.</param>
protected FunctionInvocationException(SerializationInfo info, StreamingContext context) : base(info, context)
{
if (info == null)
{
throw new ArgumentNullException("info");
}

InstanceId = Guid.Parse(info.GetString("InstanceId"));
MethodName = info.GetString("MethodName");
}

/// <summary>
/// Constructs a new instance.
/// </summary>
/// <param name="message">The exception message.</param>
/// <param name="instanceId">The function instance Id.</param>
/// <param name="methodName">The fully qualified method name.</param>
/// <param name="innerException">The exception that is the cause of the current exception (or null).</param>
public FunctionInvocationException(string message, Guid instanceId, string methodName, Exception innerException)
: base(message, innerException)
{
InstanceId = instanceId;
MethodName = methodName;
}

/// <summary>
/// Gets the instance Id of the failed invocation. This value can be correlated
/// to the Dashboard logs.
/// </summary>
public Guid InstanceId { get; set; }

/// <summary>
/// Gets the fully qualified name of the function.
/// </summary>
public string MethodName { get; set; }

/// <inheritdoc/>
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
if (info == null)
{
throw new ArgumentNullException("info");
}

info.AddValue("InstanceId", this.InstanceId);
info.AddValue("MethodName", this.MethodName);

base.GetObjectData(info, context);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public Task LogFunctionCompletedAsync(FunctionCompletedMessage message, Cancella
// Also log the eror message using TraceSource.Host, to ensure
// it gets written to Console
_trace.Error(string.Format(CultureInfo.InvariantCulture,
" Function had errors. See Azure WebJobs SDK dashboard for details. Instance ID is '{0}'", message.FunctionInstanceId), source: TraceSource.Host);
" Function had errors. See Azure WebJobs SDK dashboard for details. Instance ID is '{0}'", message.FunctionInstanceId), message.Failure.Exception, TraceSource.Host);
}
return Task.FromResult(0);
}
Expand Down
80 changes: 80 additions & 0 deletions src/Microsoft.Azure.WebJobs.Host/TraceEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;

namespace Microsoft.Azure.WebJobs.Host
{
/// <summary>
/// Defines a trace event that can be written to a <see cref="TraceWriter"/>.
/// </summary>
public class TraceEvent
{
private IDictionary<string, object> _properties;

/// <summary>
/// Constructs a new instance.
/// </summary>
/// <param name="level">The level of the trace.</param>
/// <param name="message">The trace message.</param>
/// <param name="source">The source of the trace (may be null).</param>
/// <param name="exception">The exception that caused the trace (may be null).</param>
public TraceEvent(TraceLevel level, string message, string source = null, Exception exception = null)
{
Level = level;
Message = message;
Source = source;
Exception = exception;
Timestamp = DateTime.UtcNow;
}

/// <summary>
/// The time the trace was recorded.
/// </summary>
public DateTime Timestamp { get; set; }

/// <summary>
/// The level of the trace.
/// </summary>
public TraceLevel Level { get; set; }

/// <summary>
/// The source of the trace.
/// </summary>
public string Source { get; set; }

/// <summary>
/// The trace message.
/// </summary>
public string Message { get; set; }

/// <summary>
/// The exception that caused the trace.
/// </summary>
public Exception Exception { get; set; }

/// <summary>
/// Gets a set of properties for the <see cref="TraceEvent"/>.
/// </summary>
public IDictionary<string, object> Properties
{
get
{
if (_properties == null)
{
_properties = new Dictionary<string, object>();
}
return _properties;
}
}

/// <inheritdoc/>
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "{0} {1} {2} {3} {4}", Timestamp, Level.ToString(), Message, Source, Exception);
}
}
}
27 changes: 12 additions & 15 deletions src/Microsoft.Azure.WebJobs.Host/TraceWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
namespace Microsoft.Azure.WebJobs.Host
{
/// <summary>
/// Base class for trace writers used by <see cref="JobHost"/>.
/// Base class for trace writers used by the <see cref="JobHost"/>.
/// See <see cref="JobHostConfiguration.Tracing"/> for details.
/// </summary>
public abstract class TraceWriter
Expand All @@ -33,53 +33,50 @@ protected TraceWriter(TraceLevel level)
public TraceLevel Level { get; set; }

/// <summary>
/// Writes a trace entry.
/// Writes a trace event.
/// </summary>
/// <param name="level">The <see cref="TraceLevel"/> for the trace entry</param>
/// <param name="source">Optional source of the message.</param>
/// <param name="message">The trace message.</param>
/// <param name="ex">Optional <see cref="Exception"/> (if an error is being traced).</param>
public abstract void Trace(TraceLevel level, string source, string message, Exception ex);
/// <param name="traceEvent">The <see cref="TraceEvent"/> to trace.</param>
public abstract void Trace(TraceEvent traceEvent);

/// <summary>
/// Writes a <see cref="TraceLevel.Verbose"/> level trace entry.
/// Writes a <see cref="TraceLevel.Verbose"/> level trace event.
/// </summary>
/// <param name="message">The trace message.</param>
/// <param name="source">The source of the message.</param>
public void Verbose(string message, string source = null)
{
Trace(TraceLevel.Verbose, source, message, null);
Trace(new TraceEvent(TraceLevel.Verbose, message, source));
}

/// <summary>
/// Writes a <see cref="TraceLevel.Info"/> level trace entry.
/// Writes a <see cref="TraceLevel.Info"/> level trace event.
/// </summary>
/// <param name="message">The trace message.</param>
/// <param name="source">The source of the message.</param>
public void Info(string message, string source = null)
{
Trace(TraceLevel.Info, source, message, null);
Trace(new TraceEvent(TraceLevel.Info, message, source));
}

/// <summary>
/// Writes a <see cref="TraceLevel.Warning"/> level trace entry.
/// Writes a <see cref="TraceLevel.Warning"/> level trace event.
/// </summary>
/// <param name="message">The trace message.</param>
/// <param name="source">The source of the message.</param>
public void Warning(string message, string source = null)
{
Trace(TraceLevel.Warning, source, message, null);
Trace(new TraceEvent(TraceLevel.Warning, message, source));
}

/// <summary>
/// Writes a <see cref="TraceLevel.Error"/> level trace entry.
/// Writes a <see cref="TraceLevel.Error"/> level trace event.
/// </summary>
/// <param name="message">The trace message.</param>
/// <param name="ex">The optional <see cref="Exception"/> for the error.</param>
/// <param name="source">The source of the message.</param>
public void Error(string message, Exception ex = null, string source = null)
{
Trace(TraceLevel.Error, source, message, ex);
Trace(new TraceEvent(TraceLevel.Error, message, source, ex));
}

/// <summary>
Expand Down
2 changes: 2 additions & 0 deletions src/Microsoft.Azure.WebJobs.Host/WebJobs.Host.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,7 @@
<Compile Include="Timers\RandomExtensions.cs" />
<Compile Include="Timers\RecurrentTaskSeriesCommand.cs" />
<Compile Include="Timers\TaskSeriesCommandResult.cs" />
<Compile Include="TraceEvent.cs" />
<Compile Include="TraceSource.cs" />
<Compile Include="TraceWriter.cs" />
<Compile Include="Triggers\ITriggerDataArgumentBinding.cs" />
Expand Down Expand Up @@ -860,6 +861,7 @@
<Compile Include="Triggers\TriggerData.cs" />
<Compile Include="Indexers\DefaultNameResolver.cs" />
<Compile Include="NameResolverExtensions.cs" />
<Compile Include="FunctionInvocationException.cs" />
<Compile Include="WebJobsShutdownWatcher.cs" />
<Compile Include="Tables\TableEntityPath.cs" />
<Compile Include="HostContainerNames.cs" />
Expand Down
Loading

0 comments on commit 53337ce

Please sign in to comment.