340 lines
No EOL
12 KiB
C#
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
|
|
}
|
|
} |