Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,11 @@ private Compilation GetScriptCompilation(Script<object> script, FunctionMetadata

if (_optimizationLevel == OptimizationLevel.Debug)
{
SyntaxTree scriptTree = compilation.SyntaxTrees.FirstOrDefault(t => string.IsNullOrEmpty(t.FilePath));
string scriptFileName = Path.GetFileName(functionMetadata.ScriptFile);
SyntaxTree scriptTree = compilation.SyntaxTrees.FirstOrDefault(t => string.Equals(t.FilePath, scriptFileName));
var debugTree = SyntaxFactory.SyntaxTree(scriptTree.GetRoot(),
encoding: UTF8WithNoBOM,
path: Path.GetFileName(functionMetadata.ScriptFile),
path: scriptFileName,
options: new CSharpParseOptions(kind: SourceCodeKind.Script));

compilation = compilation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,7 @@ internal DotNetFunctionInvoker(ScriptHost host,

private static IFunctionMetadataResolver CreateMetadataResolver(ScriptHost host, FunctionMetadata functionMetadata, TraceWriter traceWriter)
{
string functionScriptDirectory = Path.GetDirectoryName(functionMetadata.ScriptFile);
return new FunctionMetadataResolver(functionScriptDirectory, host.ScriptConfig.BindingProviders,
return new FunctionMetadataResolver(functionMetadata.ScriptFile, host.ScriptConfig.BindingProviders,
traceWriter, host.ScriptConfig.HostConfig.LoggerFactory);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public sealed class FunctionMetadataResolver : MetadataReferenceResolver, IFunct
{
private readonly string _privateAssembliesPath;
private readonly string _scriptFileDirectory;
private readonly string _scriptFilePath;
private readonly string[] _assemblyExtensions = new[] { ".exe", ".dll" };
private readonly string _id = Guid.NewGuid().ToString();
private readonly TraceWriter _traceWriter;
Expand Down Expand Up @@ -75,12 +76,13 @@ public sealed class FunctionMetadataResolver : MetadataReferenceResolver, IFunct
"Microsoft.Extensions.Logging"
};

public FunctionMetadataResolver(string scriptFileDirectory, ICollection<ScriptBindingProvider> bindingProviders, TraceWriter traceWriter, ILoggerFactory loggerFactory)
public FunctionMetadataResolver(string scriptFilePath, ICollection<ScriptBindingProvider> bindingProviders, TraceWriter traceWriter, ILoggerFactory loggerFactory)
{
_scriptFileDirectory = scriptFileDirectory;
_scriptFileDirectory = Path.GetDirectoryName(scriptFilePath);
_scriptFilePath = scriptFilePath;
_traceWriter = traceWriter;
_packageAssemblyResolver = new PackageAssemblyResolver(scriptFileDirectory);
_privateAssembliesPath = GetBinDirectory(scriptFileDirectory);
_packageAssemblyResolver = new PackageAssemblyResolver(_scriptFileDirectory);
_privateAssembliesPath = GetBinDirectory(_scriptFileDirectory);
_scriptResolver = ScriptMetadataResolver.Default.WithSearchPaths(_privateAssembliesPath);
_extensionSharedAssemblyProvider = new ExtensionSharedAssemblyProvider(bindingProviders);
_loggerFactory = loggerFactory;
Expand All @@ -91,10 +93,11 @@ public ScriptOptions CreateScriptOptions()
_externalReferences.Clear();

return ScriptOptions.Default
.WithMetadataResolver(this)
.WithReferences(GetCompilationReferences())
.WithImports(DefaultNamespaceImports)
.WithSourceResolver(new SourceFileResolver(ImmutableArray<string>.Empty, _scriptFileDirectory));
.WithFilePath(Path.GetFileName(_scriptFilePath))
.WithMetadataResolver(this)
.WithReferences(GetCompilationReferences())
.WithImports(DefaultNamespaceImports)
.WithSourceResolver(new SourceFileResolver(ImmutableArray<string>.Empty, _scriptFileDirectory));
}

/// <summary>
Expand Down
26 changes: 26 additions & 0 deletions src/WebJobs.Script/Description/FunctionInvokerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,32 @@ internal void TraceCompilationDiagnostics(ImmutableArray<Diagnostic> diagnostics
{
traceWriter.Trace(diagnostic.ToString(), diagnostic.Severity.ToTraceLevel(), properties);
}

if (Host.InDebugMode && Host.IsPrimary)
{
Host.EventManager.Publish(new StructuredLogEntryEvent(() =>
{
var logEntry = new StructuredLogEntry("codediagnostic");
logEntry.AddProperty("functionName", Metadata.Name);
logEntry.AddProperty("diagnostics", diagnostics.Select(d =>
{
FileLinePositionSpan span = d.Location.GetMappedLineSpan();
return new
{
code = d.Id,
message = d.GetMessage(),
source = Path.GetFileName(d.Location.SourceTree?.FilePath ?? span.Path ?? string.Empty),
severity = d.Severity,
startLineNumber = span.StartLinePosition.Line + 1,
startColumn = span.StartLinePosition.Character + 1,
endLine = span.EndLinePosition.Line + 1,
endColumn = span.EndLinePosition.Character + 1,
};
}));

return logEntry;
}));
}
}

protected virtual void Dispose(bool disposing)
Expand Down
10 changes: 8 additions & 2 deletions src/WebJobs.Script/Diagnostics/FileTraceWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public class FileTraceWriter : TraceWriter, IDisposable
internal const int MaxLogLinesPerFlushInterval = 250;
private readonly string _logFilePath;
private readonly string _instanceId;
private readonly Func<string, string> _messageFormatter;

private readonly DirectoryInfo _logDirectory;
private static object _syncLock = new object();
Expand All @@ -32,10 +33,11 @@ public class FileTraceWriter : TraceWriter, IDisposable
private Timer _flushTimer;
private ConcurrentQueue<string> _logBuffer = new ConcurrentQueue<string>();

public FileTraceWriter(string logFilePath, TraceLevel level) : base(level)
public FileTraceWriter(string logFilePath, TraceLevel level, Func<string, string> messageFormatter = null) : base(level)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FileTraceWriter is a public type and we just added an optional param to the constructor.. do we care about this runtime breaking change? I know script packages are still not RTM so from a semantic versioning perspective we're OK, but just wanted to double check anyway...

{
_logFilePath = logFilePath;
_instanceId = GetInstanceId();
_messageFormatter = messageFormatter ?? FormatMessage;

_logDirectory = new DirectoryInfo(logFilePath);
if (!_logDirectory.Exists)
Expand Down Expand Up @@ -209,10 +211,14 @@ protected virtual void AppendLine(string line)

// add the line to the current buffer batch, which is flushed
// on a timer
line = string.Format(CultureInfo.InvariantCulture, "{0} {1}", DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fff", CultureInfo.InvariantCulture), line.Trim());
line = _messageFormatter(line);

_logBuffer.Enqueue(line);
}

private string FormatMessage(string message)
=> string.Format(CultureInfo.InvariantCulture, "{0} {1}", DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fff", CultureInfo.InvariantCulture), message.Trim());

private void OnFlushLogs(object sender, ElapsedEventArgs e)
{
Flush();
Expand Down
56 changes: 56 additions & 0 deletions src/WebJobs.Script/Diagnostics/StructuredLogWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// 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.IO;
using System.Linq;
using System.Reactive.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs.Script.Eventing;

namespace Microsoft.Azure.WebJobs.Script.Diagnostics
{
public sealed class StructuredLogWriter : IDisposable
{
private readonly IDisposable _subscription;
private readonly FileTraceWriter _traceWriter;
private bool _disposedValue = false;

public StructuredLogWriter(IScriptEventManager eventManager, string baseLogPath)
{
string logPath = Path.Combine(baseLogPath, "structured");
_traceWriter = new FileTraceWriter(logPath, TraceLevel.Verbose, s => s);

_subscription = eventManager.OfType<StructuredLogEntryEvent>()
.Subscribe(OnLogEntry);
}

private void OnLogEntry(StructuredLogEntryEvent logEvent)
{
string message = logEvent.LogEntry.ToJsonLineString();
_traceWriter.Trace(message, TraceLevel.Verbose, null);
}

private void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
_subscription?.Dispose();
_traceWriter?.Dispose();
}

_disposedValue = true;
}
}

public void Dispose()
{
Dispose(true);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// 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.IO;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Microsoft.Azure.WebJobs.Script.Eventing
{
public sealed class StructuredLogEntry
{
private readonly Dictionary<string, object> _properties = new Dictionary<string, object>();

public StructuredLogEntry(string name)
: this(Guid.NewGuid(), name)
{
}

public StructuredLogEntry(Guid id, string name)
{
Id = id;
Name = name;
_properties = new Dictionary<string, object>();
}

/// <summary>
/// Gets the event ID. This uniquely identifies this <see cref="StructuredLogEntry"/> instance.
/// </summary
public Guid Id { get; }

/// <summary>
/// Gets the event name. This identifies the type of event represented by the <see cref="StructuredLogEntry"/> instance.
/// </summary>
public string Name { get; }

/// <summary>
/// Adds a log entry property.
/// </summary>
/// <param name="name">The property name.</param>
/// <param name="value">The property value.</param>
public void AddProperty(string name, object value)
{
if (string.Equals(nameof(Name), name, StringComparison.OrdinalIgnoreCase) ||
string.Equals(nameof(Id), name, StringComparison.OrdinalIgnoreCase))
{
throw new ArgumentException($"{name} is an invalid property name.", nameof(name));
}

_properties.Add(name, value);
}

/// <summary>
/// Returns a JSON string representation of this object in a single line.
/// </summary>
/// <returns>A JSON string representation of this object in a single line.</returns>
public string ToJsonLineString()
{
var resultObject = new JObject
{
["name"] = Name,
["id"] = Id
};
foreach (var item in _properties)
{
resultObject.Add(item.Key, JToken.FromObject(item.Value));
}

return resultObject.ToString(Formatting.None);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// 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.Threading;

namespace Microsoft.Azure.WebJobs.Script.Eventing
{
public sealed class StructuredLogEntryEvent : ScriptEvent
{
private readonly Lazy<StructuredLogEntry> _logEntry;

public StructuredLogEntryEvent(StructuredLogEntry logEntry)
: this(logEntry, string.Empty)
{
}

public StructuredLogEntryEvent(StructuredLogEntry logEntry, string source)
: this(() => logEntry, source)
{
}

public StructuredLogEntryEvent(Func<StructuredLogEntry> logEntryFactory)
: this(logEntryFactory, string.Empty)
{
}

public StructuredLogEntryEvent(Func<StructuredLogEntry> logEntryFactory, string source)
: base(nameof(StructuredLogEntryEvent), source)
{
_logEntry = new Lazy<StructuredLogEntry>(logEntryFactory, LazyThreadSafetyMode.ExecutionAndPublication);
}

public StructuredLogEntry LogEntry => _logEntry.Value;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.Azure.WebJobs.Script.Eventing;
using Microsoft.CodeAnalysis;

namespace Microsoft.Azure.WebJobs.Script.Description.DotNet.CSharp.Analyzers
namespace Microsoft.Azure.WebJobs.Script.Description
{
internal static class DiagnosticExtensions
{
Expand Down
5 changes: 5 additions & 0 deletions src/WebJobs.Script/GlobalSuppressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -207,3 +207,8 @@
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sku", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.ScriptConstants.#DynamicSkuConnectionLimit")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Reflection.Assembly.LoadFrom", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.ScriptHost.#AddDirectTypes(System.Collections.Generic.List`1<System.Type>,System.Collections.ObjectModel.Collection`1<Microsoft.Azure.WebJobs.Script.Description.FunctionDescriptor>)")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Reflection.Assembly.LoadFrom", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.ScriptHost.#LoadCustomExtensions(System.String)")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.DefaultLoggerFactoryBuilder.#AddLoggerProviders(Microsoft.Extensions.Logging.ILoggerFactory,Microsoft.Azure.WebJobs.Script.ScriptHostConfiguration,Microsoft.Azure.WebJobs.Script.Config.ScriptSettingsManager)")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.Eventing.StructuredLogging.StructuredLogEntry.#ToJson()")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.Eventing.StructuredLogging.StructuredLogEntry.#ToJson()")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "_traceWriter", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.Diagnostics.StructuredLogWriter.#Dispose(System.Boolean)")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.FileTraceWriter.#.ctor(System.String,System.Diagnostics.TraceLevel,System.Func`2<System.String,System.String>)")]
4 changes: 4 additions & 0 deletions src/WebJobs.Script/Host/ScriptHostManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public class ScriptHostManager : IScriptHostEnvironment, IDisposable
private readonly IScriptHostFactory _scriptHostFactory;
private readonly IScriptHostEnvironment _environment;
private readonly IDisposable _fileEventSubscription;
private readonly StructuredLogWriter _structuredLogWriter;
private ScriptHost _currentInstance;

// ScriptHosts are not thread safe, so be clear that only 1 thread at a time operates on each instance.
Expand Down Expand Up @@ -72,6 +73,8 @@ public ScriptHostManager(ScriptHostConfiguration config,
_scriptHostFactory = scriptHostFactory;

EventManager = eventManager ?? new ScriptEventManager();

_structuredLogWriter = new StructuredLogWriter(eventManager, config.RootLogPath);
}

protected IScriptEventManager EventManager { get; }
Expand Down Expand Up @@ -376,6 +379,7 @@ protected virtual void Dispose(bool disposing)
_stopEvent.Dispose();
_restartDelayTokenSource?.Dispose();
_fileEventSubscription?.Dispose();
_structuredLogWriter.Dispose();
_restartHostEvent.Dispose();

_disposed = true;
Expand Down
9 changes: 7 additions & 2 deletions src/WebJobs.Script/WebJobs.Script.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,6 @@
<Compile Include="Description\DiagnosticSeverityExtensions.cs" />
<Compile Include="Description\DotNet\Compilation\CSharp\Analyzers\AsyncVoidAnalyzer.cs" />
<Compile Include="Description\DotNet\Compilation\IDotNetCompilation.cs" />
<Compile Include="Description\DotNet\DiagnosticExtensions.cs" />
<Compile Include="Description\DotNet\Compilation\CSharp\Analyzers\InvalidFileMetadataReferenceAnalyzer.cs" />
<Compile Include="Description\DotNet\Compilation\CSharp\CSharpCompilation.cs" />
<Compile Include="Description\DotNet\Compilation\CSharp\CSharpCompilationService.cs" />
Expand Down Expand Up @@ -496,6 +495,7 @@
<Compile Include="Diagnostics\MetricsLogger.cs" />
<Compile Include="Diagnostics\HostStartedEvent.cs" />
<Compile Include="Diagnostics\DictionaryLoggerScope.cs" />
<Compile Include="Diagnostics\StructuredLogWriter.cs" />
<Compile Include="EnvironmentSettingNames.cs" />
<Compile Include="ErrorCodes.cs" />
<Compile Include="Eventing\File\FileEvent.cs" />
Expand All @@ -504,7 +504,10 @@
<Compile Include="Eventing\ScriptEvent.cs" />
<Compile Include="Eventing\IScriptEventManager.cs" />
<Compile Include="Eventing\ScriptEventManager.cs" />
<Compile Include="Eventing\StructuredLogging\StructuredLogEntry.cs" />
<Compile Include="Eventing\StructuredLogging\StructuredLogEntryEvent.cs" />
<Compile Include="Extensions\AssemblyExtensions.cs" />
<Compile Include="Extensions\DiagnosticExtensions.cs" />
<Compile Include="Extensions\ExceptionExtensions.cs" />
<Compile Include="Extensions\FileUtility.cs" />
<Compile Include="Extensions\FuncExtensions.cs" />
Expand Down Expand Up @@ -631,7 +634,9 @@
<Analyzer Include="..\..\packages\StyleCop.Analyzers.1.1.0-beta001\analyzers\dotnet\cs\StyleCop.Analyzers.CodeFixes.dll" />
<Analyzer Include="..\..\packages\StyleCop.Analyzers.1.1.0-beta001\analyzers\dotnet\cs\StyleCop.Analyzers.dll" />
</ItemGroup>
<ItemGroup />
<ItemGroup>
<Folder Include="Eventing\CodeDiagnostics\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- <Import Project="..\..\packages\StyleCop.MSBuild.4.7.50.0\build\StyleCop.MSBuild.Targets" Condition="Exists('..\..\packages\StyleCop.MSBuild.4.7.50.0\build\StyleCop.MSBuild.Targets')" /> -->
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
Expand Down
Loading