0

I have been trying to find a way to enforce unique error messages in our application. That is, I want to know exactly which line of code produced the error, so when users contact support we know the source of the error from the message logged.

I found this question which gave me a mechanism (using guids to enforce uniqueness), but one question remains: How do I prevent copy/paste duplication? - specifically, a developer copying the logging line itself:

Log(<guid>, errorMessage);

In this case the guid would be duplicated and no longer useful for identifying the unique line which generated the error.

The one idea I have had which would actually work is writing a tool to be run by our build server which would parse the code for error message guids, keep a list, and fail the build on a duplicate. I'm wondering if there is a cleaner solution.

Other things I've considered:

  • There are various ideas using some sort of central listing of error messages, but I have not found one which addresses the copy/paste issue.

  • There are also a number of schemes which would require keeping a manual list in some way. I don't want to pursue something like this as it creates the possibility of discrepancies between the list & production code.

  • I've also seen suggestions to use the stack trace, but I'm a bit hesitant to do that for security & performance reasons.

Joe Schrag
  • 855
  • 8
  • 23
  • Downvoters care to comment? – Joe Schrag May 09 '18 at 16:35
  • "I have been trying " so show Your attempts. CODE is international language for programmers – Jacek Cz May 09 '18 at 16:35
  • Put the error codes in a hashset with the error code as the key. You can't have identical keys. This will give you a runtime error. Then write some unit tests to validate that there are no runtime errors. Fail your builds when unit tests fail. – Daniel Mann May 09 '18 at 16:37
  • @JacekCz As stated in the question, I have looked at a number of C++ logging libraries. None seem to have this feature. I have searched StackOverflow, which gave me the guid idea, but I have not found a mechanism to prevent copy/paste. Other efforts have included Googling for other ideas. Something I'm missing? – Joe Schrag May 09 '18 at 16:38
  • agree, C# has totally differet object structure, identyfication comparing to C++. Many language idioms are not portable – Jacek Cz May 09 '18 at 16:39
  • @Daniel Mann That is the "write it myself" scenario. Certainly an option, but I also want this control across modules so it would have to run from my build server. – Joe Schrag May 09 '18 at 16:39
  • @JosephSchrag My suggestion uses tools and techniques that are readily available in both languages to solve the problem. This does not require building additional external tooling. – Daniel Mann May 09 '18 at 16:42
  • @DanielMann The scenario I am getting at is the developer copying the logging line itself ...Log(, logMessage); ... I can pull that guid from a hashset, but it doesn't prevent the line from being copied. I can write a tool as described before, but it would be a script running from Jenkins because this needs to cover dozens of separate projects. I don't see how language features are saving me here. – Joe Schrag May 09 '18 at 16:47
  • I didn't down-vote, but given the 3 close votes for "unclear what you're asking", I'd guess "it's unclear" would be the downvote reason as well. Perhaps a better definition of exactly what you mean by "enforce unique error codes" would be helpful. My first thought when reading your question was, if all error codes are unique why do you need them at all? Surely some failures are due to the same issue, and would have the same error code as some other failure? – Rufus L May 09 '18 at 16:49
  • @JosephSchrag I'm suggesting that you centralize your error codes and messages in a data structure that enforces uniqueness. Log statements would take a guid and retrieve the appropriate error message from the hash collection. Unit tests against the library containing the hash collection will ensure that the application doesn't suffer from runtime errors as a result of key collision. – Daniel Mann May 09 '18 at 16:53
  • You are bound to have duplicate errors. What if the same `IndexOutOfBounds` or `YouCantDoThisHere` exception occurs in two different places? Not all errors will be unique. (Yes the place they happen are unique, but imagine how long the list would be to keep track of all the unique places an error can occur). – Jimenemex May 09 '18 at 16:59
  • @RufusL I've edited the question. Is it any clearer now? – Joe Schrag May 09 '18 at 17:00
  • @DanielMann I understand the approach you are suggesting, but I don't understand how it prevents inline log statements from using the same guid to lookup. – Joe Schrag May 09 '18 at 17:00
  • @Jimenemex That is the crux of the problem :). I am looking for a way to make the error unique to the line where it occurred. – Joe Schrag May 09 '18 at 17:03
  • Only that you want the line number where the error occurred? Is that what you want as part of the logging - the stack trace info? Why don't you just log that as well instead of trying to enforce unique ids? – Rufus L May 09 '18 at 17:35
  • Ok, this has been put on hold as unclear. I've made a few edits in an attempt to clarify. Can someone tell me which portions are still unclear? – Joe Schrag May 09 '18 at 20:10
  • I'm looking at this in the Re-open queue and I'm seeing a "tool request", which would make the question off-topic, even if it is now clear (and an interesting question). FWIW someone commented about using the identifier as a unique key in a list (or database, or whatever). Seems to me that would be the best approach. As far as finding an existing tool - that is off-topic for the site. Of course, if someone knows of one and posts in Comments before this is removed from the site, you have what you're lookng for... – Cindy Meister May 10 '18 at 06:03
  • @CindyMeister Thanks for taking a look! I've reworded the question to eliminate the tool request. As far as storing the unique keys in a list, I don't think that solves the issue. What is to prevent two logging statements from using the same unique key? The key would be unique in the list, but two logging statements could reference it. – Joe Schrag May 10 '18 at 15:40
  • @JacekCz I've added a list of options already considered to the question. No code has been written because I have not found anything I think would work on a conceptual level. No point in writing code. – Joe Schrag May 10 '18 at 15:42
  • @JosephSchrag I'm more database-oriented, understand, so when I think about something like this, I set it up so that the *database* doesn't allow a duplicate in that field. If people are adding something to a list or log or whatever, using your code, can't your code check whether the entry already exists in the list and not allow adding it? – Cindy Meister May 10 '18 at 16:04
  • @CindyMeister I see. No, in this case, the code isn't adding the unique id to the collection, it would only be referencing (a lookup). So, I'm looking for a compile-time means of preventing lookups from two different locations in the code. Even in a case where the log statements were adding to unique ids to a collection, I would need some sort of test which hit every log statement in the program. This would not be practical and would not catch errors until runtime. – Joe Schrag May 10 '18 at 17:39

