How to increase/decrease indenting when stepping i

2019-07-24 16:18发布

问题:

TL;DR - jump to last paragraph

Background

I'm performing some data driven testing and using the log file as one of the testing outputs. It works something like this-

  • Read first file in folder
    • Process first line and convert to a test
    • Run Test
      • Perform Validation 1
      • ...
    • ...
  • Read next file
  • Etc.

My log file reflects this:

INFO - Start RunAllFilesInFolder  
INFO - File1:
INFO -      Some info
INFO -      Executing Test 1  
INFO -          Validation A result
INFO -          ...
INFO -      ...  
INFO - File2:  
...

At the moment I use/call Log 4 net like this-

static class LogHelper
{
    internal static readonly log4net.ILog Log = log4net.LogManager.GetLogger
            (System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
}

// another file
using static Some.Namespace.LogHelper;
class SomeClass
{
     Log.Info($"\t\tExecuting {t.Number}-{t.Name}");
}

Where indent = "\t", "\t\t" or "\t\t\t" depending on the level the test is in.


Is there a way to refactor the call to LogHelper.Log so that it takes into account a static "indent" that can be increased/decreased as I step into the different test levels without explicitly specifying the indent in each call?

I.e. Be able to call where applicable something like

Log.Indent.Increase();

Or

Log.Indent.Decrease();

And replace the above call to log simply with -

Log.Info($"Executing {t.Number}-{t.Name}");

回答1:

You could use the stack trace length, by counting the number of new lines:

int someSensibleMinimum = 3; //something that works for you
int count = Environment.StackTrace.Count(a => a=='\n');
var indent = new string('\t', Math.Max(0, count - someSensibleMinimum));

Note that in Release it may behave differently:

However, the StackTrace property might not report as many method calls as expected due to code transformations that occur during optimization.

Or, you could automagically calculate the length using this approach (pseudocode):

int count = Environment.StackTrace.Count(a => a=='\n');
Look for stack length in dictionary<int,string> (length to indent string)
If found use it.
If not found, find the largest entry in dictionary where key < count
add new entry to dictionary one tab char longer

In code:

public sealed class IndentTracker
{
    private readonly ThreadLocal<Dictionary<int, string>> _dictionaryLocal =
        new ThreadLocal<Dictionary<int, string>>(() => new Dictionary<int, string>());

    public string GetIndent()
    {
        Dictionary<int, string> dictionary = _dictionaryLocal.Value;

        int count = Environment.StackTrace.Count(a => a == '\n');

        if (!dictionary.Any())
        {
            string initialIndent = string.Empty;
            dictionary.Add(count, initialIndent);
            return initialIndent;
        }

        string indent;
        if (dictionary.TryGetValue(count, out indent))
            return indent;

        string last = dictionary.OrderByDescending(k => k.Key).First(k => k.Key < count).Value;
        string newIndent = last + '\t';
        dictionary.Add(count, newIndent);
        return newIndent;
    }
}

This works when log in increasing depth order. For example, it fails if you log at stack depth 10, then 5 (without previously logging at stack depth 5 then 10).