Skip to content
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

[FEATURE]- Major update to Console Debugging (Discussion encouraged) #56

Open
ndorin opened this issue Sep 16, 2020 · 6 comments
Open
Assignees
Labels
enhancement New feature or request help wanted Extra attention is needed planning question Further information is requested
Milestone

Comments

@ndorin
Copy link
Contributor

ndorin commented Sep 16, 2020

Is your feature request related to a problem? Please describe.
The current console debugging mechanisms in the Debug static class has reached the limits of it's usefulness. The global debug level setting, especially when set to level 2 results in a quantity of messages that affects usability and readability in a negative way. We are at the point where we need effective ways to selectively enable and disable debug messages on a per device basis.

The idea here so far is that we would add console commands that allow users CRUD "Debug Contexts". Each DebugContext would contain the information about what devices are part of that context and what debug level each device has. Then debugging can be enabled/disabled for each context.

Describe the solution you'd like

  • Enable debugging on individual device basis
    • Ability to easily turn on off per device
    • Ability to set the level for each device
  • Debugging contexts (groups of devices that get their debug settings set collectively)
    • Contexts should contain a collection of device keys and each device should be able to have it's own level
    • Should a device be able to be part of multiple contexts? Probably. The device itself it told it's highest debugging level and that determines which messages print
  • Consider future web app visual interface for debugging via WS API or similar?

Challenges

  • Aim to reduce runtime overhead for computation of whether to print statements or not.
  • Maintain maximum flexibility while optimizing user friendliness

Implementation

  • Create IDebuggable interface
  • Add overloads for Console that match Debug.Console() methods
  • Something like this:
public interface IDebuggable : IKeyed
{
    int DebugLevel { get; }
    void Console(uint level, string format, params object[] items);
    void Console(uint level, IKeyed dev, string format, params object[] items);
    void Console(uint level, ErrorLogLevel errorLogLevel, string format, params object[] items);
    void ConsoleWithLog(uint level, string format, params object[] items);
    void ConsoleWithLog(uint level, IKeyed dev, string format, params object[] items);
    void LogError(ErrorLogLevel errorLogLevel, string str);
}
  • Implement IDebuggable on Device
    • Have IDebuggable methods call corresponding methods in Debug static class (maybe via interface extension methods?)
  • Add console methods to Debug class to create/remove contexts and add/remove devices to contexts at specified levels
    • The action of adding/removing a device from a context should set the DebugLevel value on any device that is IDebuggable
  • Serious Console interaction involved
    • Create a "pretty" console interaction interface to show current debug context status and what devices belong to what contexts at what level(s)
@ndorin ndorin added enhancement New feature or request help wanted Extra attention is needed question Further information is requested labels Sep 16, 2020
@andrew-welker andrew-welker added this to the 1.1.0 milestone Sep 16, 2020
@bitm0de
Copy link

bitm0de commented Sep 16, 2020

Here's some example code with a minimal level of abstraction for simplicity. (I wrote this as a normal console application since it's easier to toy with the code when you can test any code changes in less than a few seconds.)

public interface IDebugContext
{
    int DebugLevel { get; set; }
    bool DebugEnabled { get; set; }
}

public interface IDebugger
{
    bool GlobalDebugEnable { get; set; }
    int GlobalMaximumDebugLevel { get; set; }
    void Write(IDebugContext ctx, int level, string data);
}

public class ConsoleDebugger : IDebugger
{
    public bool GlobalDebugEnable { get; set; }
    public int GlobalMaximumDebugLevel { get; set; }

    public void Write(IDebugContext ctx, int level, string data)
    {
        if (GlobalDebugEnable && ctx.DebugEnabled && level <= Math.Min(ctx.DebugLevel, GlobalMaximumDebugLevel))
            Console.WriteLine("[DEBUG] {0} {1}: {2}", ctx.GetType().Name, level, data);
    }
}

public class DebuggableObject : IDebugContext
{
    private readonly IDebugger _debugger;