1 Answers1

1

I don't know if this is really what you're looking for, but you can include the file, method, line number (and other things) in your log message without needing a unique number that you would later search the source code for if you make use of the System.Diagnostics.StackTrace class. This way, even if there's a copy/paste violation, you still know exactly where the call to Log came from.

Here's a simplified example that returns the file name, method signature, and line number of a stack trace item. Note that this code finds the stack trace item for a call to the "Log" method and returns the next one. That will be more clear shortly:

using System.Diagnostics;  // Needed for the StackTrace class

private static string GetStackTraceInfo()
{
    var stackTrace = new StackTrace(true).GetFrames();

    // Find the item just after the call to teh 'Log' method:
    var item = stackTrace?
        .SkipWhile(st => !st.GetMethod().Name.Equals("Log"))
        .Skip(1)
        .FirstOrDefault();

    return item == null
        ? string.Empty
        : string.Format("{0} => {1}, line #{2}", Path.GetFileName(item.GetFileName()),
            item.GetMethod(), item.GetFileLineNumber());
}

Here's the Log method that enforces the stack trace info added to a log (and this is the method name that we were searching for in the code above):

private static void Log(int id, string message)
{
    Console.WriteLine($"Error #{id}: {message} ({GetStackTraceInfo()})");
}

And an example usage:

private static void Main()
{
    DoSomething();
    DoSomethingElse();

    GetKeyFromUser("\nDone! Press any key to exit...");
}

private static void DoSomething()
{
    Log(1000, "I copied/pasted this error message from somewhere!");
}

private static void DoSomethingElse()
{
    Log(1000, "I copied/pasted this error message from somewhere!");
}

Output

enter image description here

Rufus L
  • 36,127
  • 5
  • 30
  • 43
  • I don't want to do this for security reasons. I don't think publishing the internal structure of your code is a great idea. – Joe Schrag May 09 '18 at 20:07
  • 1
    Ok, well this shows how to get the line number, and you don't need to use the method signature, so if there is some way to map the code files to numbers (like a dictionary where Program.cs = 0001), you could do something like "0001.81" which would mean line 81 in Program.cs. Good luck! – Rufus L May 09 '18 at 21:23
  • Well, that is creative. Downside is maintaining a list of what files match which numbers, but it is a possibility I hadn't considered. – Joe Schrag May 09 '18 at 21:34
  • 1
    Yeah, I first thought maybe `"Program.cs".GetHashCode()` would work, but hash codes are not reliably unique and can change (for the same string) between framework versions. Also consider that, without stack trace information, you won't know which methods called the method that failed. So having a unique error per line may not be as helpful as we would like. You might want to take a look at [this question and answers](https://softwareengineering.stackexchange.com/questions/364918/making-code-findable-by-using-globally-unique-message-ids) if you haven't already. – Rufus L May 09 '18 at 21:40