-
Notifications
You must be signed in to change notification settings - Fork 110
elm logger #47
Changes from all commits
f18b7a8
e971917
5cb468e
08d6dbf
0279c78
eee5c15
c82747d
f6ce304
08ba8f8
3de8a5f
c872ee9
9012ca3
36ac963
b690ff1
241eb93
56e3cbc
e4c1ad6
f4ba7c4
a5c902f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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; } | ||
| } | ||
| } |
| 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 | ||
| }; | ||
| } | ||
| } | ||
| } |
| 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 => { })); | ||
| } | ||
| } | ||
| } |
| 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), | ||
| 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(); | ||
| } | ||
| } | ||
| } | ||
| 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); | ||
| } | ||
| } | ||
| } |
| 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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Split into middleware options and view options.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ViewOptions?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah that's fair. |
||
| } | ||
| } | ||
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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()!