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);
};
}