Skip to content
This repository was archived by the owner on Dec 8, 2018. It is now read-only.
Closed
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
15 changes: 15 additions & 0 deletions DiagnosticsPages.sln
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Diagnostic
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ErrorHandlerSample", "samples\ErrorHandlerSample\ErrorHandlerSample.kproj", "{427CDB36-78B0-4583-9EBC-7F283DE60355}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Diagnostics.Elm", "src\Microsoft.AspNet.Diagnostics.Elm\Microsoft.AspNet.Diagnostics.Elm.kproj", "{624B0019-956A-4157-B008-270C5B229553}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -108,6 +110,18 @@ Global
{427CDB36-78B0-4583-9EBC-7F283DE60355}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{427CDB36-78B0-4583-9EBC-7F283DE60355}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{427CDB36-78B0-4583-9EBC-7F283DE60355}.Release|x86.ActiveCfg = Release|Any CPU
{624B0019-956A-4157-B008-270C5B229553}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{624B0019-956A-4157-B008-270C5B229553}.Debug|Any CPU.Build.0 = Debug|Any CPU
{624B0019-956A-4157-B008-270C5B229553}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{624B0019-956A-4157-B008-270C5B229553}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{624B0019-956A-4157-B008-270C5B229553}.Debug|x86.ActiveCfg = Debug|Any CPU
{624B0019-956A-4157-B008-270C5B229553}.Debug|x86.Build.0 = Debug|Any CPU
{624B0019-956A-4157-B008-270C5B229553}.Release|Any CPU.ActiveCfg = Release|Any CPU
{624B0019-956A-4157-B008-270C5B229553}.Release|Any CPU.Build.0 = Release|Any CPU
{624B0019-956A-4157-B008-270C5B229553}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{624B0019-956A-4157-B008-270C5B229553}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{624B0019-956A-4157-B008-270C5B229553}.Release|x86.ActiveCfg = Release|Any CPU
{624B0019-956A-4157-B008-270C5B229553}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -120,5 +134,6 @@ Global
{CD62A191-39F5-4C86-BC1D-7731085120F5} = {ACAA0157-A8C4-4152-93DE-90CCDF304087}
{994351B4-7B2A-4139-8B72-72C5BB5CC618} = {2AF90579-B118-4583-AE88-672EFACB5BC4}
{427CDB36-78B0-4583-9EBC-7F283DE60355} = {ACAA0157-A8C4-4152-93DE-90CCDF304087}
{624B0019-956A-4157-B008-270C5B229553} = {509A6F36-AD80-4A18-B5B1-717D38DFF29D}
EndGlobalSection
EndGlobal
18 changes: 18 additions & 0 deletions src/Microsoft.AspNet.Diagnostics.Elm/ActivityContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;

namespace Microsoft.AspNet.Diagnostics.Elm
{
public class ActivityContext
{
public Guid Id { get; set; }

public HttpInfo HttpInfo { get; set; }

public ScopeNode Root { get; set; }

public DateTimeOffset Time { get; set; }

public bool IsCollapsed { get; set; }
}
}
70 changes: 70 additions & 0 deletions src/Microsoft.AspNet.Diagnostics.Elm/ElmCaptureMiddleware.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Threading.Tasks;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Http;
using Microsoft.Framework.Logging;
using Microsoft.Framework.OptionsModel;

namespace Microsoft.AspNet.Diagnostics.Elm
{
/// <summary>
/// Enables the Elm logging service.
/// </summary>
public class ElmCaptureMiddleware
{
private readonly RequestDelegate _next;
private readonly ElmOptions _options;
private readonly ILogger _logger;

public ElmCaptureMiddleware(RequestDelegate next, ILoggerFactory factory, IOptions<ElmOptions> options)
{
_next = next;
_options = options.Options;
_logger = factory.Create<ElmCaptureMiddleware>();
}

public async Task Invoke(HttpContext context)
{
var requestId = Guid.NewGuid();
using (_logger.BeginScope(string.Format("request {0}", requestId)))
{
var p = ElmScope.Current;
ElmScope.Current.Context.HttpInfo = GetHttpInfo(context, requestId);
try
{
await _next(context);
}
finally
{
ElmScope.Current.Context.HttpInfo.StatusCode = context.Response.StatusCode;
}
}
}

/// <summary>
/// Takes the info from the given HttpContext and copies it to an HttpInfo object
/// </summary>
/// <returns>The HttpInfo for the current elm context</returns>
private static HttpInfo GetHttpInfo(HttpContext context, Guid requestId)
{
return new HttpInfo()
{
RequestID = requestId,
Host = context.Request.Host,
ContentType = context.Request.ContentType,
Path = context.Request.Path,
Scheme = context.Request.Scheme,
StatusCode = context.Response.StatusCode,
User = context.User,
Method = context.Request.Method,
Protocol = context.Request.Protocol,
Headers = context.Request.Headers,
Query = context.Request.QueryString,
Cookies = context.Request.Cookies
};
}
}
}
46 changes: 46 additions & 0 deletions src/Microsoft.AspNet.Diagnostics.Elm/ElmExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using Jetbrains.Annotations;
using Microsoft.AspNet.Diagnostics.Elm;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Logging;
using Microsoft.Framework.OptionsModel;

