Skip to content

Exception Handling / Error Logging in English #40427

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
Chiramisu opened this issue Aug 5, 2020 · 57 comments
Open

Exception Handling / Error Logging in English #40427

Chiramisu opened this issue Aug 5, 2020 · 57 comments
Labels
area-System.Resources question Answer questions and provide assistance, not an issue with source code or documentation.
Milestone

Comments

@Chiramisu
Copy link

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.

@svick
Copy link
Contributor

svick commented Aug 5, 2020

This is a duplicate of microsoft/dotnet#474.

@333fred
Copy link
Member

333fred commented Aug 5, 2020

This isn't a language request, but rather a runtime request. I'm going to transfer this over there, and hopefully they'll have an answer or can point you in the right direction.

@333fred 333fred transferred this issue from dotnet/csharplang Aug 5, 2020
@Dotnet-GitSync-Bot
Copy link
Collaborator

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added the untriaged New issue has not been triaged by the area owner label Aug 5, 2020
@Gnbrkm41
Copy link
Contributor

Gnbrkm41 commented Aug 5, 2020

Does .NET Core runtime itself do any localisation for the exception message? Most of the exception messages I'm seeing doesn't seem to be in my Windows UI language (Korean) and even if they are they seem to originate from Windows API or something.

@mangod9 mangod9 added area-System.Globalization question Answer questions and provide assistance, not an issue with source code or documentation. labels Aug 6, 2020
@ghost
Copy link

ghost commented Aug 6, 2020

Tagging subscribers to this area: @tarekgh, @safern, @krwq
See info in area-owners.md if you want to be subscribed.

@danmoseley
Copy link
Member

@ChirChiara what version of.NET are you using?.NET Core's own resources aren't currently localized.

What strings are getting logged and which code do they come from?

@tarekgh tarekgh added this to the Future milestone Aug 6, 2020
@tarekgh
Copy link
Member

tarekgh commented Aug 6, 2020

Also you can force English resources if you set CultureInfo.CurrentUICulture to something like en-US Culture. Also, you can use the machine translation to translate the localized string.

@Chiramisu
Copy link
Author

For the record, I'm presently stuck with .NET Framework 3.5 on the app in question.

@333fred Thanks for moving it, I wasn't sure.
@svick I think that duplicate you referenced may also be in the wrong area if indeed it is a duplicate.

@tarekgh I definitely don't want machine translation because that quickly becomes a game of "operator" where details can be lost in translation. Also, CultureInfo.CurrentUICulture does not work in my case because this did not become read-write until 4.6.

@tarekgh
Copy link
Member

tarekgh commented Aug 6, 2020

Also, CultureInfo.CurrentUICulture does not work in my case because this did not become read-write until 4.6.

You can use Thread.CurrentThread.CurrentUICulture instead.
Also, you can set CultureInfo.DefaultThreadCurrentUICulture too to ensure other created threads later will get the english culture.

Side point, if you are talking about the full framework, then it is very unlikely such request will get added there. .NET core is our focus of future development and the full framework still supported but it is feature complete.

@tarekgh tarekgh removed the untriaged New issue has not been triaged by the area owner label Aug 7, 2020
@Chiramisu
Copy link
Author

Yes on the first point, no on the second. CultureInfo.DefaultThreadCurrentUICulture is not available in my version, and Thread.CurrentThread.CurrentUICulture comes with a security warning, but will probably be my only option for now.

I agree with you that my code base will inevitably need to be updated, and that is planned for the future, but even in modern versions of .NET I don't see this ability to declare Exceptions be loggable in the language of your choice. I want to maintain that my users see the errors in whatever language they prefer, but the logs are belong to me (intentional grammatical error) and I need them in English. 😋

@tarekgh
Copy link
Member

tarekgh commented Aug 7, 2020

CultureInfo.DefaultThreadCurrentUICulture is not available in my version

CultureInfo.DefaultThreadCurrentUICulture is supported since version 4.5. are you running on 4.0? if so, definitely you need to upgrade.

@Chiramisu
Copy link
Author

