sources/Google.Solutions.Common/Util/ExceptionExtensions.cs (117 lines of code) (raw):
//
// Copyright 2020 Google LLC
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//
using Google.Solutions.Common.Linq;
using System;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Google.Solutions.Common.Util
{
public static class ExceptionExtensions
{
/// <summary>
/// Create a stack trace that looks like a default stack trace,
/// but additionally contains IL offsets.
/// </summary>
private static string CreateStackTraceWithOffsets(Exception e)
{
var buffer = new StringBuilder();
buffer.AppendLine($"{e.GetType().FullName}: {e.Message}");
foreach (var frame in new StackTrace(e, false)
.GetFrames()
.EnsureNotNull())
{
var method = frame.GetMethod();
var parameters = string.Join(
", ",
method
.GetParameters()
.EnsureNotNull()
.Select(p => $"{p.ParameterType.Name} {p.Name}"));
buffer.Append($" at {method.ReflectedType.FullName}.{method.Name}({parameters})");
buffer.Append($" +IL_{frame.GetILOffset():x4}");
var line = frame.GetFileName();
if (line != null)
{
buffer.Append($" in {line}:{frame.GetFileLineNumber()}");
}
buffer.AppendLine();
}
return buffer.ToString();
}
/// <summary>
/// Create a compact, single-line stack trace.
/// </summary>
private static string CreateCompactStackTrace(Exception e)
{
var buffer = new StringBuilder();
buffer.Append($"{e.GetType().Name}: {e.Message}");
if (new StackTrace(e, false)
.GetFrames()
.EnsureNotNull()
.Where(f => !f.GetMethod().ReflectedType.Namespace.StartsWith("System."))
.FirstOrDefault() is StackFrame frame)
{
var method = frame.GetMethod();
buffer.Append($" at {method.ReflectedType.FullName}.{method.Name}");
}
return buffer.ToString();
}
/// <summary>
/// Remove all enclosing <c>AggregateException</c> and
/// <c>TargetInvocationException</c> exceptions.
/// </summary>
public static Exception Unwrap(this Exception e)
{
if (e is AggregateException aggregate &&
aggregate.InnerException != null)
{
return aggregate.InnerException.Unwrap();
}
else if (e is TargetInvocationException target &&
target.InnerException != null)
{
return target.InnerException.Unwrap();
}
else
{
return e;
}
}
/// <summary>
/// Test if the exception, when unwrapped, is of a certain
/// type. Can be used in exception filters.
/// </summary>
public static bool Is<T>(this Exception e) where T : Exception
{
return e.Unwrap() is T;
}
public static bool IsCancellation(this Exception e)
{
return e.Is<TaskCanceledException>() || e.Is<OperationCanceledException>();
}
public static bool IsComException(this Exception e)
{
return e.Is<COMException>() || e.Is<InvalidComObjectException>();
}
/// <summary>
/// Combine the exception message of all nested exceptions.
/// </summary>
public static string FullMessage(this Exception exception)
{
var fullMessage = new StringBuilder();
for (var ex = exception; ex != null; ex = ex.InnerException)
{
if (fullMessage.Length > 0)
{
fullMessage.Append(": ");
}
fullMessage.Append(ex.Message);
}
return fullMessage.ToString();
}
/// <summary>
/// Format an exception.
/// </summary>
public static string ToString(
this Exception exception,
ExceptionFormatOptions options)
{
return options switch
{
ExceptionFormatOptions.IncludeOffsets
=> CreateStackTraceWithOffsets(exception),
ExceptionFormatOptions.Compact
=> CreateCompactStackTrace(exception),
_ => exception.ToString(),
};
}
}
public enum ExceptionFormatOptions
{
/// <summary>
/// Normal format.
/// </summary>
None,
/// <summary>
/// Include IL offsets.
/// </summary>
IncludeOffsets,
/// <summary>
/// Single-line format that only contains the most relevant
/// information.
/// </summary>
Compact
}
}