Skip to content

(v0.x) Custom Tracing

Ben Davies edited this page Feb 11, 2021 · 1 revision

Customizing tracing support

With regards to tracing of different types of objects, Trait::Traced is fully extensible. For instance, if you have a role that you don't want to have traced with the default tracing support for types, it's possible to add your own tracing support without modifying the library itself.

Traced is a class that implements the API with which all types of traces are generated. The subtypes of this that exist in Trait::Traced are fairly generic, but Traced subtypes that are more tailored to one use case are reasonable to write.

Defining custom tracing types

There are three steps to implementing tracing support with Traced. Traced::Stash, which implements tracing for stash accesses, will be used as an example from hereon out.

Defining the trace output

Instances of Traced are used to generate trace output. There are several attributes that this type must be constructed with, which are best handled using a slurpy hash:

method new(::?CLASS:_: *%rest --> ::?CLASS:D) {
    self.bless: |%rest
}

Out of the attributes of Traced, the only two that would ordinarily become relevant when implementing custom tracing support are Mu $.result and Mu $.exception. Each trace must have either a result value or an exception that was thrown by the traced operation in order to fill these out.

There are a handful of methods that must be implemented by Traced subtypes (along with one optional one) to generate trace output:

  • method colour(::?CLASS:D: --> Int:D)

Returns the colour code for the trace's key (think SGM ANSI escape sequences).

  • method category(::?CLASS:D: --> Str:D)

Returns the category of the trace.

  • method type(::?CLASS:D: --> Str:D)

Returns the type of the trace.

  • method what(::?CLASS:D: --> Str:D)

Returns the title of the trace.

  • method entries(::?CLASS:D: --> Iterable:D) (optional)

Returns the entries of the trace. By default, this will be an empty list.

Generating traces with wrap and trace

proto method wrap(::?CLASS:U: | --> Mu)

This method is used to set up tracing for types of operations on an object. Its arguments need to include an object to be made traceable; the remainder should be any additional information needed to output a trace. In Traced::Stash, this mixes in a role to a Stash:D $stash that implements AT-KEY, BIND-KEY, and ASSIGN-KEY methods that invoke Traced::Stash.trace:

proto method trace(::?CLASS:U: |rest --> Mu)

This method is used to perform a traced operation. The proto will take the return value or exception thrown by any multi, automatically generating the trace itself by passing the arguments given to Traced.new. In Traced::Stash, this calls the untraced AT-KEY, BIND-KEY, and ASSIGN-KEY methods of a stash.

Adding support for custom tracing types

Once any Traced types needed exist, there needs to be a way to use them with the is traced trait. This can be done by adding a multi to &trait_mod:<is> with a type more specific than the following, default multis:

multi sub trait_mod:<is>(Variable:D $variable, Bool:D :traced($)! where ?*)
multi sub trait_mod:<is>(Parameter:D $parameter, Bool:D :traced($)! where ?*)
multi sub trait_mod:<is>(Routine:D $routine is raw, Bool:D :traced($)! where ?*)
multi sub trait_mod:<is>(Method:D $method is raw, Bool:D :traced($)! where ?*)
multi sub trait_mod:<is>(Attribute:D $attribute is raw, Bool:D :traced($)! where ?*)
multi sub trait_mod:<is>(Mu \T, Bool:D :traced($)! where ?*)

The multi(s) added should invoke the wrap method of any Traced subtypes needed to make your type of object traceable. Tracing for stashes is handled using a more specific Mu multi like so:

# Kind[Metamodel::Stashing] is roughly equivalent to *.HOW ~~ Metamodel::Stashing
multi sub trait_mod:<is>(Mu \T where Kind[Metamodel::Stashing], Bool:D :traced($)! where ?*) is export {
    Traced::Stash.wrap: T.WHO;
    nextsame;
}

Note the nextsame. Tracing can be made composable using nextsame and other dispatch routines!