Chiramisu commented Aug 7, 2020

3.5 SMH 🤦🏼‍♂️ Still trying to convince the PMs to let go of Windows XP support. 😫 I'm holding onto hope that we can finally do that on our next Major rev.

@tarekgh
Copy link
Member

tarekgh commented Aug 7, 2020

I hope you'll succeed with your mission :-)

do you mind if we close this issue as there is no action required here? feel free to send more questions if you need help with any issue you face.

@Clockwork-Muse
Copy link
Contributor

I want to maintain that my users see the errors in whatever language they prefer, but the logs are belong to me (intentional grammatical error) and I need them in English.

... permanently set the UI culture as English, and then explicitly set the culture when doing all formatting/ToString()-ing? Although I'm not sure how that would affect some right-to-left text UI elements. Otherwise, are you saying that you're displaying raw exceptions to users?

@Chiramisu
Copy link
Author

I don't believe I got an answer to my question though; or did I miss it? Does anything in the .NET multiverse natively support logging Exceptions (for Developer use) in English?

@tarekgh
Copy link
Member

tarekgh commented Aug 7, 2020

Does anything in the .NET multiverse natively support logging Exceptions (for Developer use) in English?

There is nothing you can do without forcing the UI language on the app/running environment to English to guarantee getting English. Does the exception messages you are getting are the framework messages or app messages?

@Chiramisu
Copy link
Author

Framework of course. My app doesn't rely on any other third party APIs etc, so all app messages are written by me and already in English. It's the Framework that is generating Exceptions in the native culture of the client machine.

@tarekgh
Copy link
Member

tarekgh commented Aug 9, 2020

One other idea you may try is to uninstall the .NET Framework language packs from the machine. I don't have handy Windows XP machine but I believe you can do that from Control Panel, Add Or Remove programs options.

@Zymlex
Copy link

Zymlex commented Oct 10, 2020

One other idea you may try is to uninstall the .NET Framework language packs from the machine.

🤦‍♂️
The use of implicit localization of the program, and even more of the localization of exceptions (which only developers see), was the most idiotic idea.

@tarekgh
Copy link
Member

tarekgh commented Oct 10, 2020

@Zymlex my suggestion was specific workaround for @Chiramisu case based on my question #40427 (comment) and not intended to be for any situation. If you have any constructive feedback, please share it.

@ghost
Copy link

ghost commented Jul 20, 2022

Tagging subscribers to this area: @dotnet/area-system-resources
See info in area-owners.md if you want to be subscribed.

Issue Details

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.

Author: Chiramisu
Assignees: -
Labels:

question, area-System.Resources

Milestone: Future

@ericstj
Copy link
Member

ericstj commented Jul 20, 2022

~~ For Exception.ToString(CultureInfo) can you do this, using @AaronRobinsonMSFT's new API? ~~

The workaround shared previously did not work well. A new workaround is here https://gist.github.com/ericstj/c72b90b0c12f86b7918850ee276fac3b but will only work if the Windows machine is English or has the English language pack installed.

@tarekgh
Copy link
Member

tarekgh commented Jul 20, 2022

@ericstj I am not sure if the proposed code is going to work. Windows depends on the thread locale. Setting Thread.CurrentThread.CurrentCulture is not going to affect Windows thread locale as I understand.

@ericstj
Copy link
Member

ericstj commented Jul 20, 2022

I see, so instead of that we'd need to PINvoke to SetThreadLocale, maybe I can update the sample.

@AaronRobinsonMSFT
Copy link
Member

@tarekgh Did we miss an opportunity here to create a culture overload for that API?

public static class Marshal
{
    public static string GetPInvokeErrorMessage(int error, CultureInfo cultureInfo);
}

@ericstj
Copy link
Member

ericstj commented Jul 20, 2022

Adding that overload might be interesting. It would have made this extension method easier to implement. Updated my sample above to use SetThreadLocale (note it requires the new source generator as well). Still not sure if this is actually changing the behavior of FormatMessage. At this point one could just as well have the extensions method call FormatMessage itself.

@tarekgh
Copy link
Member

