Open Chiramisu opened 4 years ago
BTW, probably everyone knows this, but if it helps anyone -- net helpmsg
is a quick way to call FormatMessage(..FORMAT_MESSAGE_FROM_SYSTEM..) manually. eg
C:\>net helpmsg 10060
A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.
@danmoseley
[...] I do not see how it would work for Win32Exception though, there is no enum (such an enum would be vast, eg., it would be all of winerror.h, etc)
Your assumption is correct, this enum contains about 2840 values: I created my own workaround and copied all names and values including their descriptions from the child pages of https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes into an Excel spreadsheet, added some concat functions and copied the results into an enum. It took about an hour. This enum looks like:
public enum EnumSystemErrorCode
{
[Description ("The operation completed successfully.")]
ERROR_SUCCESS = 0,
[Description ("Incorrect function.")]
ERROR_INVALID_FUNCTION = 1,
[Description ("The system cannot find the file specified.")]
ERROR_FILE_NOT_FOUND = 2,
[Description ("The system cannot find the path specified.")]
ERROR_PATH_NOT_FOUND = 3,
[Description ("The system cannot open the file.")]
ERROR_TOO_MANY_OPEN_FILES = 4,
[Description ("Access is denied.")]
ERROR_ACCESS_DENIED = 5,
...
[Description ("The length of the state manager setting name has exceeded the limit.")]
ERROR_STATE_SETTING_NAME_SIZE_LIMIT_EXCEEDED = 15817,
[Description ("The length of the state manager container name has exceeded the limit.")]
ERROR_STATE_CONTAINER_NAME_SIZE_LIMIT_EXCEEDED = 15818,
[Description ("This API cannot be used in the context of the caller's application type.")]
ERROR_API_UNAVAILABLE = 15841,
}
If somebody wants the complete file, I can post it here or upload it to my github account.
The problem in using the descriptions from the websites is, that they contain placeholders like:
[Description ("{Missing System File} The required system file %hs is bad or missing.")]
ERROR_MISSING_SYSTEMFILE = 573,
[Description ("{Application Error} The exception %s (0x%08lx) occurred in the application at location 0x%08lx.")]
ERROR_UNHANDLED_EXCEPTION = 574,
Therefore I had to combine the original exception message, which contains the data that was inserted to the placeholders, with the English exception message from the description.
The final message looks like:
A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond. (original message: 'Ein Verbindungsversuch ist fehlgeschlagen, da die Gegenstelle nach einer bestimmten Zeitspanne nicht richtig reagiert hat, oder die hergestellte Verbindung war fehlerhaft, da der verbundene Host nicht reagiert hat. [::ffff:100.111.1.2]:55900'), Socket Error = TimedOut, Socket Error Code = 10060 (0x0000274C)
The code to create this message is:
public static class EnumSystemErrorCodeHelper
{
public static string? GetDescription (this EnumSystemErrorCode i_systemErrorCode)
{
return DescriptionAttributeHelper.GetDescription (i_systemErrorCode);
}
}
public static class DescriptionAttributeHelper
{
public static string? GetDescription (Enum? i_member)
{
string? memberName = i_member?.ToString ();
if (i_member == null
|| string.IsNullOrEmpty (memberName))
throw new ArgumentNullException (nameof (i_member));
var memberInfos = i_member.GetType ().GetMember (memberName);
if (!memberInfos.Any ())
return null;
object[] attributes = memberInfos[0].GetCustomAttributes (typeof (DescriptionAttribute), false);
return attributes.Any ()
? ((DescriptionAttribute)attributes[0]).Description
: null;
}
}
public static class ExceptionHelper
{
/// ------------------------------------------------------------------
/// <summary>
/// Create a message text from the exception.
/// </summary>
/// <param name="i_exception"> The exception from which the message will be created. </param>
/// <param name="i_separator"> The separator text that will be inserted between different parts of the message. </param>
/// <param name="i_withInnerExceptions"> A flag that specifies whether the messages of inner exceptions should be added to the created message. </param>
/// <returns> A message text from the exception. </returns>
/// ------------------------------------------------------------------
public static string? GetMessage (this Exception? i_exception,
string i_separator = ", ",
bool i_withInnerExceptions = true)
{
if (i_exception == null)
return null;
string? newlineSeparator = i_separator.EqualsAny (CONSTS.CR, CONSTS.LF, CONSTS.CRLF)
? i_separator
: null;
string exceptionMessage = i_exception.Message;
var sbMessage = new StringBuilder (exceptionMessage);
if (!string.Equals (System.Threading.Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName, "en", StringComparison.InvariantCultureIgnoreCase))
{
if (i_exception is System.ComponentModel.Win32Exception win32Exception)
{
var systemErrorCode = (EnumSystemErrorCode)win32Exception.ErrorCode;
if (!Enum.IsDefined (systemErrorCode))
systemErrorCode = (EnumSystemErrorCode)win32Exception.NativeErrorCode;
if (Enum.IsDefined (systemErrorCode))
{
string? description = DescriptionAttributeHelper.GetDescription (systemErrorCode);
if (!string.IsNullOrEmpty (description))
{
_ = sbMessage.Clear ()
.Append (description)
.Append (newlineSeparator ?? " ")
.Append ("(original message: '")
.Append (exceptionMessage)
.Append ("')");
}
}
}
}
switch (i_exception) // Do NOT change the order of the cases.
{
case System.Net.Sockets.SocketException socketException:
_ = sbMessage.Append ($"{i_separator}Socket Error = {socketException.SocketErrorCode}")
.Append ($"{i_separator}Socket Error Code = {socketException.ErrorCode} (0x{socketException.ErrorCode:X8})");
break;
case System.ComponentModel.Win32Exception win32Exception:
_ = sbMessage.Append ($"{i_separator}Error Code = {win32Exception.ErrorCode} (0x{win32Exception.ErrorCode:X8})")
.Append ($"{i_separator}Native Error Code = {win32Exception.NativeErrorCode} (0x{win32Exception.NativeErrorCode:X8})");
break;
case System.Runtime.InteropServices.ExternalException externalException:
_ = sbMessage.Append ($"{i_separator}Error Code = {externalException.ErrorCode} (0x{externalException.ErrorCode:X8})");
break;
case System.Net.Http.HttpRequestException httpRequestException:
_ = sbMessage.Append ($"{i_separator}Status Code = {httpRequestException.StatusCode}");
break;
}
if (newlineSeparator is null)
_ = sbMessage.Replace (CONSTS.CRLF, CONSTS.CommaSpace)
.Replace (CONSTS.CR, CONSTS.CommaSpace)
.Replace (CONSTS.LF, CONSTS.CommaSpace);
if (i_exception.InnerException != null
&& i_withInnerExceptions)
{
_ = sbMessage.Append (" Inner exception: {")
.Append (i_exception.InnerException.GetMessage (i_separator, i_withInnerExceptions))
.Append ("}");
}
return sbMessage.ToString ();
}
/// ------------------------------------------------------------------
/// <summary>
/// Create a detailed text from the exception. The text contains message, source and stacktrace from the given exception and all inner exceptions.
/// </summary>
/// <param name="i_exception"> The exception from which the text will be created. </param>
/// <param name="i_separator"> The separator text that will be inserted between different parts of the exception text. </param>
/// <returns> A detailed text from the exception. </returns>
/// ------------------------------------------------------------------
public static string GetText (this Exception? i_exception,
string i_separator = ", ")
{
if (i_exception == null)
return s_text_exceptionObjectMissing;
string? newlineSeparator = i_separator.EqualsAny (CONSTS.CR, CONSTS.LF, CONSTS.CRLF)
? i_separator
: null;
var exception = i_exception;
var sb = new StringBuilder ();
int levelOfInnerException = 0;
do
{
if (levelOfInnerException == 0)
{
_ = sb.Append (">>>>> Exception: ");
}
else
{
_ = sb.Append (i_separator);
_ = sb.Append ($">>>>> Inner Exception #{levelOfInnerException}: ");
}
_ = sb.Append (newlineSeparator);
_ = sb.AppendWithSeparator (exception.GetType ().FullName, i_separator);
_ = sb.AppendWithSeparator (exception.GetMessage (i_separator, false), i_separator);
if (exception.Source != null)
{
_ = sb.Append (">>> Source: ");
_ = sb.Append (newlineSeparator);
_ = sb.AppendWithSeparator (exception.Source, i_separator);
}
if (exception.StackTrace != null)
{
string stackTrace = exception.StackTrace;
if (newlineSeparator is null)
stackTrace = stackTrace.Replace (CONSTS.CRLF, CONSTS.CommaSpace)
.Replace (CONSTS.CR, CONSTS.CommaSpace)
.Replace (CONSTS.LF, CONSTS.CommaSpace);
_ = sb.Append (">>> Stack Trace: ");
_ = sb.Append (newlineSeparator);
_ = sb.Append (stackTrace);
}
exception = exception.InnerException;
levelOfInnerException++;
}
while (exception != null);
return sb.ToString ();
}
}
@tfenise Are we expected to know or look up the error codes? If yes, is there a central page of all possible error codes?
copied all names and values including their descriptions from the child pages of https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes
In case it is useful to others -- what I do is just grep the SDK headers, which you will have if you have installed the C++ workload in Visual Studio, eg
C:\Program Files (x86)\Windows Kits\10\Include>findstr /sipc:"10060" *h
10.0.19041.0\shared\winerror.h:#define WSAETIMEDOUT 10060L
the message is in a comment next to there.
Of course, the numbers in those are not always in decimal. However the ERRLOOK.EXE tool that installs with Visual Studio at "C:\Program Files\Microsoft Visual Studio\2022\Preview\Common7\Tools\errlook.exe" can handle hex, etc.
I am not sure which headers ERRLOOK.EXE has aggregated. I do not know where the sources are.
The only concrete step identified in this issue so far is the suggested change to SocketsException. We can pass feedback on to Windows, but I do not thing we will change how they create and deploy Windows for this.
At least this issue did not get closed yet... They closed mine and this one and this one
When can we stop the need to translate something that resembles this (in dutch):
The surgery is crippled while the opening is not switched on.
And figure out it meant something like
This operation is invalid while the window is disabled.
Or even worse (an example from @macmade)
개체 참조가 개체의 인스턴스로 설정되지 않았습니다
Which according to him means
Object reference not set to an instance of an object.
Maybe .Net 9 will finally give us Exception.ToString(CultureInfo.InvariantCulture)
or a setting System.Environment.ExceptionCulture
or something equivalent?... Please?
Exception.ToString(CultureInfo.InvariantCulture)
+1
For our localized app, we want english logs and localized error messages for the user.
So only specifying System.Environment.ExceptionCulture
would be insufficient (though better than the status quo).
Exception.ToString(CultureInfo.InvariantCulture)
+1For our localized app, we want english logs and localized error messages for the user. So only specifying
System.Environment.ExceptionCulture
would be insufficient (though better than the status quo).
The catch is that you should rarely be displaying "raw" exception messages to the user (and never a stack trace), regardless of the language, but instead be displaying something that would be actionable for them. At that point, you'd be constructing messages and dialogs, so the language/culture of the exception should be irrelevant.
I have an application that is used in other countries. When receiving exception / error logs, they're invariably in other languages that I can't understand. I've looked into this, but had no luck. How do we force our C# .NET programs to log errors in English? If this currently isn't supported, please add it. I've seen requests for this around the Interwebz going back over a decade.
The user doesn't care about error logs. They are for developers; the vast majority of whom speak English.