From d0abbf0664053a7cacbcec4718199dbc232f843a Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 31 May 2017 15:25:07 +0200 Subject: [PATCH] Better boot fail error reporting --- src/Umbraco.Core/CoreRuntime.cs | 1 + .../Exceptions/BootFailedException.cs | 5 +++ src/Umbraco.Core/IRuntime.cs | 1 - src/Umbraco.Core/IRuntimeState.cs | 6 ++++ src/Umbraco.Core/RuntimeState.cs | 8 ++++- src/Umbraco.Web/UmbracoModule.cs | 32 +++++++++++++++++-- 6 files changed, 48 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Core/CoreRuntime.cs b/src/Umbraco.Core/CoreRuntime.cs index c9beb18f02..bfee15f71b 100644 --- a/src/Umbraco.Core/CoreRuntime.cs +++ b/src/Umbraco.Core/CoreRuntime.cs @@ -105,6 +105,7 @@ namespace Umbraco.Core { _state.Level = RuntimeLevel.BootFailed; var bfe = e as BootFailedException ?? new BootFailedException("Boot failed.", e); + _state.BootFailedException = bfe; bootTimer.Fail(exception: bfe); // be sure to log the exception - even if we repeat ourselves // throwing here can cause w3wp to hard-crash and we want to avoid it. diff --git a/src/Umbraco.Core/Exceptions/BootFailedException.cs b/src/Umbraco.Core/Exceptions/BootFailedException.cs index 3e13c83af4..dbfe9a33eb 100644 --- a/src/Umbraco.Core/Exceptions/BootFailedException.cs +++ b/src/Umbraco.Core/Exceptions/BootFailedException.cs @@ -7,6 +7,11 @@ namespace Umbraco.Core.Exceptions /// public class BootFailedException : Exception { + /// + /// Defines the default boot failed exception message. + /// + public const string DefaultMessage = "Boot failed: Umbraco cannot run. Sad. See Umbraco's log file for more details."; + /// /// Initializes a new instance of the class with a specified error message. /// diff --git a/src/Umbraco.Core/IRuntime.cs b/src/Umbraco.Core/IRuntime.cs index ce0f77ffe4..26ed4a9f14 100644 --- a/src/Umbraco.Core/IRuntime.cs +++ b/src/Umbraco.Core/IRuntime.cs @@ -1,4 +1,3 @@ -using System; using LightInject; namespace Umbraco.Core diff --git a/src/Umbraco.Core/IRuntimeState.cs b/src/Umbraco.Core/IRuntimeState.cs index 3c399f6a92..afee65c9aa 100644 --- a/src/Umbraco.Core/IRuntimeState.cs +++ b/src/Umbraco.Core/IRuntimeState.cs @@ -1,5 +1,6 @@ using System; using Semver; +using Umbraco.Core.Exceptions; using Umbraco.Core.Sync; namespace Umbraco.Core @@ -55,5 +56,10 @@ namespace Umbraco.Core /// Gets the runtime level of execution. /// RuntimeLevel Level { get; } + + /// + /// Gets the exception that caused the boot to fail. + /// + BootFailedException BootFailedException { get; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/RuntimeState.cs b/src/Umbraco.Core/RuntimeState.cs index 7c3f9b3d6a..267837ea8d 100644 --- a/src/Umbraco.Core/RuntimeState.cs +++ b/src/Umbraco.Core/RuntimeState.cs @@ -4,6 +4,7 @@ using System.Web; using Semver; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Sync; @@ -87,7 +88,7 @@ namespace Umbraco.Core /// public RuntimeLevel Level { - get { return _level; } + get => _level; internal set { _level = value; if (value == RuntimeLevel.Run) _runLevel.Set(); } } @@ -113,5 +114,10 @@ namespace Umbraco.Core { return _runLevel.WaitHandle.WaitOne(timeout); } + + /// + /// Gets the exception that caused the boot to fail. + /// + public BootFailedException BootFailedException { get; internal set; } } } \ No newline at end of file diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs index 78ed5d3a58..47816c53c1 100644 --- a/src/Umbraco.Web/UmbracoModule.cs +++ b/src/Umbraco.Web/UmbracoModule.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.IO; +using System.Text; using System.Web; using System.Web.Routing; using LightInject; @@ -504,7 +505,31 @@ namespace Umbraco.Web if (Core.Composing.Current.RuntimeState.Level == RuntimeLevel.BootFailed) { // there's nothing we can do really - app.BeginRequest += (sender, args) => throw new BootFailedException("Boot failed. Umbraco cannot run. Umbraco's log file contains details about what caused the boot to fail."); + app.BeginRequest += (sender, args) => + { + // would love to avoid throwing, and instead display a customized Umbraco 500 + // page - however if we don't throw here, something else might go wrong, and + // it's this later exception that would be reported. could not figure out how + // to prevent it, either with httpContext.Response.End() or .ApplicationInstance + // .CompleteRequest() + + // also, if something goes wrong with our DI setup, the logging subsystem may + // not even kick in, so here we try to give as much detail as possible + + Exception e = Core.Composing.Current.RuntimeState.BootFailedException; + if (e == null) + throw new BootFailedException(BootFailedException.DefaultMessage); + var m = new StringBuilder(); + m.Append(BootFailedException.DefaultMessage); + while (e != null) + { + m.Append($"\n\n-> {e.GetType().FullName}: {e.Message}"); + if (string.IsNullOrWhiteSpace(e.StackTrace) == false) + m.Append($"\n{e.StackTrace}"); + e = e.InnerException; + } + throw new BootFailedException(m.ToString()); + }; return; } @@ -548,14 +573,15 @@ namespace Umbraco.Web app.EndRequest += (sender, args) => { var httpContext = ((HttpApplication) sender).Context; + if (UmbracoContext.Current != null && UmbracoContext.Current.IsFrontEndUmbracoRequest) { Logger.Debug($"End Request. ({DateTime.Now.Subtract(UmbracoContext.Current.ObjectCreated).TotalMilliseconds}ms)"); } - OnEndRequest(new UmbracoRequestEventArgs(UmbracoContext.Current, new HttpContextWrapper(httpContext))); + OnEndRequest(new UmbracoRequestEventArgs(UmbracoContext.Current, new HttpContextWrapper(httpContext))); - DisposeHttpContextItems(httpContext); + DisposeHttpContextItems(httpContext); }; }