diff --git a/Logging.sln b/Logging.sln index 4df337f5..21f95227 100644 --- a/Logging.sln +++ b/Logging.sln @@ -19,6 +19,12 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "SampleApp", "samples\Sample EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Framework.Logging.Console", "src\Microsoft.Framework.Logging.Console\Microsoft.Framework.Logging.Console.kproj", "{75A4DE6D-BBAA-4D59-829D-94009E759A18}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Logging.Elm", "src\Microsoft.AspNet.Logging.Elm\Microsoft.AspNet.Logging.Elm.kproj", "{889A6055-2498-4AD0-A866-3B1638257D8B}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ElmSampleApp", "samples\ElmSampleApp\ElmSampleApp.kproj", "{55FB3329-DC96-461C-8E80-B409DF4B494C}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "PageGenerator", "src\PageGenerator\PageGenerator.kproj", "{4D4A785A-ECB9-4916-A88F-0FD306EE3B74}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -89,6 +95,36 @@ Global {75A4DE6D-BBAA-4D59-829D-94009E759A18}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {75A4DE6D-BBAA-4D59-829D-94009E759A18}.Release|Mixed Platforms.Build.0 = Release|Any CPU {75A4DE6D-BBAA-4D59-829D-94009E759A18}.Release|x86.ActiveCfg = Release|Any CPU + {889A6055-2498-4AD0-A866-3B1638257D8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {889A6055-2498-4AD0-A866-3B1638257D8B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {889A6055-2498-4AD0-A866-3B1638257D8B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {889A6055-2498-4AD0-A866-3B1638257D8B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {889A6055-2498-4AD0-A866-3B1638257D8B}.Debug|x86.ActiveCfg = Debug|Any CPU + {889A6055-2498-4AD0-A866-3B1638257D8B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {889A6055-2498-4AD0-A866-3B1638257D8B}.Release|Any CPU.Build.0 = Release|Any CPU + {889A6055-2498-4AD0-A866-3B1638257D8B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {889A6055-2498-4AD0-A866-3B1638257D8B}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {889A6055-2498-4AD0-A866-3B1638257D8B}.Release|x86.ActiveCfg = Release|Any CPU + {55FB3329-DC96-461C-8E80-B409DF4B494C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {55FB3329-DC96-461C-8E80-B409DF4B494C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {55FB3329-DC96-461C-8E80-B409DF4B494C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {55FB3329-DC96-461C-8E80-B409DF4B494C}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {55FB3329-DC96-461C-8E80-B409DF4B494C}.Debug|x86.ActiveCfg = Debug|Any CPU + {55FB3329-DC96-461C-8E80-B409DF4B494C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {55FB3329-DC96-461C-8E80-B409DF4B494C}.Release|Any CPU.Build.0 = Release|Any CPU + {55FB3329-DC96-461C-8E80-B409DF4B494C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {55FB3329-DC96-461C-8E80-B409DF4B494C}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {55FB3329-DC96-461C-8E80-B409DF4B494C}.Release|x86.ActiveCfg = Release|Any CPU + {4D4A785A-ECB9-4916-A88F-0FD306EE3B74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4D4A785A-ECB9-4916-A88F-0FD306EE3B74}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4D4A785A-ECB9-4916-A88F-0FD306EE3B74}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {4D4A785A-ECB9-4916-A88F-0FD306EE3B74}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {4D4A785A-ECB9-4916-A88F-0FD306EE3B74}.Debug|x86.ActiveCfg = Debug|Any CPU + {4D4A785A-ECB9-4916-A88F-0FD306EE3B74}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4D4A785A-ECB9-4916-A88F-0FD306EE3B74}.Release|Any CPU.Build.0 = Release|Any CPU + {4D4A785A-ECB9-4916-A88F-0FD306EE3B74}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {4D4A785A-ECB9-4916-A88F-0FD306EE3B74}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {4D4A785A-ECB9-4916-A88F-0FD306EE3B74}.Release|x86.ActiveCfg = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -96,5 +132,6 @@ Global GlobalSection(NestedProjects) = preSolution {96B1D6A8-7E40-43C7-813F-898DC8192DDE} = {09920C51-6220-4D8D-94DC-E70C13446187} {550E0247-0BDD-4016-A29B-250F075686FD} = {8C1F5D80-88EA-4961-84DC-7AC6E13951F4} + {55FB3329-DC96-461C-8E80-B409DF4B494C} = {8C1F5D80-88EA-4961-84DC-7AC6E13951F4} EndGlobalSection EndGlobal diff --git a/samples/ElmSampleApp/ElmSampleApp.kproj b/samples/ElmSampleApp/ElmSampleApp.kproj new file mode 100644 index 00000000..d54ac0e9 --- /dev/null +++ b/samples/ElmSampleApp/ElmSampleApp.kproj @@ -0,0 +1,18 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 55fb3329-dc96-461c-8e80-b409df4b494c + Web + WebApplication2 + + + 2.0 + 42601 + + + \ No newline at end of file diff --git a/samples/ElmSampleApp/Startup.cs b/samples/ElmSampleApp/Startup.cs new file mode 100644 index 00000000..36ce8abc --- /dev/null +++ b/samples/ElmSampleApp/Startup.cs @@ -0,0 +1,43 @@ +using System; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.Logging.Elm; +using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.Logging; + +namespace ElmSampleApp +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + services.AddSingleton(); // registering the service so it can be injected into constructors + services.Configure(options => + { + options.Path = new PathString("/foo"); + }); + services.AddMvc(); + } + + public void Configure(IApplicationBuilder app, ILoggerFactory factory) + { + app.UseErrorPage(); + app.UseElm(); + app.UseMvc(); +#pragma warning disable CS1998 + app.Run(async context => +#pragma warning restore CS1998 + { + throw new InvalidOperationException(); + }); + } + } + + public class HomeController + { + public string Index() + { + return "Hello World"; + } + } +} diff --git a/samples/ElmSampleApp/project.json b/samples/ElmSampleApp/project.json new file mode 100644 index 00000000..c2cf91d1 --- /dev/null +++ b/samples/ElmSampleApp/project.json @@ -0,0 +1,16 @@ +{ + "webroot": "wwwroot", + "exclude": "wwwroot/**/*.*", + "dependencies": { + "Microsoft.AspNet.Diagnostics": "1.0.0-*", + "Microsoft.AspNet.Http": "1.0.0-*", + "Microsoft.AspNet.Logging.Elm": "1.0.0-*", + "Microsoft.AspNet.Mvc": "6.0.0-*", + "Microsoft.AspNet.PipelineCore": "1.0.0-*", + "Microsoft.AspNet.Server.IIS": "1.0.0-*" + }, + "frameworks" : { + "aspnet50" : { }, + "aspnetcore50" : { } + } +} diff --git a/src/Microsoft.AspNet.Logging.Elm/ElmExtensions.cs b/src/Microsoft.AspNet.Logging.Elm/ElmExtensions.cs new file mode 100644 index 00000000..e01ac09f --- /dev/null +++ b/src/Microsoft.AspNet.Logging.Elm/ElmExtensions.cs @@ -0,0 +1,15 @@ +// 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 Microsoft.AspNet.Logging.Elm; + +namespace Microsoft.AspNet.Builder +{ + public static class ElmExtensions + { + public static IApplicationBuilder UseElm(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Logging.Elm/ElmLogger.cs b/src/Microsoft.AspNet.Logging.Elm/ElmLogger.cs new file mode 100644 index 00000000..dabfa3de --- /dev/null +++ b/src/Microsoft.AspNet.Logging.Elm/ElmLogger.cs @@ -0,0 +1,107 @@ +// 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.Collections.Generic; +using System.Threading; +using Microsoft.AspNet.Http; +using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.Logging; +using Microsoft.Framework.Logging.Elm; + +namespace Microsoft.AspNet.Logging.Elm +{ + public class ElmLogger : ILogger + { + private readonly string _name; + private readonly ElmLoggerProvider _provider; + private IElmStore _store; + private readonly IContextAccessor _contextAccessor; + private readonly object _requestIdentifierKey; + private readonly object _logContextKey; + + public ElmLogger(string name, ElmLoggerProvider provider, IElmStore store, + IContextAccessor contextAccessor, + object requestIdentifierKey, object logContextKey) + { + _name = name; + _provider = provider; + _store = store; + _contextAccessor = contextAccessor; + _requestIdentifierKey = requestIdentifierKey; + _logContextKey = logContextKey; + } + + public void Write(TraceType traceType, int eventId, object state, Exception exception, + Func formatter) + { + LogInfo info = new LogInfo() + { + Context = GetLogContext(), + Name = _name, + EventID = eventId, + Severity = traceType, + Exception = exception, + State = state, + Time = DateTime.Now + }; + if (ElmScope.Counts.ContainsKey(GetLogContext().RequestID)) + { + // TODO: display nested scopes nicely + for (var i = 0; i < ElmScope.Counts[GetLogContext().RequestID].Count; i++) + { + state = "-----" + state; + } + info.State = state; + info.Scopes = new List(ElmScope.Counts[GetLogContext().RequestID]); + } + _store.Add(info); + } + + public bool IsEnabled(TraceType traceType) + { + return true; + } + + public IDisposable BeginScope(object state) + { + return new ElmScope(this, state, GetLogContext().RequestID); + } + + private LogContext GetLogContext() + { + var context = _contextAccessor.Value; + if (context == null) + { + // TODO: group non-request logs by Thread ID + return new LogContext() + { + ThreadID = Thread.CurrentThread.ManagedThreadId + }; + } + + var logContext = context.Items[_logContextKey] as LogContext; + if (logContext == null) + { + logContext = new LogContext() + { + RequestID = (Guid)context.Items[_requestIdentifierKey], + 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 + }; + context.Items[_logContextKey] = logContext; + } + + return logContext; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Logging.Elm/ElmLoggerProvider.cs b/src/Microsoft.AspNet.Logging.Elm/ElmLoggerProvider.cs new file mode 100644 index 00000000..f9cafebf --- /dev/null +++ b/src/Microsoft.AspNet.Logging.Elm/ElmLoggerProvider.cs @@ -0,0 +1,31 @@ +// 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 Microsoft.AspNet.Http; +using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.Logging; + +namespace Microsoft.AspNet.Logging.Elm +{ + public class ElmLoggerProvider : ILoggerProvider + { + private readonly IElmStore _store; + private readonly IContextAccessor _contextAccessor; + private readonly object _requestIdentifierKey; + private readonly object _logContextKey; + + public ElmLoggerProvider(IElmStore store, IContextAccessor contextAccessor, + object requestIdentifierKey, object logContextKey) + { + _store = store; + _contextAccessor = contextAccessor; + _requestIdentifierKey = requestIdentifierKey; + _logContextKey = logContextKey; + } + + public ILogger Create(string name) + { + return new ElmLogger(name, this, _store, _contextAccessor, _requestIdentifierKey, _logContextKey); + } + } +} diff --git a/src/Microsoft.AspNet.Logging.Elm/ElmMiddleware.cs b/src/Microsoft.AspNet.Logging.Elm/ElmMiddleware.cs new file mode 100644 index 00000000..dd9f88df --- /dev/null +++ b/src/Microsoft.AspNet.Logging.Elm/ElmMiddleware.cs @@ -0,0 +1,120 @@ +// 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.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.Logging.Elm.Views; +using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.Logging; +using Microsoft.Framework.OptionsModel; + +namespace Microsoft.AspNet.Logging.Elm +{ + /// + /// Enables the Elm logging service + /// + public class ElmMiddleware + { + private readonly RequestDelegate _next; + private readonly ElmOptions _options; + private readonly ElmLoggerProvider _provider; + private readonly IElmStore _store; + private readonly ILogger _logger; + private IContextAccessor _contextAccessor; + private static readonly object _requestIdentifierKey = new object(); + private static readonly object _logContextKey = new object(); + + public ElmMiddleware( + RequestDelegate next, ILoggerFactory factory, IOptions options, + IElmStore store, IContextAccessor contextAccessor) + { + _next = next; + _options = options.Options; + _store = store; + _logger = factory.Create(); + _contextAccessor = contextAccessor; + _provider = new ElmLoggerProvider(_store, contextAccessor, _requestIdentifierKey, _logContextKey); + factory.AddProvider(_provider); + // non-request logs + _logger.WriteWarning("hello world"); + _logger.WriteCritical("critical: aliens approaching"); + } + + public async Task Invoke(HttpContext context) + { + context.Items[_requestIdentifierKey] = Guid.NewGuid(); + if (context.Request.Path != _options.Path && !context.Request.Path.StartsWithSegments(_options.Path)) + { + try + { + await _next(context); + } + catch (Exception ex) + { + _contextAccessor.SetContextSource(() => context, null); + _logger.WriteError("An unhandled exception has occurred: " + ex.Message, ex); + throw; + } + } + + // parse params + var logs = (IEnumerable)null; + if (context.Request.Query.ContainsKey("level")) + { + var minLevel = (TraceType)int.Parse(context.Request.Query.GetValues("level")[0]); + logs = _store.GetLogs(minLevel); + _options.MinLevel = minLevel; + } + else + { + logs = _store.GetLogs(); + _options.MinLevel = TraceType.Verbose; + } + if (context.Request.Query.ContainsKey("name")) + { + var namePrefix = context.Request.Query.GetValues("name")[0]; + logs = logs.Where(l => l.Name.StartsWith(namePrefix)); + _options.NamePrefix = namePrefix; + } + + // main log page + if (context.Request.Path == _options.Path) + { + var model = new LogPageModel() + { + // sort so most recent logs are first + Logs = logs.OrderBy(l => l.Time).Reverse(), + Options = _options + }; + var logPage = new LogPage(model); + await logPage.ExecuteAsync(context); + } + // request details page + else + { + try + { + var parts = context.Request.Path.Value.Split('/'); + var id = Guid.Parse(parts[parts.Length - 1]); + var requestLogs = logs.Where(l => l.Context.RequestID == id); + var model = new RequestPageModel() + { + RequestID = id, + Logs = requestLogs, + Options = _options + }; + var requestPage = new RequestPage(model); + await requestPage.ExecuteAsync(context); + } + catch (Exception) + { + // TODO: bad url + } + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Logging.Elm/ElmOptions.cs b/src/Microsoft.AspNet.Logging.Elm/ElmOptions.cs new file mode 100644 index 00000000..c18c219b --- /dev/null +++ b/src/Microsoft.AspNet.Logging.Elm/ElmOptions.cs @@ -0,0 +1,34 @@ +// 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 Microsoft.AspNet.Http; +using Microsoft.Framework.Logging; + +namespace Microsoft.AspNet.Logging.Elm +{ + /// + /// Options for ElmMiddleware + /// + public class ElmOptions + { + public ElmOptions() + { + Path = new PathString("/Elm"); + } + + /// + /// Specifies the path to view the logs + /// + public PathString Path { get; set; } + + /// + /// The minimum severity level shown + /// + public TraceType MinLevel { get; set; } + + /// + /// prefix filter for the loggers shown + /// + public string NamePrefix { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Logging.Elm/ElmScope.cs b/src/Microsoft.AspNet.Logging.Elm/ElmScope.cs new file mode 100644 index 00000000..ff1a028b --- /dev/null +++ b/src/Microsoft.AspNet.Logging.Elm/ElmScope.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Microsoft.Framework.Logging; + +namespace Microsoft.Framework.Logging.Elm +{ + public class ElmScope : IDisposable + { + private bool _isDisposed; + private readonly Stopwatch _stopwatch; + private readonly ILogger _logger; + private readonly object _state; + private readonly Guid _request; + private readonly Guid _id; + + // Maps a request id to a list of Guids representing each scope within that request + public static IDictionary> Counts = new Dictionary>(); + private readonly object _lock = new object(); + + public ElmScope(ILogger logger, object state, Guid request) + { + _logger = logger; + _state = state; + _request = request; + _id = Guid.NewGuid(); + _stopwatch = Stopwatch.StartNew(); + lock (_lock) + { + if (!Counts.ContainsKey(_request)) + { + Counts.Add(_request, new List()); + } + Counts[_request].Add(_id); + } + _logger.WriteInformation(string.Format("Begin {0}", _state)); + } + + public void Dispose() + { + if (!_isDisposed) + { + _stopwatch.Stop(); + _logger.WriteInformation(string.Format("Completed {0} in {1}ms", _state, _stopwatch.ElapsedMilliseconds)); + lock (_lock) + { + Counts[_request].Remove(_id); + } + _isDisposed = true; + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Logging.Elm/ElmStore.cs b/src/Microsoft.AspNet.Logging.Elm/ElmStore.cs new file mode 100644 index 00000000..c9cd3cbd --- /dev/null +++ b/src/Microsoft.AspNet.Logging.Elm/ElmStore.cs @@ -0,0 +1,29 @@ +// 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.Collections.Generic; +using System.Linq; +using Microsoft.Framework.Logging; + +namespace Microsoft.AspNet.Logging.Elm +{ + public class ElmStore : IElmStore + { + private readonly List _logs = new List(); + + public IEnumerable GetLogs() + { + return _logs; + } + + public IEnumerable GetLogs(TraceType minLevel) + { + return _logs.Where(l => l.Severity >= minLevel); + } + + public void Add(LogInfo info) + { + _logs.Add(info); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Logging.Elm/IElmStore.cs b/src/Microsoft.AspNet.Logging.Elm/IElmStore.cs new file mode 100644 index 00000000..004305f1 --- /dev/null +++ b/src/Microsoft.AspNet.Logging.Elm/IElmStore.cs @@ -0,0 +1,15 @@ +// 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.Collections.Generic; +using Microsoft.Framework.Logging; + +namespace Microsoft.AspNet.Logging.Elm +{ + public interface IElmStore + { + void Add(LogInfo info); + IEnumerable GetLogs(); + IEnumerable GetLogs(TraceType minLevel); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Logging.Elm/LogContext.cs b/src/Microsoft.AspNet.Logging.Elm/LogContext.cs new file mode 100644 index 00000000..d1bd835d --- /dev/null +++ b/src/Microsoft.AspNet.Logging.Elm/LogContext.cs @@ -0,0 +1,38 @@ +// 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.Security.Claims; +using Microsoft.AspNet.Http; + +namespace Microsoft.AspNet.Logging.Elm +{ + public class LogContext + { + public int ThreadID { get; set; } + + public Guid RequestID { get; set; } + + public HostString Host { get; set; } + + public PathString Path { get; set; } + + public string ContentType { get; set; } + + public string Scheme { get; set; } + + public int StatusCode { get; set; } + + public ClaimsPrincipal User { get; set; } + + public string Method { get; set; } + + public string Protocol { get; set; } + + public IHeaderDictionary Headers { get; set; } + + public QueryString Query { get; set; } + + public IReadableStringCollection Cookies { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Logging.Elm/LogInfo.cs b/src/Microsoft.AspNet.Logging.Elm/LogInfo.cs new file mode 100644 index 00000000..fb34b0e5 --- /dev/null +++ b/src/Microsoft.AspNet.Logging.Elm/LogInfo.cs @@ -0,0 +1,28 @@ +// 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.Collections.Generic; +using Microsoft.Framework.Logging; + +namespace Microsoft.AspNet.Logging.Elm +{ + public class LogInfo + { + public LogContext Context { get; set; } + + public string Name { get; set; } + + public object State { get; set; } + + public Exception Exception { get; set; } + + public TraceType Severity { get; set; } + + public int EventID { get; set; } + + public DateTime Time { get; set; } + + public IList Scopes { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Logging.Elm/Microsoft.AspNet.Logging.Elm.kproj b/src/Microsoft.AspNet.Logging.Elm/Microsoft.AspNet.Logging.Elm.kproj new file mode 100644 index 00000000..e39fc101 --- /dev/null +++ b/src/Microsoft.AspNet.Logging.Elm/Microsoft.AspNet.Logging.Elm.kproj @@ -0,0 +1,19 @@ + + + + 12.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + 889a6055-2498-4ad0-a866-3b1638257d8b + Library + Microsoft.Framework.Logging.Elm + + + + 2.0 + + + \ No newline at end of file diff --git a/src/Microsoft.AspNet.Logging.Elm/Views/LogPage.cs b/src/Microsoft.AspNet.Logging.Elm/Views/LogPage.cs new file mode 100644 index 00000000..bc7a8114 --- /dev/null +++ b/src/Microsoft.AspNet.Logging.Elm/Views/LogPage.cs @@ -0,0 +1,440 @@ +namespace Microsoft.AspNet.Logging.Elm.Views +{ +#line 1 "LogPage.cshtml" +using System + +#line default +#line hidden + ; +#line 2 "LogPage.cshtml" +using System.Globalization + +#line default +#line hidden + ; +#line 3 "LogPage.cshtml" +using System.Linq + +#line default +#line hidden + ; +#line 4 "LogPage.cshtml" +using Microsoft.AspNet.Logging.Elm.Views + +#line default +#line hidden + ; +#line 5 "LogPage.cshtml" +using Microsoft.Framework.Logging + +#line default +#line hidden + ; + using System.Threading.Tasks; + + public class LogPage : Microsoft.AspNet.Diagnostics.Views.BaseView + { +#line 8 "LogPage.cshtml" + + public LogPage(LogPageModel model) + { + Model = model; + } + + public LogPageModel Model { get; set; } + +#line default +#line hidden + #line hidden + public LogPage() + { + } + + #pragma warning disable 1998 + public override async Task ExecuteAsync() + { + WriteLiteral("\r\n"); + WriteLiteral(@" + + + + ELM + + + + +

ELM

+
+ \r\n (Model.Options.NamePrefix, 1118), false)); + WriteLiteral(@"/> + +
+ + + + + + + + + + + + + + + + +"); +#line 62 "LogPage.cshtml" + + +#line default +#line hidden + +#line 62 "LogPage.cshtml" + foreach (var logs in Model.Logs.GroupBy(g => g.Context)) + { + +#line default +#line hidden + + WriteLiteral(" \r\n \r\n"); +#line 66 "LogPage.cshtml" + + +#line default +#line hidden + +#line 66 "LogPage.cshtml" + + var requestPath = Model.Options.Path.Value + "/" + logs.Key.RequestID; + + +#line default +#line hidden + + WriteLiteral("\r\n \r\n \r\n + \r\n \r\n \r\n"); +#line 109 "LogPage.cshtml" + } + +#line default +#line hidden + + WriteLiteral(@"
PathHostStatus CodeLogs
(requestPath, 1964), false)); + WriteAttribute("title", Tuple.Create(" title=\"", 1977), Tuple.Create("\"", 1999), + Tuple.Create(Tuple.Create("", 1985), Tuple.Create(logs.Key.Path, 1985), false)); + WriteLiteral(">"); +#line 69 "LogPage.cshtml" + Write(logs.Key.Path); + +#line default +#line hidden + WriteLiteral(""); +#line 70 "LogPage.cshtml" + Write(logs.Key.Host); + +#line default +#line hidden + WriteLiteral(""); +#line 71 "LogPage.cshtml" + Write(logs.Key.StatusCode); + +#line default +#line hidden + WriteLiteral(@" + + + + + + + + + + + + + +"); +#line 86 "LogPage.cshtml" + + +#line default +#line hidden + +#line 86 "LogPage.cshtml" + foreach (var log in logs.Reverse()) + { + var scopes = ""; + if (log.Scopes != null) + { + // classes cannot begin with a number, prepend an underscore + scopes = string.Join(" _", log.Scopes); + } + var logState = string.Format("{0} _{1}", "logState", scopes); + +#line default +#line hidden + + WriteLiteral(" \r\n " + +" \r\n \r\n (log.Name, 3776), false)); + WriteLiteral(">"); +#line 98 "LogPage.cshtml" + Write(log.Name); + +#line default +#line hidden + WriteLiteral("\r\n (log.Severity, 3854), false)); + WriteLiteral(">"); +#line 99 "LogPage.cshtml" + Write(log.Severity); + +#line default +#line hidden + WriteLiteral("\r\n (log.State, 3940), false)); + WriteAttribute("class", Tuple.Create(" class=\"", 3951), Tuple.Create("\"", 3968), + Tuple.Create(Tuple.Create("", 3959), Tuple.Create(logState, 3959), false)); + WriteLiteral(">"); +#line 100 "LogPage.cshtml" + Write(log.State); + +#line default +#line hidden + WriteLiteral("\r\n (log.Exception, 4038), false)); + WriteLiteral(">"); +#line 101 "LogPage.cshtml" + Write(log.Exception); + +#line default +#line hidden + WriteLiteral("\r\n \r\n"); +#line 103 "LogPage.cshtml" + } + +#line default +#line hidden + + WriteLiteral(" \r\n
DateTimeNameSeverityStateError
"); +#line 96 "LogPage.cshtml" + Write(string.Format("{0:MM/dd/yy}", log.Time)); + +#line default +#line hidden + WriteLiteral(""); +#line 97 "LogPage.cshtml" + Write(string.Format("{0:H:mm:ss}", log.Time)); + +#line default +#line hidden + WriteLiteral("
\r\n " + +"
+ + +"); + } + #pragma warning restore 1998 + } +} diff --git a/src/Microsoft.AspNet.Logging.Elm/Views/LogPage.cshtml b/src/Microsoft.AspNet.Logging.Elm/Views/LogPage.cshtml new file mode 100644 index 00000000..19a06319 --- /dev/null +++ b/src/Microsoft.AspNet.Logging.Elm/Views/LogPage.cshtml @@ -0,0 +1,141 @@ +@using System +@using System.Globalization +@using System.Linq +@using Microsoft.AspNet.Logging.Elm.Views +@using Microsoft.Framework.Logging + +@functions +{ + public LogPage(LogPageModel model) + { + Model = model; + } + + public LogPageModel Model { get; set; } +} + + + + + ELM + + + + +

ELM

+
+ + + +
+ + + + + + + + + + + + + + + + + @foreach (var logs in Model.Logs.GroupBy(g => g.Context)) + { + + + @{ + var requestPath = Model.Options.Path.Value + "/" + logs.Key.RequestID; + } + + + + + + + } +
PathHostStatus CodeLogs
@logs.Key.Path@logs.Key.Host@logs.Key.StatusCode + + + + + + + + + + + + + @* logs are chronologically ordered within each request *@ + @foreach (var log in logs.Reverse()) + { + var scopes = ""; + if (log.Scopes != null) + { + // classes cannot begin with a number, prepend an underscore + scopes = string.Join(" _", log.Scopes); + } + var logState = string.Format("{0} _{1}", "logState", scopes); + + + + + + + + + } + +
DateTimeNameSeverityStateError
@string.Format("{0:MM/dd/yy}", log.Time)@string.Format("{0:H:mm:ss}", log.Time)@log.Name@log.Severity@log.State@log.Exception
+
+ + + \ No newline at end of file diff --git a/src/Microsoft.AspNet.Logging.Elm/Views/LogPage.css b/src/Microsoft.AspNet.Logging.Elm/Views/LogPage.css new file mode 100644 index 00000000..eea8f021 --- /dev/null +++ b/src/Microsoft.AspNet.Logging.Elm/Views/LogPage.css @@ -0,0 +1,107 @@ +body { + font-family: 'Segoe UI', Tahoma, Arial, Helvetica, sans-serif; + font-size: .813em; + line-height: 1.4em; + white-space: nowrap; + margin: 10px; +} + +col:nth-child(2) { + background-color: #FAFAFA; +} + +h1 { + font-family: Arial, Helvetica, sans-serif; + font-size: 2em; +} + +table { + margin: 0px auto; + border-spacing: 0px; + table-layout: fixed; + width: 100%; + border-collapse: collapse; +} + +td { + text-overflow: ellipsis; + overflow: hidden; +} + +td, th { + padding: 4px; +} + +thead { + font-size: 1em; + font-family: Arial; +} + +tr { + height: 23px; +} + +tr:nth-child(2n) { + background-color: #F6F6F6; +} + +#requestHeader { + border-bottom: solid 1px gray; + border-top: solid 1px gray; + margin-bottom: 2px; + font-size: 1em; + line-height: 2em; +} + +.date, .time { + width: 70px; +} + +.logHeader { + border-bottom: 1px solid lightgray; + color: gray; + text-align: left; +} + +.logState { + text-overflow: ellipsis; + overflow: hidden; +} + +.logTd { + border-left: 1px solid gray; + padding: 0px; +} + +.logs { + width: 80%; +} + +.requestRow>td { + border-bottom: solid 1px gray; +} + +.severity { + width: 80px; +} + +.Critical { + background-color: red; + color: white; +} + +.Error { + color: red; +} + +.Information { + color: blue; +} + +.Verbose { + color: black; +} + +.Warning { + color: orange; +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Logging.Elm/Views/LogPageModel.cs b/src/Microsoft.AspNet.Logging.Elm/Views/LogPageModel.cs new file mode 100644 index 00000000..2aa84bc4 --- /dev/null +++ b/src/Microsoft.AspNet.Logging.Elm/Views/LogPageModel.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace Microsoft.AspNet.Logging.Elm.Views +{ + public class LogPageModel + { + public IEnumerable Logs { get; set; } + + public ElmOptions Options { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Logging.Elm/Views/RequestPage.cs b/src/Microsoft.AspNet.Logging.Elm/Views/RequestPage.cs new file mode 100644 index 00000000..b07b4320 --- /dev/null +++ b/src/Microsoft.AspNet.Logging.Elm/Views/RequestPage.cs @@ -0,0 +1,483 @@ +namespace Microsoft.AspNet.Logging.Elm.Views +{ +#line 1 "RequestPage.cshtml" +using System + +#line default +#line hidden + ; +#line 2 "RequestPage.cshtml" +using System.Globalization + +#line default +#line hidden + ; +#line 3 "RequestPage.cshtml" +using System.Linq + +#line default +#line hidden + ; +#line 4 "RequestPage.cshtml" +using Microsoft.AspNet.Logging.Elm + +#line default +#line hidden + ; +#line 5 "RequestPage.cshtml" +using Microsoft.AspNet.Logging.Elm.Views + +#line default +#line hidden + ; +#line 6 "RequestPage.cshtml" +using Microsoft.Framework.Logging + +#line default +#line hidden + ; + using System.Threading.Tasks; + + public class RequestPage : Microsoft.AspNet.Diagnostics.Views.BaseView + { +#line 9 "RequestPage.cshtml" + + public RequestPage(RequestPageModel model) + { + Model = model; + } + + public RequestPageModel Model { get; set; } + +#line default +#line hidden + #line hidden + public RequestPage() + { + } + + #pragma warning disable 1998 + public override async Task ExecuteAsync() + { + WriteLiteral("\r\n"); + WriteLiteral(@" + + + + ELM + + + +

ELM

+ +

Request Details

+ + +"); +#line 32 "RequestPage.cshtml" + + +#line default +#line hidden + +#line 32 "RequestPage.cshtml" + + var context = Model.Logs.First().Context; + + +#line default +#line hidden + + WriteLiteral("\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n " + +" \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n "); +#line 53 "RequestPage.cshtml" + Write(context.Protocol); + +#line default +#line hidden + WriteLiteral(@" + + + + \r\n " + +" \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n " + +" \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n"); +#line 97 "RequestPage.cshtml" + + +#line default +#line hidden + +#line 97 "RequestPage.cshtml" + if (context.Cookies.Any()) + { + +#line default +#line hidden + + WriteLiteral(@"
Path"); +#line 37 "RequestPage.cshtml" + Write(context.Path); + +#line default +#line hidden + WriteLiteral("
Host"); +#line 41 "RequestPage.cshtml" + Write(context.Host); + +#line default +#line hidden + WriteLiteral("
Content Type"); +#line 45 "RequestPage.cshtml" + Write(context.ContentType); + +#line default +#line hidden + WriteLiteral("
Method" + +""); +#line 49 "RequestPage.cshtml" + Write(context.Method); + +#line default +#line hidden + WriteLiteral("
Protocol
Headers + + + + + + + + +"); +#line 66 "RequestPage.cshtml" + + +#line default +#line hidden + +#line 66 "RequestPage.cshtml" + foreach (var header in context.Headers) + { + +#line default +#line hidden + + WriteLiteral(" \r\n \r\n \r\n \r\n"); +#line 72 "RequestPage.cshtml" + } + +#line default +#line hidden + + WriteLiteral(" \r\n
VariableValue
"); +#line 69 "RequestPage.cshtml" + Write(header.Key); + +#line default +#line hidden + WriteLiteral(""); +#line 70 "RequestPage.cshtml" + Write(string.Join(";", header.Value)); + +#line default +#line hidden + WriteLiteral("
\r\n
Status Code"); +#line 79 "RequestPage.cshtml" + Write(context.StatusCode); + +#line default +#line hidden + WriteLiteral("
User"); +#line 84 "RequestPage.cshtml" + Write(context.User.Identity.Name); + +#line default +#line hidden + WriteLiteral("
Scheme" + +""); +#line 88 "RequestPage.cshtml" + Write(context.Scheme); + +#line default +#line hidden + WriteLiteral("
Query"); +#line 92 "RequestPage.cshtml" + Write(context.Query.Value); + +#line default +#line hidden + WriteLiteral("
Cookies
+ + + + + + + +"); +#line 107 "RequestPage.cshtml" + + +#line default +#line hidden + +#line 107 "RequestPage.cshtml" + foreach (var cookie in context.Cookies) + { + +#line default +#line hidden + + WriteLiteral(" \r\n \r\n \r\n \r\n"); +#line 113 "RequestPage.cshtml" + } + +#line default +#line hidden + + WriteLiteral(" \r\n
VariableValue
"); +#line 110 "RequestPage.cshtml" + Write(cookie.Key); + +#line default +#line hidden + WriteLiteral(""); +#line 111 "RequestPage.cshtml" + Write(string.Join(";", cookie.Value)); + +#line default +#line hidden + WriteLiteral("
\r\n"); +#line 116 "RequestPage.cshtml" + } + +#line default +#line hidden + + WriteLiteral(" \r\n \r\n \r\n\r\n

Logs

\r\n
\r\n \r\n (Model.Options.NamePrefix, 3938), false)); + WriteLiteral(@" /> + +
+ + + + + + + + + + + +"); +#line 151 "RequestPage.cshtml" + + +#line default +#line hidden + +#line 151 "RequestPage.cshtml" + foreach (var log in Model.Logs) + { + +#line default +#line hidden + + WriteLiteral(" \r\n \r\n \r\n (log.Severity, 4548), false)); + WriteLiteral(">"); +#line 156 "RequestPage.cshtml" + Write(log.Severity); + +#line default +#line hidden + WriteLiteral("\r\n (log.Name, 4610), false)); + WriteLiteral(">"); +#line 157 "RequestPage.cshtml" + Write(log.Name); + +#line default +#line hidden + WriteLiteral("\r\n (log.State, 4664), false)); + WriteLiteral(" class=\"logState\" width=\"100px\">"); +#line 158 "RequestPage.cshtml" + Write(log.State); + +#line default +#line hidden + WriteLiteral("\r\n (log.Exception, 4751), false)); + WriteLiteral(">"); +#line 159 "RequestPage.cshtml" + Write(log.Exception); + +#line default +#line hidden + WriteLiteral("\r\n \r\n"); +#line 161 "RequestPage.cshtml" + } + +#line default +#line hidden + + WriteLiteral("
DateTimeSeverityNameStateError
"); +#line 154 "RequestPage.cshtml" + Write(string.Format("{0:MM/dd/yy}", log.Time)); + +#line default +#line hidden + WriteLiteral(""); +#line 155 "RequestPage.cshtml" + Write(string.Format("{0:H:mm:ss}", log.Time)); + +#line default +#line hidden + WriteLiteral("
\r\n\r\n"); + } + #pragma warning restore 1998 + } +} diff --git a/src/Microsoft.AspNet.Logging.Elm/Views/RequestPage.cshtml b/src/Microsoft.AspNet.Logging.Elm/Views/RequestPage.cshtml new file mode 100644 index 00000000..83500678 --- /dev/null +++ b/src/Microsoft.AspNet.Logging.Elm/Views/RequestPage.cshtml @@ -0,0 +1,164 @@ +@using System +@using System.Globalization +@using System.Linq +@using Microsoft.AspNet.Logging.Elm +@using Microsoft.AspNet.Logging.Elm.Views +@using Microsoft.Framework.Logging + +@functions +{ + public RequestPage(RequestPageModel model) + { + Model = model; + } + + public RequestPageModel Model { get; set; } +} + + + + + ELM + + + +

ELM

+ +

Request Details

+ + + @{ + var context = Model.Logs.First().Context; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @* TODO: show more information about the user*@ + + + + + + + + + + + + + + +
Path@context.Path
Host@context.Host
Content Type@context.ContentType
Method@context.Method
Protocol@context.Protocol
Headers + + + + + + + + + @foreach (var header in context.Headers) + { + + + + + } + +
VariableValue
@header.Key@string.Join(";", header.Value)
+
Status Code@context.StatusCode
User@context.User.Identity.Name
Scheme@context.Scheme
Query@context.Query.Value
Cookies + @if (context.Cookies.Any()) + { + + + + + + + + + @foreach (var cookie in context.Cookies) + { + + + + + } + +
VariableValue
@cookie.Key@string.Join(";", cookie.Value)
+ } +
+ +

Logs

+
+ + + +
+ + + + + + + + + + + + @foreach (var log in Model.Logs) + { + + + + + + + + + } +
DateTimeSeverityNameStateError
@string.Format("{0:MM/dd/yy}", log.Time)@string.Format("{0:H:mm:ss}", log.Time)@log.Severity@log.Name@log.State@log.Exception
+ + \ No newline at end of file diff --git a/src/Microsoft.AspNet.Logging.Elm/Views/RequestPage.css b/src/Microsoft.AspNet.Logging.Elm/Views/RequestPage.css new file mode 100644 index 00000000..7f5abbea --- /dev/null +++ b/src/Microsoft.AspNet.Logging.Elm/Views/RequestPage.css @@ -0,0 +1,79 @@ +body { + font-family: 'Segoe UI', Tahoma, Arial, Helvtica, sans-serif; + font-size: 0.9em; + line-height: 1.4em; + width: 90%; + margin: 0px auto; +} + +h1, h2 { + font-weight: normal; +} + +table { + border-spacing: 0px; + width: 100%; + border-collapse: collapse; + border: 1px solid black; + white-space: pre-wrap; +} + +td { + text-overflow: ellipsis; + overflow: hidden; +} + +th { + font-family: Arial; +} + +td, th { + padding: 8px; +} + +tr:nth-child(2n) { + background-color: #F6F6F6; +} + +#headerTable { + border: none; + height: 100%; +} + +#headerTd { + white-space: normal; +} + +#label { + width: 20%; + border-right: 1px solid black; +} + +#logs>tbody>tr>td { + border-right: 1px dashed lightgray; +} + +#logs>thead>tr>th { + border: 1px solid black; +} + +.Critical { + background-color: red; + color: white; +} + +.Error { + color: red; +} + +.Information { + color: blue; +} + +.Verbose { + color: black; +} + +.Warning { + color: orange; +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Logging.Elm/Views/RequestPageModel.cs b/src/Microsoft.AspNet.Logging.Elm/Views/RequestPageModel.cs new file mode 100644 index 00000000..fcdea619 --- /dev/null +++ b/src/Microsoft.AspNet.Logging.Elm/Views/RequestPageModel.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; + +namespace Microsoft.AspNet.Logging.Elm.Views +{ + public class RequestPageModel + { + public Guid RequestID { get; set; } + + public IEnumerable Logs { get; set; } + + public ElmOptions Options { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Logging.Elm/project.json b/src/Microsoft.AspNet.Logging.Elm/project.json new file mode 100644 index 00000000..d2c3640b --- /dev/null +++ b/src/Microsoft.AspNet.Logging.Elm/project.json @@ -0,0 +1,15 @@ +{ + "version": "1.0.0-*", + "dependencies": { + "Microsoft.AspNet.Diagnostics": "1.0.0-*", + "Microsoft.AspNet.Http": "1.0.0-*", + "Microsoft.AspNet.RequestContainer": "1.0.0-*", + "Microsoft.Framework.Logging.Interfaces": { "version": "1.0.0-*", "type": "build" }, + "Microsoft.Framework.Runtime.Interfaces": { "version": "1.0.0-*", "type": "build" } + }, + + "frameworks": { + "aspnet50": { }, + "aspnetcore50": { } + } +} diff --git a/src/PageGenerator/PageGenerator.kproj b/src/PageGenerator/PageGenerator.kproj new file mode 100644 index 00000000..dc172378 --- /dev/null +++ b/src/PageGenerator/PageGenerator.kproj @@ -0,0 +1,20 @@ + + + + 12.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 4d4a785a-ecb9-4916-a88f-0fd306ee3b74 + Console + + + + + + + 2.0 + + + \ No newline at end of file diff --git a/src/PageGenerator/Program.cs b/src/PageGenerator/Program.cs new file mode 100644 index 00000000..c4b5048c --- /dev/null +++ b/src/PageGenerator/Program.cs @@ -0,0 +1,119 @@ +// 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.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.AspNet.Razor; +using Microsoft.Framework.Runtime; + +namespace PageGenerator +{ + public class Program + { + private readonly ILibraryManager _libraryManager; + + public Program(ILibraryManager libraryManager) + { + _libraryManager = libraryManager; + } + + public void Main(string[] args) + { + var diagnosticsLibInfo = _libraryManager.GetLibraryInformation("Microsoft.AspNet.Logging.Elm"); + var viewBasePath = Path.Combine(Path.GetDirectoryName(diagnosticsLibInfo.Path), "Views"); + + Console.WriteLine("Generating code files for views in {0}", viewBasePath); + Console.WriteLine(); + + var cshtmlFiles = GetCshtmlFiles(viewBasePath); + + var fileCount = 0; + foreach (var fileName in cshtmlFiles) + { + Console.WriteLine(" Generating code file for view {0}...", Path.GetFileName(fileName)); + GenerateCodeFile(fileName); + Console.WriteLine(" Done!"); + fileCount++; + } + + Console.WriteLine(); + Console.WriteLine("{0} files successfully generated.", fileCount); + Console.WriteLine(); + + Console.Write("Press enter to close application..."); + Console.ReadLine(); + } + + private static IEnumerable GetCshtmlFiles(string path) + { + if (!Directory.Exists(path)) + { + throw new ArgumentException("path"); + } + + return Directory.EnumerateFiles(path, "*.cshtml"); + } + + private static void GenerateCodeFile(string cshtmlFilePath) + { + var basePath = Path.GetDirectoryName(cshtmlFilePath); + var fileName = Path.GetFileName(cshtmlFilePath); + var fileNameNoExtension = Path.GetFileNameWithoutExtension(fileName); + var codeLang = new CSharpRazorCodeLanguage(); + var host = new RazorEngineHost(codeLang); + host.DefaultBaseClass = "Microsoft.AspNet.Diagnostics.Views.BaseView"; + var engine = new RazorTemplateEngine(host); + + using (var fileStream = File.OpenText(cshtmlFilePath)) + { + var code = engine.GenerateCode( + input: fileStream, + className: fileNameNoExtension, + rootNamespace: "Microsoft.AspNet.Logging.Elm.Views", + sourceFileName: fileName); + + var source = code.GeneratedCode; + var startIndex = 0; + while (startIndex < source.Length) + { + var startMatch = @"<%$ include: "; + var endMatch = @" %>"; + startIndex = source.IndexOf(startMatch, startIndex); + if (startIndex == -1) + { + break; + } + var endIndex = source.IndexOf(endMatch, startIndex); + if (endIndex == -1) + { + break; + } + var includeFileName = source.Substring(startIndex + startMatch.Length, endIndex - (startIndex + startMatch.Length)); + includeFileName = SanitizeFileName(includeFileName); + Console.WriteLine(" Inlining file {0}", includeFileName); + var replacement = File.ReadAllText(Path.Combine(basePath, includeFileName)).Replace("\"", "\\\""); + source = source.Substring(0, startIndex) + replacement + source.Substring(endIndex + endMatch.Length); + startIndex = startIndex + replacement.Length; + } + + File.WriteAllText(Path.Combine(basePath, string.Format("{0}.cs", fileNameNoExtension)), source); + } + } + + private static string SanitizeFileName(string fileName) + { + // The Razor generated code sometimes splits strings across multiple lines + // which can hit the include file name, so we need to strip out the non-filename chars. + //ErrorPage.j" + + //"s + + var invalidChars = new List(Path.GetInvalidFileNameChars()); + invalidChars.Add('+'); + invalidChars.Add(' '); + + return string.Join(string.Empty, fileName.Where(c => !invalidChars.Contains(c)).ToArray()); + } + } +} diff --git a/src/PageGenerator/project.json b/src/PageGenerator/project.json new file mode 100644 index 00000000..db68833d --- /dev/null +++ b/src/PageGenerator/project.json @@ -0,0 +1,18 @@ +{ + "version": "1.0.0-*", + "dependencies": { + "Microsoft.AspNet.Logging.Elm": "1.0.0-*", + "Microsoft.AspNet.Diagnostics": "1.0.0-*", + "Microsoft.AspNet.Razor": "4.0.0-*", + "Microsoft.Framework.Runtime.Interfaces": "1.0.0-*" + }, + + "frameworks": { + "aspnet50": { }, + "aspnetcore50": { + "dependencies": { + "System.Console": "4.0.0-beta-*" + } + } + } +}