namespace Microsoft.AspNet.Builder
{
public static class ElmExtensions
{
/// <summary>
/// Enables the Elm logging service, which can be accessed via the <see cref="ElmPageMiddleware"/>.
/// </summary>
public static IApplicationBuilder UseElmCapture([NotNull] this IApplicationBuilder builder)
{
// add the elm provider to the factory here so the logger can start capturing logs immediately
var factory = builder.ApplicationServices.GetRequiredService<ILoggerFactory>();
var store = builder.ApplicationServices.GetRequiredService<ElmStore>();
var options = builder.ApplicationServices.GetService<IOptions<ElmOptions>>();
factory.AddProvider(new ElmLoggerProvider(store, options?.Options ?? new ElmOptions()));

return builder.UseMiddleware<ElmCaptureMiddleware>();
}

/// <summary>
/// Enables viewing logs captured by the <see cref="ElmCaptureMiddleware"/>.
/// </summary>
public static IApplicationBuilder UseElmPage([NotNull] this IApplicationBuilder builder)
{
return builder.UseMiddleware<ElmPageMiddleware>();
}

/// <summary>
/// Registers an <see cref="ElmStore"/> and configures <see cref="ElmOptions"/>.
/// </summary>
public static IServiceCollection AddElm([NotNull] this IServiceCollection services, Action<ElmOptions> configureOptions = null)
{
services.AddSingleton<ElmStore>(); // registering the service so it can be injected into constructors
return services.Configure(configureOptions ?? (o => { }));
}
}
}
81 changes: 81 additions & 0 deletions src/Microsoft.AspNet.Diagnostics.Elm/ElmLogger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using Microsoft.Framework.Logging;

namespace Microsoft.AspNet.Diagnostics.Elm
{
public class ElmLogger : ILogger
{
private readonly string _name;
private readonly ElmOptions _options;
private readonly ElmStore _store;

public ElmLogger(string name, ElmOptions options, ElmStore store)
{
_name = name;
_options = options;
_store = store;
}

public void Write(LogLevel logLevel, int eventId, object state, Exception exception,
Func<object, Exception, string> formatter)
{
if (!IsEnabled(logLevel) || (state == null && exception == null))
{
return;
}
LogInfo info = new LogInfo()
{
ActivityContext = GetCurrentActivityContext(),
Name = _name,
EventID = eventId,
Severity = logLevel,
Exception = exception,
State = state,
Message = formatter == null ? state.ToString() : formatter(state, exception),
Copy link
Member

Choose a reason for hiding this comment

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

Don't do this but I wonder if C# 6 magic would look like /cc @Eilon

formatter?.Invoke(state, exception) ?? state.ToString()

Copy link
Contributor

Choose a reason for hiding this comment

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

That's so awful 😄 Calling delegates directly is so much nicer than using .Invoke()!

Time = DateTimeOffset.UtcNow
};
if (ElmScope.Current != null)
{
ElmScope.Current.Node.Messages.Add(info);
}
// The log does not belong to any scope - create a new context for it
else
{
var context = GetNewActivityContext();
context.Id = Guid.Empty; // mark as a non-scope log
context.Root = new ScopeNode();
context.Root.Messages.Add(info);
_store.AddActivity(context);
}
}

public bool IsEnabled(LogLevel logLevel)
{
return _options.Filter(_name, logLevel);
}

public IDisposable BeginScope(object state)
{
var scope = new ElmScope(_name, state);
scope.Context = ElmScope.Current?.Context ?? GetNewActivityContext();
return ElmScope.Push(scope, _store);
}

private ActivityContext GetNewActivityContext()
{
return new ActivityContext()
{
Id = Guid.NewGuid(),
Time = DateTimeOffset.UtcNow
};
}

private ActivityContext GetCurrentActivityContext()
{
return ElmScope.Current?.Context ?? GetNewActivityContext();
}
}
}
25 changes: 25 additions & 0 deletions src/Microsoft.AspNet.Diagnostics.Elm/ElmLoggerProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using Jetbrains.Annotations;
using Microsoft.Framework.Logging;

namespace Microsoft.AspNet.Diagnostics.Elm
{
public class ElmLoggerProvider : ILoggerProvider
{
private readonly ElmStore _store;
private readonly ElmOptions _options;

public ElmLoggerProvider([NotNull] ElmStore store, [NotNull] ElmOptions options)
{
_store = store;
_options = options;
}

public ILogger Create(string name)
{
return new ElmLogger(name, _options, _store);
}
}
}
26 changes: 26 additions & 0 deletions src/Microsoft.AspNet.Diagnostics.Elm/ElmOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using Microsoft.AspNet.Http;
using Microsoft.Framework.Logging;

namespace Microsoft.AspNet.Diagnostics.Elm
{
/// <summary>
/// Options for ElmMiddleware
/// </summary>
public class ElmOptions
Copy link
Member

Choose a reason for hiding this comment

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

Split into middleware options and view options.

Copy link
Member

Choose a reason for hiding this comment

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

ViewOptions?

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, there are middleware options (ElmOptions.Path) and then there are per-request view options taken from the query string (MinLevel, NamePrefix). They're never used together, so they should be on separate types.

{
/// <summary>
/// Specifies the path to view the logs.
/// </summary>
public PathString Path { get; set; } = new PathString("/Elm");

/// <summary>
/// Determines whether log statements should be logged based on the name of the logger
/// and the <see cref="LogLevel"/> of the message.
/// </summary>
public Func<string, LogLevel, bool> Filter { get; set; } = (name, level) => true;
Copy link
Contributor

Choose a reason for hiding this comment

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

I forget - did we want the default to be to log everything, or should it be > Diagnostics or whatever? Might want this to be consistent with the ConsoleLogger?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I had intended the default to log everything, because of the ability to filter this in the view.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah that's fair.

}
}
Loading