    public int DebugLevel { get; set; }
    public bool DebugEnabled { get; set; }

    public DebuggableObject(IDebugger debugger)
    {
        _debugger = debugger;
    }

    public void Test()
    {
        _debugger.Write(this, 1, "Level 1 debug message");
        _debugger.Write(this, 2, "Level 2 debug message");
        _debugger.Write(this, 3, "Level 3 debug message");
        _debugger.Write(this, 4, "Level 4 debug message");
        _debugger.Write(this, 5, "Level 5 debug message");

        Console.WriteLine();
    }
}

internal class Program
{
    private static readonly ConsoleDebugger _consoleDebugger = new ConsoleDebugger {
        GlobalMaximumDebugLevel = 4
    };

    public static void Main()
    {
        var obj1 = new DebuggableObject(_consoleDebugger) { DebugEnabled = true, DebugLevel = 1 };
        var obj2 = new DebuggableObject(_consoleDebugger) { DebugEnabled = true, DebugLevel = 3 };
        var obj3 = new DebuggableObject(_consoleDebugger) { DebugEnabled = true, DebugLevel = 5 };

        Console.WriteLine("OBJ 1: [Debug Level {0}]", obj1.DebugLevel);
        obj1.Test();

        Console.WriteLine("OBJ 2: [Debug Level {0}]", obj2.DebugLevel);
        obj2.Test();

        Console.WriteLine("OBJ 3: [Debug Level {0}]", obj3.DebugLevel);
        obj3.Test();

        Console.WriteLine("Done...");
        Console.ReadKey();
    }
}

image

Here you can associate each DebuggableObject with its own debug context. You can also have a global debug context set directly within the debugger class itself that takes precedence over any of the more scoped contexts. This way you can control things on a vast or more granular scale, whichever suits the requirements at troubleshooting time.

The benefit to attaching the context as part of the object itself is that you now have direct type information available via reflection if you need it. Reflection is slow though, so doing too much other than retrieving the name of the type may degrade performance at runtime.

This setup can branch out a few different ways... You can introduce a base class to encapsulate shared functionality between debuggers (i.e. the debug context checks), and you can even modify the IDebugContext interface to hold all the contextual properties within an object for better transferability. For instance, you could encapsulate the context information in an object and have that as a property of the interface, then introduce a few methods that would allow you to take a debug context object and assign that to overwrite or copy it to another object that implements IDebugContext.

@ndorin
Copy link
Contributor Author

ndorin commented Sep 22, 2020

We will be hosting a public Zoom meeting to encourage discussion on this topic on Thursday September 24th at 12p ET/3p PT. Details can be found on the Essentials Discord server.

@ndorin
Copy link
Contributor Author

ndorin commented Sep 24, 2020

Thoughts from Zoom discussion (September 24, 2020):

  • Add ability to load contexts from config
  • Print error log level with console message if specified
  • Modify console print prefix to include not only deviceKey, verbosity level and error log level (severity) if specified.
  • DebugSettings File:
    • Segregate contexts from state
    • State should include an object with the currently enabled context keys
    • A separate object would contain a collection of all devices with a level > 0
  • Implement a global debug expiry timer that gets modified by the last modification of any context

@andrew-welker
Copy link
Contributor

Is there a case to be made for storing the debug settings in a SQLite database instead of as a file on the processor?

@bitm0de
Copy link

bitm0de commented Oct 1, 2020

@andrew-welker There may be some inherent benefits if the database was structured properly. For instance, now you could have commands or methods that retrieve stored debug messages of a certain criteria (i.e. debug level, messages from a particular context, etc). The SQLite database could still be a file, but in that case you'd probably want to do it in transactions though to reduce database write operations. (It also allows you to fallback.)

@jtalborough
Copy link
Contributor

Tagging @jkdevito on this issue as I know he's been working on a solution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request help wanted Extra attention is needed planning question Further information is requested
Projects
None yet
Development

No branches or pull requests

4 participants