tarekgh commented Jul 20, 2022

I see, so instead of that we'd need to PINvoke to SetThreadLocale, maybe I can update the sample.

Yes, I think this will work.

Did we miss an opportunity here to create a culture overload for that API?

I think such overload will be helpful.

@AaronRobinsonMSFT
Copy link
Member

@tarekgh and @ericstj Filed #72546 for .NET 8

/cc @danmoseley

@tarekgh
Copy link
Member

tarekgh commented Jul 20, 2022

I just learned that Windows localized machines is not guaranteed to have English resources. For such situation there will not be anyway getting the English resources with Win32Exeption.

@AaronRobinsonMSFT

This comment was marked as outdated.

@TobiasKnauss
Copy link

I just learned that Windows localized machines is not guaranteed to have English resources. For such situation there will not be anyway getting the English resources with Win32Exeption.

This means we may create a request for the Windows development team, aiming at always including English resources.

@tfenise
Copy link
Contributor

tfenise commented Jul 21, 2022

To reproduce:

Console.WriteLine (new SocketException(10060).Message);

In my German Windows 10, I get a message in German language.

However, new SocketException(10060).ToString() starts with "System.Net.Sockets.SocketException (10060):", whatever the language in which the following message is in. Is that not helpful enough?

@cremor
Copy link

cremor commented Jul 21, 2022

@tfenise Are we expected to know or look up the error codes? If yes, is there a central page of all possible error codes?

Even if the answer is yes, that's still way more work to figure out the actual error when you have to read through a log file.

@tfenise
Copy link
Contributor

tfenise commented Jul 21, 2022

In the case of SocketException, the developer can just run new SocketException(10060).ToString() on their own machine (or write a program that looks for System.Net.Sockets.SocketException (###): in a log file and insert the error message in the preferred language) and see what the error code means. If this is still unsatisfactory, I think SocketException.ToString() (or Win32Exception.ToString()) can be modified to include not only the decimal or hexadecimal printing of the error code, but also Enum.GetName<SocketError>(the_error_code), which would be "TimedOut" for 10060. This change is certainly easier than adding some new API or even change in Windows localization.

@cremor
Copy link

cremor commented Jul 21, 2022

In the case of SocketException, the developer can just run new SocketException(10060).ToString() on their own machine (or write a program that looks for System.Net.Sockets.SocketException (###): in a log file and insert the error message in the preferred language) and see what the error code means.

That would still be a manual step. Not very convenient.

If this is still unsatisfactory, I think SocketException.ToString() (or Win32Exception.ToString()) can be modified to include not only the decimal or hexadecimal printing of the error code, but also Enum.GetName<SocketError>(the_error_code), which would be "TimedOut" for 10060. This change is certainly easier than adding some new API or even change in Windows localization.

That would certainly be a quick win that should be done.

@danmoseley
Copy link
Member

If this is still unsatisfactory, I think SocketException.ToString() (or Win32Exception.ToString()) can be modified to include not only the decimal or hexadecimal printing of the error code, but also Enum.GetName(the_error_code), which would be "TimedOut" for 10060. This change is certainly easier than adding some new API or even change in Windows localization.

Please open an issue if you want to suggest that for SocketException. It does sound useful. 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)

@danmoseley
Copy link
Member

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.

@TobiasKnauss
Copy link

@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 ();
  }
}

@danmoseley
Copy link
Member

@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.
image

I am not sure which headers ERRLOOK.EXE has aggregated. I do not know where the sources are.

@danmoseley
Copy link
Member

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.

@mlsomers
Copy link

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?

@tarekgh tarekgh added the untriaged New issue has not been triaged by the area owner label Mar 27, 2024
@dotnet-policy-service dotnet-policy-service bot removed the untriaged New issue has not been triaged by the area owner label Mar 27, 2024
@juwens
Copy link

juwens commented Oct 10, 2024

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).

@Clockwork-Muse
Copy link
Contributor

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).

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-System.Resources question Answer questions and provide assistance, not an issue with source code or documentation.
Projects
No open projects
Development

No branches or pull requests