JD2022-TU1/main/tools/framework/JD.LogService/CanonicalError.cs

340 lines
No EOL
12 KiB
C#

using System;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
using JD.Kernel.StaticHelpers;
using JD.Kernel.Structs;
namespace JD.LogService
{
/// <summary>
/// Class responsible for converting errors from/to strings.
/// </summary>
public static class CanonicalError
{
#region Variables
private const string PrefixGroupName = "PREFIX";
private const string OriginGroupName = "ORIGIN";
private const string CategoryGroupName = "CATEGORY";
private const string TextGroupName = "TEXT";
private const string CodeGroupName = "CODE";
private const string ErrorText = "error";
private const string WarningText = "warning";
private const string RegexString = @"^\s*" +
@"(?<{6}>(\d+>)?)" + // prefix
@"(((?<{0}>(([a-z]?:[^:]*)|([^:]*))):)|())" + // origin or empty
@"(([^:]*?\s)|())" + // any char except ':' or empty
@"((\[\d+:\d+:\d+\.\d+\])\s|())" + // time [hh:mm:ss.sss] or empty
@"(?<{1}>({4}|{5}))\s*" + // category (warning or error)
@"(?<{2}>[^: ]*)\s*:" + // code
@"(?<{3}>.*)$"; // text
private static readonly Regex m_mainRegex = new Regex(string.Format(RegexString, OriginGroupName, CategoryGroupName, CodeGroupName, TextGroupName, ErrorText, WarningText, PrefixGroupName),
RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace | RegexOptions.CultureInvariant);
private static readonly Regex m_projectPath = new Regex(@"^(.*)\[(.+\\).*\..*\](.*)$", RegexOptions.IgnoreCase);
private static readonly string[] NewLines = { "\r\n", "\n" };
private static readonly int ms_PrefixGroup;
private static readonly int ms_OriginGroup;
private static readonly int ms_CategoryGroup;
private static readonly int ms_TextGroup;
private static readonly int ms_CodeGroup;
#endregion
#region Constructor
static CanonicalError()
{
ms_PrefixGroup = m_mainRegex.GroupNumberFromName(PrefixGroupName);
ms_OriginGroup = m_mainRegex.GroupNumberFromName(OriginGroupName);
ms_CategoryGroup = m_mainRegex.GroupNumberFromName(CategoryGroupName);
ms_TextGroup = m_mainRegex.GroupNumberFromName(TextGroupName);
ms_CodeGroup = m_mainRegex.GroupNumberFromName(CodeGroupName);
}
#endregion
#region Methods
/// <summary>
/// Parse the given <paramref name="message"/> and tries to extract canonical properties from it.
/// </summary>
/// <param name="message"></param>
/// <returns>Null if unable to extract canonical properties.</returns>
public static Error Parse(string message)
{
if (string.IsNullOrEmpty(message))
{
return null;
}
LogCategory category;
Match match = m_mainRegex.Match(message);
if (!match.Success)
{
return null;
}
string locationString = match.Groups[ms_OriginGroup].Value.Trim();
string leadingInfo = match.Groups[ms_PrefixGroup].Value.Trim();
string categoryString = match.Groups[ms_CategoryGroup].Value.Trim();
string errorCode = match.Groups[ms_CodeGroup].Value.Trim();
string errorMessage = match.Groups[ms_TextGroup].Value.Trim();
switch (categoryString.ToLowerInvariant())
{
case ErrorText:
category = LogCategory.Error;
break;
case WarningText:
category = LogCategory.Warning;
break;
default:
return null;
}
// MSBuild ouput project path and by default filePath is relative to that path
// Ex : ..\myLib\myFile.cpp(253): error XXX [D:\Code\Projects\myLib.vcxproj]
// The project path regex is costly and should only try with the begin of the error, otherwise it
// might run until infinite.
var errorMessageStart = errorMessage.Length <= 1000 ? errorMessage : errorMessage.Substring(0, 1000);
string projectLocation = null;
Match projectMatch = m_projectPath.Match(errorMessageStart);
if (projectMatch.Success)
{
errorMessage = projectMatch.Groups[1].Value;
projectLocation = projectMatch.Groups[2].Value;
}
FileLocation location = ParseFileLocation(locationString, projectLocation);
return new Error(category)
{
LeadingInfo = leadingInfo,
Prefix = leadingInfo,
Origin = location,
Message = errorMessage,
Code = errorCode
};
}
private static FileLocation ParseFileLocation(string line, string projectPath)
{
if (string.IsNullOrEmpty(line))
{
return FileLocation.Empty;
}
FileLocation defaultLocation = new FileLocation(line);
if (line[line.Length - 1] != ')')
{
return defaultLocation;
}
int parenthesisIndex = line.LastIndexOf('(');
if (parenthesisIndex == -1)
{
return defaultLocation;
}
string filename = line.Substring(0, parenthesisIndex);
string numbers = line.Substring(parenthesisIndex + 1, line.Length - 2 - parenthesisIndex);
try
{
if (!System.IO.Path.IsPathRooted(filename) && !string.IsNullOrEmpty(projectPath))
{
filename = System.IO.Path.GetFullPath(projectPath + filename);
}
}
catch (Exception)
{
return defaultLocation;
}
if (string.IsNullOrEmpty(numbers))
{
return defaultLocation;
}
int lineNumber, endLineNumber, columnNumber, endColumnNumber;
if (!ParseLineNumbers(numbers, out lineNumber, out columnNumber, out endLineNumber, out endColumnNumber))
{
return defaultLocation;
}
return new FileLocation(filename, lineNumber, columnNumber, endLineNumber, endColumnNumber);
}
private static bool ParseLineNumbers(string numbers, out int lineNumber, out int columnNumber, out int endLineNumber, out int endColumnNumber)
{
lineNumber = 0;
columnNumber = 0;
endLineNumber = 0;
endColumnNumber = 0;
string[] tokens = numbers.Split(',');
switch (tokens.Length)
{
case 1:
if (!ParseDualNumber(tokens[0], out lineNumber, ref endLineNumber))
{
return false;
}
break;
case 2:
if (!ParseDualNumber(tokens[0], out lineNumber, ref endLineNumber))
{
return false;
}
if (!ParseDualNumber(tokens[1], out columnNumber, ref endColumnNumber))
{
return false;
}
break;
case 4:
if (!int.TryParse(tokens[0], out lineNumber))
{
return false;
}
if (!int.TryParse(tokens[1], out columnNumber))
{
return false;
}
if (!int.TryParse(tokens[2], out endLineNumber))
{
return false;
}
if (!int.TryParse(tokens[3], out endColumnNumber))
{
return false;
}
break;
default:
return false;
}
return true;
}
private static bool ParseDualNumber(string token, out int number1, ref int number2)
{
if (string.IsNullOrEmpty(token))
{
number1 = 0;
return false;
}
int index = token.IndexOf('-');
if (index == -1)
{
bool success = int.TryParse(token, out number1);
number2 = number1;
return success;
}
if (!int.TryParse(token.Substring(0, index), out number1))
{
return false;
}
return int.TryParse(token.Substring(index + 1), out number2);
}
/// <summary>
/// Builds a canonical error string.
/// </summary>
/// <param name="error"></param>
/// <returns></returns>
public static string Build(Error error)
{
return Build(error.Category, error.Prefix, error.Code, error.Message, error.Origin, error.LeadingInfo);
}
/// <summary>
/// Builds a canonical error string.
/// </summary>
/// <returns></returns>
public static string Build(LogCategory category, string prefix, string code, string message, FileLocation location, string leadingInfo)
{
Throw.InvalidArgumentIf(category == LogCategory.Message, "Messages cannot be converted to canonical errors", "category");
StringBuilder builder = new StringBuilder();
builder.Append(leadingInfo);
if (!string.IsNullOrEmpty(prefix))
{
builder.Append(prefix);
}
string fileLocation = location.ToString();
if (!string.IsNullOrEmpty(fileLocation))
{
builder.Append("{0}: "); // File location
}
if (!message.StartsWith(ErrorText, StringComparison.OrdinalIgnoreCase) &&
!message.StartsWith(WarningText, StringComparison.OrdinalIgnoreCase))
{
builder.Append("{1} "); // Category
}
if (!string.IsNullOrEmpty(code))
builder.Append("{2}: ");
else if (builder.Length != 0)
builder.Append(": ");
builder.Append("{3}"); // Message
string textToFormat = builder.ToString();
string[] messageLines = SplitStringOnNewLines(message);
StringBuilder secondBuilder = new StringBuilder();
for (int numLine = 0; numLine < messageLines.Length; numLine++)
{
secondBuilder.Append(string.Format(CultureInfo.InvariantCulture, textToFormat, new object[] { fileLocation, GetCategoryString(category), code, messageLines[numLine] }));
if (numLine < messageLines.Length - 1)
{
secondBuilder.AppendLine();
}
}
return secondBuilder.ToString();
}
private static string GetCategoryString(LogCategory category)
{
switch (category)
{
case LogCategory.Error:
return ErrorText;
case LogCategory.Warning:
return WarningText;
default:
throw new InvalidOperationException("Unknown error category");
}
}
private static string[] SplitStringOnNewLines(string str)
{
return str.Split(NewLines, StringSplitOptions.None);
}
#endregion
}
}