Logs can be used to store pretty much anything to your interface. Let’s create our first log.

unify.log(x=0, y={"a": [1, 2, 3]}, msg="hello", score=0.123)

Logs appear as rows in tables, and fields appear as columns

Logging Contexts

If you want to incrementally update the same log (row), then you can use with unify.Log(): to create a logging context, and all calls to unify.log will then update this existing log.

with unify.Log():
    unify.log(x=0)
    unify.log(y=1)
    unify.log(z=2)

These can also be arbitrarily nested. Note that the most recently created log is always shown at the top of the table (not most recently updated).

with unify.Log():
    unify.log(x=0)
    with unify.Log():
        unify.log(x=1)
        unify.log(y=2)
        unify.log(z=3)
    unify.log(y=1)
    unify.log(z=2)

We can also pass arguments, which will also be part of the same log.

with unify.Log(x=0, y=1):
    unify.log(z=2)

Using with unify.Log() is the best practice, as this avoids the need to pass explicit log handles throughout your program. This code uses explicit handles (each function has a log argument):

def fn(b, c, log):
    log.add_entries(b=b, bx2=b*2, c=c)
    dsqrd = inner_fn(d=b+c, log=log)
    log.add_entries(dsqrd=dsqrd)
    return dsqrd
    
def inner_fn(d, log):
    log.add_entries(d=d)
    return d**2

a, b, c = 1, 2, 3
log = unify.log(a=a)
fn(b=b, c=c, log=log)

This code logs the same data, but avoids the need to pass the Log instance around:

def fn(b, c):
    unify.log(b=b, bx2=b*2, c=c)
    dsqrd = inner_fn(d=b+c)
    unify.log(dsqrd=dsqrd)
    return dsqrd

def inner_fn(d):
    unify.log(d=d)
    return d**2

a, b, c = 1, 2, 3
with unify.Log(a=a):
    fn(b=b, c=c)

Both result in the same log: