mirror of
https://github.com/Tyrrrz/CliFx.git
synced 2025-10-25 15:19:17 +00:00
Improve stack trace parsing
This commit is contained in:
@@ -160,13 +160,11 @@ namespace CliFx
|
|||||||
foreach (var stackFrame in StackFrame.ParseMany(exception.StackTrace))
|
foreach (var stackFrame in StackFrame.ParseMany(exception.StackTrace))
|
||||||
{
|
{
|
||||||
console.Error.Write(indentationShared + indentationLocal);
|
console.Error.Write(indentationShared + indentationLocal);
|
||||||
|
console.Error.Write("at ");
|
||||||
// "at"
|
|
||||||
console.Error.Write(stackFrame.Prefix + " ");
|
|
||||||
|
|
||||||
// "CliFx.Demo.Commands.BookAddCommand."
|
// "CliFx.Demo.Commands.BookAddCommand."
|
||||||
console.WithForegroundColor(ConsoleColor.DarkGray, () =>
|
console.WithForegroundColor(ConsoleColor.DarkGray, () =>
|
||||||
console.Error.Write(stackFrame.ParentType)
|
console.Error.Write(stackFrame.ParentType + ".")
|
||||||
);
|
);
|
||||||
|
|
||||||
// "ExecuteAsync"
|
// "ExecuteAsync"
|
||||||
@@ -176,47 +174,62 @@ namespace CliFx
|
|||||||
|
|
||||||
console.Error.Write("(");
|
console.Error.Write("(");
|
||||||
|
|
||||||
foreach (var parameter in stackFrame.Parameters)
|
for (var i = 0; i < stackFrame.Parameters.Count; i++)
|
||||||
{
|
{
|
||||||
|
var parameter = stackFrame.Parameters[i];
|
||||||
|
|
||||||
// "IConsole"
|
// "IConsole"
|
||||||
console.WithForegroundColor(ConsoleColor.Blue, () =>
|
console.WithForegroundColor(ConsoleColor.Blue, () =>
|
||||||
console.Error.Write(parameter.Type)
|
console.Error.Write(parameter.Type)
|
||||||
);
|
);
|
||||||
|
|
||||||
// "console"
|
if (!string.IsNullOrWhiteSpace(parameter.Name))
|
||||||
console.WithForegroundColor(ConsoleColor.White, () =>
|
|
||||||
console.Error.Write(parameter.Name)
|
|
||||||
);
|
|
||||||
|
|
||||||
// ", '
|
|
||||||
if (parameter.Separator != null)
|
|
||||||
{
|
{
|
||||||
console.Error.Write(parameter.Separator);
|
console.Error.Write(" ");
|
||||||
|
|
||||||
|
// "console"
|
||||||
|
console.WithForegroundColor(ConsoleColor.White, () =>
|
||||||
|
console.Error.Write(parameter.Name)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Separator
|
||||||
|
if (stackFrame.Parameters.Count > 1 && i < stackFrame.Parameters.Count - 1)
|
||||||
|
{
|
||||||
|
console.Error.Write(", ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.Error.Write(") ");
|
console.Error.Write(") ");
|
||||||
|
|
||||||
// "in"
|
// Location
|
||||||
console.Error.Write(stackFrame.LocationPrefix);
|
if (!string.IsNullOrWhiteSpace(stackFrame.FilePath))
|
||||||
console.Error.Write("\n" + indentationShared + indentationLocal + indentationLocal);
|
{
|
||||||
|
console.Error.Write("in");
|
||||||
|
console.Error.Write("\n" + indentationShared + indentationLocal + indentationLocal);
|
||||||
|
|
||||||
// "E:\Projects\Softdev\CliFx\CliFx.Demo\Commands\"
|
// "E:\Projects\Softdev\CliFx\CliFx.Demo\Commands\"
|
||||||
console.WithForegroundColor(ConsoleColor.DarkGray, () =>
|
var stackFrameDirectoryPath = Path.GetDirectoryName(stackFrame.FilePath);
|
||||||
console.Error.Write(stackFrame.DirectoryPath)
|
console.WithForegroundColor(ConsoleColor.DarkGray, () =>
|
||||||
);
|
console.Error.Write(stackFrameDirectoryPath + Path.DirectorySeparatorChar)
|
||||||
|
);
|
||||||
|
|
||||||
// "BookAddCommand.cs"
|
// "BookAddCommand.cs"
|
||||||
console.WithForegroundColor(ConsoleColor.Yellow, () =>
|
var stackFrameFileName = Path.GetFileName(stackFrame.FilePath);
|
||||||
console.Error.Write(stackFrame.FileName)
|
console.WithForegroundColor(ConsoleColor.Yellow, () =>
|
||||||
);
|
console.Error.Write(stackFrameFileName)
|
||||||
|
);
|
||||||
|
|
||||||
console.Error.Write(":");
|
if (!string.IsNullOrWhiteSpace(stackFrame.LineNumber))
|
||||||
|
{
|
||||||
|
console.Error.Write(":");
|
||||||
|
|
||||||
// "35"
|
// "35"
|
||||||
console.WithForegroundColor(ConsoleColor.Blue, () =>
|
console.WithForegroundColor(ConsoleColor.Blue, () =>
|
||||||
console.Error.Write(stackFrame.LineNumber)
|
console.Error.Write(stackFrame.LineNumber)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
console.Error.WriteLine();
|
console.Error.WriteLine();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,121 +10,110 @@ namespace CliFx.Internal
|
|||||||
{
|
{
|
||||||
public string Type { get; }
|
public string Type { get; }
|
||||||
|
|
||||||
public string Name { get; }
|
public string? Name { get; }
|
||||||
|
|
||||||
public string? Separator { get; }
|
public StackFrameParameter(string type, string? name)
|
||||||
|
|
||||||
public StackFrameParameter(
|
|
||||||
string type,
|
|
||||||
string name,
|
|
||||||
string? separator)
|
|
||||||
{
|
{
|
||||||
Type = type;
|
Type = type;
|
||||||
Name = name;
|
Name = name;
|
||||||
Separator = separator;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal partial class StackFrame
|
internal partial class StackFrame
|
||||||
{
|
{
|
||||||
public string Prefix { get; }
|
|
||||||
|
|
||||||
public string ParentType { get; }
|
public string ParentType { get; }
|
||||||
|
|
||||||
public string MethodName { get; }
|
public string MethodName { get; }
|
||||||
|
|
||||||
public IReadOnlyList<StackFrameParameter> Parameters { get; }
|
public IReadOnlyList<StackFrameParameter> Parameters { get; }
|
||||||
|
|
||||||
public string LocationPrefix { get; }
|
public string? FilePath { get; }
|
||||||
|
|
||||||
public string DirectoryPath { get; }
|
public string? LineNumber { get; }
|
||||||
|
|
||||||
public string FileName { get; }
|
|
||||||
|
|
||||||
public string LineNumber { get; }
|
|
||||||
|
|
||||||
public StackFrame(
|
public StackFrame(
|
||||||
string prefix,
|
|
||||||
string parentType,
|
string parentType,
|
||||||
string methodName,
|
string methodName,
|
||||||
IReadOnlyList<StackFrameParameter> parameters,
|
IReadOnlyList<StackFrameParameter> parameters,
|
||||||
string locationPrefix,
|
string? filePath,
|
||||||
string directoryPath,
|
string? lineNumber)
|
||||||
string fileName,
|
|
||||||
string lineNumber)
|
|
||||||
{
|
{
|
||||||
Prefix = prefix;
|
|
||||||
ParentType = parentType;
|
ParentType = parentType;
|
||||||
MethodName = methodName;
|
MethodName = methodName;
|
||||||
Parameters = parameters;
|
Parameters = parameters;
|
||||||
LocationPrefix = locationPrefix;
|
FilePath = filePath;
|
||||||
DirectoryPath = directoryPath;
|
|
||||||
FileName = fileName;
|
|
||||||
LineNumber = lineNumber;
|
LineNumber = lineNumber;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal partial class StackFrame
|
internal partial class StackFrame
|
||||||
{
|
{
|
||||||
private static readonly Regex MethodMatcher =
|
private const string Space = @"[\x20\t]";
|
||||||
new Regex(@"(?<prefix>\S+) (?<name>.*?)(?<methodName>[^\.]+)\(");
|
private const string NotSpace = @"[^\x20\t]";
|
||||||
|
|
||||||
private static readonly Regex ParameterMatcher =
|
// Taken from https://github.com/atifaziz/StackTraceParser
|
||||||
new Regex(@"(?<type>.+? )(?<name>.+?)(?:(?<separator>, )|\))");
|
private static readonly Regex Pattern = new Regex(@"
|
||||||
|
^
|
||||||
|
" + Space + @"*
|
||||||
|
\w+ " + Space + @"+
|
||||||
|
(?<frame>
|
||||||
|
(?<type> " + NotSpace + @"+ ) \.
|
||||||
|
(?<method> " + NotSpace + @"+? ) " + Space + @"*
|
||||||
|
(?<params> \( ( " + Space + @"* \)
|
||||||
|
| (?<pt> .+?) " + Space + @"+ (?<pn> .+?)
|
||||||
|
(, " + Space + @"* (?<pt> .+?) " + Space + @"+ (?<pn> .+?) )* \) ) )
|
||||||
|
( " + Space + @"+
|
||||||
|
( # Microsoft .NET stack traces
|
||||||
|
\w+ " + Space + @"+
|
||||||
|
(?<file> ( [a-z] \: # Windows rooted path starting with a drive letter
|
||||||
|
| / ) # *nix rooted path starting with a forward-slash
|
||||||
|
.+? )
|
||||||
|
\: \w+ " + Space + @"+
|
||||||
|
(?<line> [0-9]+ ) \p{P}?
|
||||||
|
| # Mono stack traces
|
||||||
|
\[0x[0-9a-f]+\] " + Space + @"+ \w+ " + Space + @"+
|
||||||
|
<(?<file> [^>]+ )>
|
||||||
|
:(?<line> [0-9]+ )
|
||||||
|
)
|
||||||
|
)?
|
||||||
|
)
|
||||||
|
\s*
|
||||||
|
$",
|
||||||
|
RegexOptions.IgnoreCase |
|
||||||
|
RegexOptions.Multiline |
|
||||||
|
RegexOptions.ExplicitCapture |
|
||||||
|
RegexOptions.CultureInvariant |
|
||||||
|
RegexOptions.IgnorePatternWhitespace,
|
||||||
|
TimeSpan.FromSeconds(5)
|
||||||
|
);
|
||||||
|
|
||||||
private static readonly Regex FileMatcher =
|
public static IEnumerable<StackFrame> ParseMany(string stackTrace)
|
||||||
new Regex(@"(?<prefix>\S+?) (?<path>.*?)(?<file>[^\\/]+?(?:\.\w*)?):[^:]+? (?<line>\d+).*");
|
|
||||||
|
|
||||||
public static StackFrame Parse(string stackFrame)
|
|
||||||
{
|
{
|
||||||
var methodMatch = MethodMatcher.Match(stackFrame);
|
var matches = Pattern.Matches(stackTrace).Cast<Match>().ToArray();
|
||||||
|
|
||||||
var parameterMatches = ParameterMatcher.Matches(stackFrame, methodMatch.Index + methodMatch.Length)
|
// Ensure success
|
||||||
.Cast<Match>()
|
var lastMatch = matches.LastOrDefault();
|
||||||
.ToArray();
|
if (lastMatch == null ||
|
||||||
|
lastMatch.Index + lastMatch.Length < stackTrace.Length)
|
||||||
var fileMatch = FileMatcher.Match(
|
|
||||||
stackFrame,
|
|
||||||
parameterMatches.Length switch
|
|
||||||
{
|
|
||||||
0 => methodMatch.Index + methodMatch.Length + 1,
|
|
||||||
_ => parameterMatches[parameterMatches.Length - 1].Index +
|
|
||||||
parameterMatches[parameterMatches.Length - 1].Length
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Ensure everything was parsed successfully
|
|
||||||
var isSuccessful =
|
|
||||||
methodMatch.Success &&
|
|
||||||
parameterMatches.All(m => m.Success) &&
|
|
||||||
fileMatch.Success &&
|
|
||||||
fileMatch.Index + fileMatch.Length == stackFrame.Length;
|
|
||||||
|
|
||||||
if (!isSuccessful)
|
|
||||||
{
|
{
|
||||||
throw new FormatException("Failed to parse stack frame.");
|
throw new FormatException("Could not parse stack trace.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var parameters = parameterMatches
|
return from m in matches
|
||||||
.Select(match => new StackFrameParameter(
|
select m.Groups
|
||||||
match.Groups["type"].Value,
|
into groups
|
||||||
match.Groups["name"].Value,
|
let pt = groups["pt"].Captures
|
||||||
match.Groups["separator"].Value.NullIfWhiteSpace()
|
let pn = groups["pn"].Captures
|
||||||
)).ToArray();
|
select new StackFrame(
|
||||||
|
groups["type"].Value,
|
||||||
return new StackFrame(
|
groups["method"].Value,
|
||||||
methodMatch.Groups["prefix"].Value,
|
(
|
||||||
methodMatch.Groups["name"].Value,
|
from i in Enumerable.Range(0, pt.Count)
|
||||||
methodMatch.Groups["methodName"].Value,
|
select new StackFrameParameter(pt[i].Value, pn[i].Value.NullIfWhiteSpace())
|
||||||
parameters,
|
).ToArray(),
|
||||||
fileMatch.Groups["prefix"].Value,
|
groups["file"].Value.NullIfWhiteSpace(),
|
||||||
fileMatch.Groups["path"].Value,
|
groups["line"].Value.NullIfWhiteSpace()
|
||||||
fileMatch.Groups["file"].Value,
|
);
|
||||||
fileMatch.Groups["line"].Value
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IReadOnlyList<StackFrame> ParseMany(string stackTrace) =>
|
|
||||||
stackTrace.Split('\n', StringSplitOptions.RemoveEmptyEntries).Select(Parse).ToArray();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user