Logging

The Coyote runtime provides a Microsoft.Coyote.IO.ILogger interface for logging so that your program output can be captured and included in coyote test tool output logs. A default Logger is provided and can be accessed like this:

Programming Model Accessing the Logger
Task based program Microsoft.Coyote.Actors.RuntimeFactory.Create().Logger
Actor based program Microsoft.Coyote.Runtime.RuntimeFactory.Create().Logger
Actor.Logger, StateMachine.Logger, Monitor.Logger

The default Logger is a ConsoleLogger which is used to write output to the System.Console. You can provide your own implementation of Microsoft.Coyote.IO.ILogger by setting the Logger property on the ICoyoteRuntime or IActorRuntime.

The IActorRuntime also provides a higher level logging interface called IActorRuntimeLog for logging Actor and StateMachine activity.

Example of custom ILogger

It is possible to replace the default logger with a custom one. The following example captures all log output in a StringBuilder:

public class CustomLogger : ILogger
{
    private StringBuilder StringBuilder;

    public TextWriter TextWriter => throw new NotImplementedException();

    public CustomLogger()
    {
        this.StringBuilder = new StringBuilder();
    }

    public void Write(string value)
    {
        this.Write(LogSeverity.Informational, value);
    }

    public void WriteLine(string value)
    {
        this.WriteLine(LogSeverity.Informational, value);
    }

    public void Write(string format, params object[] args)
    {
        this.Write(LogSeverity.Informational, format, args);
    }

    public void WriteLine(string format, params object[] args)
    {
        this.WriteLine(LogSeverity.Informational, format, args);
    }

    public void Write(LogSeverity severity, string format, object[] args)
    {
        this.Write(severity, string.Format(format, args));
    }

    public void WriteLine(LogSeverity severity, string format, object[] args)
    {
        this.WriteLine(severity, string.Format(format, args));
    }

    public void Write(LogSeverity severity, string value)
    {
        switch (severity)
        {
            case LogSeverity.Informational:
                this.StringBuilder.Append("<info>" + value);
                break;
            case LogSeverity.Warning:
                this.StringBuilder.Append("<warning>" + value);
                break;
            case LogSeverity.Error:
                this.StringBuilder.Append("<error>" + value);
                break;
            case LogSeverity.Important:
                this.StringBuilder.Append("<important>" + value);
                break;
        }
    }

    public void WriteLine(LogSeverity severity, string value)
    {
        this.Write(severity, value);
        this.StringBuilder.AppendLine();
    }

    public override string ToString()
    {
        return this.StringBuilder.ToString();
    }

    public void Dispose()
    {
        // todo
    }
}

To replace the default logger, call the following IActorRuntime method:

runtime.Logger = new CustomLogger();

The above method replaces the previously installed logger with the specified one and returns the previously installed logger.

Note that the old Logger might be disposable, so if you care about disposing the old logger at the same time you may need to write this instead:

using (var oldLogger = runtime.Logger)
{
   runtime.Logger = new CustomLogger();
}

You could write a custom ILogger to intercept all logging messages and send them to an Azure Log table, or over a TCP socket.

IActorRuntimeLog

The default IActorRuntimeLog implementation is the ActorRuntimeLogTextFormatter base class which is responsible for formatting all Actor and StateMachine activity as text and writing that out using the installed Logger.

You can add your own implementation of IActorRuntimeLog or ActorRuntimeLogTextFormatter using the RegisterLog method on IActorRuntime. This is additive so you can have the default ActorRuntimeLogTextFormatter and another logger running at the same time. For example, see the ActorRuntimeLogGraphBuilder class which implements IActorRuntimeLog and generates a directed graph representing all activities that happened during the execution of your actors. See activity coverage for an example graph output. The coyote test tool sets this up for you when you specify --graph or --coverage activity command line options.

The --verbosity command line option can also affect the default logging behavior. When --verbosity is specified all log output is written to the System.Console by default, but you can specify different levels of output using --verbosity quiet to get no output, --verbosity minimal to see only error messages and --verbosity normal to get errors and warnings. This can produce a lot of output especially if you run many testing iterations. It is usually more useful to only capture the output of the one failing iteration in a log file and this is done automatically by the testing runtime when --verbosity is not specified.

See IActorRuntimeLog API documentation.

Example of a custom IActorRuntimeLog

You can also implement your own IActorRuntimeLog. The following is an example of how to do this:

internal class CustomLogWriter : IActorRuntimeLog
{
  // Callbacks on runtime events

  public void OnCreateActor(ActorId id, ActorId creator)
  {
    // Override to change the behavior.
  }

  public void OnEnqueueEvent(ActorId id, Event e)
  {
    // Override to change the behavior.
  }

  // More methods to implement.
}

You can then register your new implementation using the following IActorRuntime method:

runtime.RegisterLog(new CustomLogWriter());

You can register multiple IActorRuntimeLog objects in case you have loggers that are doing very different things. The runtime will invoke each callback for every registered IActorRuntimeLog.

Customizing the ActorRuntimeLogTextFormatter

You can modify the format of text log messages by providing your own ActorRuntimeLogTextFormatter. You can subclass the default ActorRuntimeLogTextFormatter implementation to override its default behavior. The following is an example of how to do this:

internal class CustomLogFormatter : ActorRuntimeLogTextFormatter
{
  // Methods for formatting log messages

  public override void OnCreateActor(ActorId id, ActorId creator)
  {
    // Override to change the text to be logged.
    this.Logger.WriteLine("Hello!");
  }

  public override void OnEnqueueEvent(ActorId id, Event e)
  {
    // Override to conditionally hide certain events from the log.
    if (!(e is SecretEvent))
    {
      base.OnEnqueueEvent(id, e);
    }
  }

  // More methods that can be overridden.
}

You can then replace the default ActorRuntimeLogTextFormatter with your new implementation using the following IActorRuntime method:

runtime.RegisterLog(new CustomLogFormatter());

The above method replaces the previously installed ActorRuntimeLogTextFormatter with the specified one.

See ActorRuntimeLogTextFormatter documentation.