Transcripts

In this section we extend Cicada’s basic Logging functionality to support advanced use cases including detailed network logging, function tracing, and zero-knowledge proofs. We refer to these more detailed logs as transcripts, and generate them using the cicada.transcript module.

Tip

You’ll need a good working knowledge of the Python logging module to fully understand the following examples!

As always, you should initialize Python’s logging system at the beginning of a Cicada program, even if you don’t plan to use logging yourself:

[1]:
import logging
logging.basicConfig(level=logging.INFO)

Throughout this section, we’ll be working with the following Cicada program, which adds two numbers using additive secret sharing:

[2]:
import numpy

from cicada.additive import AdditiveProtocolSuite
from cicada.communicator import SocketCommunicator
from cicada.encoding import FixedPoint

def main(communicator):
    protocol = AdditiveProtocolSuite(communicator, order=127, encoding=FixedPoint(precision=2))
    a_share = protocol.share(src=0, secret=numpy.array(2), shape=())
    b_share = protocol.share(src=1, secret=numpy.array(3), shape=())
    c_share = protocol.add(a_share, b_share)
    c = protocol.reveal(c_share)

SocketCommunicator.run(fn=main, world_size=3);

Note that we’re using a very small field (order=127) and a very low-precision encoding (precision=2) to make the output values easier to read.

Transcript Setup

Let’s begin by setting-up the special transcript logger provided with Cicada:

[3]:
from cicada import transcript

def main(communicator):
    handler = logging.StreamHandler()
    handler.setFormatter(transcript.Formatter())
    handler.addFilter(transcript.HideAllFunctions())
    transcript.logger.addHandler(handler)

    protocol = AdditiveProtocolSuite(communicator, order=127, encoding=FixedPoint(precision=2))
    a_share = protocol.share(src=0, secret=numpy.array(2), shape=())
    b_share = protocol.share(src=1, secret=numpy.array(3), shape=())
    c_share = protocol.add(a_share, b_share)
    c = protocol.reveal(c_share)

with transcript.record():
    SocketCommunicator.run(fn=main, world_size=3);

First, we add a stream handler to the transcript logger; this will direct the transcript outputs to the console. Second, instead of the default log formatter provided by Python, we specify the custom transcript.Formatter provided by Cicada, which is designed to work with the rich set of log records and custom fields generated during transcript.

Warning

Transcript logging generates many special fields that require transcript.Formatter for correct output … your program will likely throw exceptions if you try to display transcript events with other formatters. For this reason, the transcript logger has propagation turned-off by default, so that transcript outputs are managed separately from other, more general log messages.

Finally, we call transcript.record before running the rest of the program to enable transcription.

Despite all this, the above example doesn’t produce any output because of the transcript.HideAllFunctions log filter, which we’ll be discussing in a moment. In the meantime, let’s produce some output using transcript.log to add application-specific messages to the transcript:

[4]:
def main(communicator):
    handler = logging.StreamHandler()
    handler.setFormatter(transcript.Formatter())
    handler.addFilter(transcript.HideAllFunctions())
    transcript.logger.addHandler(handler)

    transcript.log("Let's setup additive sharing!")
    protocol = AdditiveProtocolSuite(communicator, order=127, encoding=FixedPoint(precision=2))
    transcript.log("Let's share some secrets!")
    a_share = protocol.share(src=0, secret=numpy.array(2), shape=())
    b_share = protocol.share(src=1, secret=numpy.array(3), shape=())
    transcript.log("Let's add some secrets!")
    c_share = protocol.add(a_share, b_share)
    transcript.log("Let's reveal the results!")
    c = protocol.reveal(c_share)

with transcript.record():
    SocketCommunicator.run(fn=main, world_size=3);
Player 0: Let's setup additive sharing!
Player 1: Let's setup additive sharing!
Player 2: Let's setup additive sharing!
Player 0: Let's share some secrets!
Player 1: Let's share some secrets!
Player 2: Let's share some secrets!
Player 0: Let's add some secrets!
Player 2: Let's add some secrets!
Player 1: Let's add some secrets!
Player 0: Let's reveal the results!
Player 2: Let's reveal the results!
Player 1: Let's reveal the results!

Now, we see the output that originates directly from our script. Messages logged by the application can provide valuable context about what the program is doing at a high level, making it much easier to understand the rest of the transcript output.

Message Transcripts

Let’s generate more output. In some situations, you may want a detailed log of every message that Cicada sends over the network at runtime; this functionality is available using a pair of log filters, transcript.ShowSentMessages and transcript.ShowReceivedMessages:

[5]:
def main(communicator):
    handler = logging.StreamHandler()
    handler.setFormatter(transcript.Formatter())
    handler.addFilter(transcript.ShowSentMessages())
    handler.addFilter(transcript.ShowReceivedMessages())
    handler.addFilter(transcript.HideAllFunctions())
    transcript.logger.addHandler(handler)

    transcript.log("Let's setup additive sharing!")
    protocol = AdditiveProtocolSuite(communicator, order=127, encoding=FixedPoint(precision=2))
    transcript.log("Let's share some secrets!")
    a_share = protocol.share(src=0, secret=numpy.array(2), shape=())
    b_share = protocol.share(src=1, secret=numpy.array(3), shape=())
    transcript.log("Let's add some secrets!")
    c_share = protocol.add(a_share, b_share)
    transcript.log("Let's reveal the results!")
    c = protocol.reveal(c_share)

    handler.close()

with transcript.record():
    SocketCommunicator.run(fn=main, world_size=3)
Player 0: Let's setup additive sharing!
Player 1: Let's setup additive sharing!
Player 2: Let's setup additive sharing!
Player 0: --> 1 PRZS 5055980699667503963
Player 1: --> 2 PRZS 9182265299702583487
Player 2: --> 0 PRZS 8728523590282708449
Player 1: <-- 0 PRZS 5055980699667503963
Player 0: <-- 2 PRZS 8728523590282708449
Player 2: <-- 1 PRZS 9182265299702583487
Player 1: Let's share some secrets!
Player 0: Let's share some secrets!
Player 2: Let's share some secrets!
Player 1: Let's add some secrets!
Player 0: Let's add some secrets!
Player 2: Let's add some secrets!
Player 1: Let's reveal the results!
Player 0: Let's reveal the results!
Player 2: Let's reveal the results!
Player 1: --> 0 GATHER 49
Player 0: --> 0 GATHER 121
Player 2: --> 0 GATHER 104
Player 1: --> 1 GATHER 49
Player 0: <-- 1 GATHER 49
Player 2: --> 1 GATHER 104
Player 1: <-- 1 GATHER 49
Player 0: <-- 0 GATHER 121
Player 2: --> 2 GATHER 104
Player 1: <-- 2 GATHER 104
Player 0: <-- 2 GATHER 104
Player 2: <-- 2 GATHER 104
Player 0: --> 1 GATHER 121
Player 0: --> 2 GATHER 121
Player 1: <-- 0 GATHER 121
Player 2: <-- 0 GATHER 121
Player 1: --> 2 GATHER 49
Player 2: <-- 1 GATHER 49

Now, our transcript contains every single message transmitted by the program. The logged events show the rank of the players sending and receiving the message, along with the message type and payload.

By the way …

If you examine the message transcript closely, you may be surprised to see that our program sends messages while setting-up secret sharing and revealing the final result, but not while generating secret shares. How can this be? It’s because Cicada uses a cool trick - pseudorandom zero sharing - to generate additive secret shares without communication.

If you want to customize the transcript, you can override the message format string for transcript.Formatter, using a rich set of fields including message.arrow (message direction, relative to the local rank), message.comm.name (communicator name), message.dir (message direction, relative to the local rank), message.dst (message destination), message.other (the other player sending or receiving with the local rank), message.payload (message payload), message.comm.rank (local player), message.src (message source), message.tag (message type), and message.verb (“send” or “receive”, depending on whether the local player is sending or receiving).

For example, you could override the default message format to produce more compact output:

[6]:
def main(communicator):
    handler = logging.StreamHandler()

    msgfmt="{message.comm.rank} {message.dir} {message.other} {message.tag} {message.payload}"
    handler.setFormatter(transcript.Formatter(msgfmt=msgfmt))

    handler.addFilter(transcript.ShowSentMessages())
    handler.addFilter(transcript.ShowReceivedMessages())
    handler.addFilter(transcript.HideAllFunctions())
    transcript.logger.addHandler(handler)

    transcript.log("Let's setup additive sharing!")
    protocol = AdditiveProtocolSuite(communicator, order=127, encoding=FixedPoint(precision=2))
    transcript.log("Let's share some secrets!")
    a_share = protocol.share(src=0, secret=numpy.array(2), shape=())
    b_share = protocol.share(src=1, secret=numpy.array(3), shape=())
    transcript.log("Let's add some secrets!")
    c_share = protocol.add(a_share, b_share)
    transcript.log("Let's reveal the results!")
    c = protocol.reveal(c_share)

    handler.close()

with transcript.record():
    SocketCommunicator.run(fn=main, world_size=3)
Player 0: Let's setup additive sharing!
Player 1: Let's setup additive sharing!
Player 2: Let's setup additive sharing!
0 > 1 PRZS 8706282897491032853
1 > 2 PRZS 3830210240957405466
2 > 0 PRZS 4199077368193352387
1 < 0 PRZS 8706282897491032853
2 < 1 PRZS 3830210240957405466
0 < 2 PRZS 4199077368193352387
Player 1: Let's share some secrets!
Player 2: Let's share some secrets!
Player 0: Let's share some secrets!
Player 1: Let's add some secrets!
Player 2: Let's add some secrets!
Player 0: Let's add some secrets!
Player 1: Let's reveal the results!
Player 2: Let's reveal the results!
Player 0: Let's reveal the results!
1 > 0 GATHER 38
2 > 0 GATHER 42
0 > 0 GATHER 67
1 > 1 GATHER 38
2 > 1 GATHER 42
0 < 1 GATHER 38
1 < 1 GATHER 38
2 > 2 GATHER 42
0 < 2 GATHER 42
1 < 2 GATHER 42
2 < 2 GATHER 42
0 < 0 GATHER 67
0 > 1 GATHER 67
0 > 2 GATHER 67
1 < 0 GATHER 67
2 < 0 GATHER 67
1 > 2 GATHER 38
2 < 1 GATHER 38

Note from the log outputs that an entry is generated both when a message is sent and when it’s received, and that the rank of the player logging the event is always on the left, whether they are the sender or the receiver. With the arrows indicating which direction the message is travelling, this is intuitive for a person to understand, but it can complicate programmatically parsing the transcript. As an alternative, you could use different fields to unconditionally put the sender on the left and the recipient on the right, regardless of which player is logging the event:

[7]:
def main(communicator):
    handler = logging.StreamHandler()

    msgfmt="{message.src} > {message.dst} {message.tag} {message.payload}"
    handler.setFormatter(transcript.Formatter(msgfmt=msgfmt))

    handler.addFilter(transcript.ShowSentMessages())
    handler.addFilter(transcript.ShowReceivedMessages())
    handler.addFilter(transcript.HideAllFunctions())
    transcript.logger.addHandler(handler)

    transcript.log("Let's setup additive sharing!")
    protocol = AdditiveProtocolSuite(communicator, order=127, encoding=FixedPoint(precision=2))
    transcript.log("Let's share some secrets!")
    a_share = protocol.share(src=0, secret=numpy.array(2), shape=())
    b_share = protocol.share(src=1, secret=numpy.array(3), shape=())
    transcript.log("Let's add some secrets!")
    c_share = protocol.add(a_share, b_share)
    transcript.log("Let's reveal the results!")
    c = protocol.reveal(c_share)

    handler.close()

with transcript.record():
    SocketCommunicator.run(fn=main, world_size=3)
Player 0: Let's setup additive sharing!
Player 1: Let's setup additive sharing!
Player 2: Let's setup additive sharing!
0 > 1 PRZS 2092923569329434604
1 > 2 PRZS 3537555968097532286
2 > 0 PRZS 1907803819699352987
0 > 1 PRZS 2092923569329434604
2 > 0 PRZS 1907803819699352987
1 > 2 PRZS 3537555968097532286
Player 1: Let's share some secrets!
Player 0: Let's share some secrets!
Player 2: Let's share some secrets!
Player 1: Let's add some secrets!
Player 0: Let's add some secrets!
Player 2: Let's add some secrets!
Player 0: Let's reveal the results!
Player 1: Let's reveal the results!
Player 2: Let's reveal the results!
0 > 0 GATHER 39
1 > 0 GATHER 4
2 > 0 GATHER 104
0 > 0 GATHER 39
1 > 1 GATHER 4
2 > 1 GATHER 104
1 > 0 GATHER 4
1 > 1 GATHER 4
2 > 2 GATHER 104
2 > 0 GATHER 104
2 > 1 GATHER 104
2 > 2 GATHER 104
0 > 1 GATHER 39
0 > 2 GATHER 39
0 > 1 GATHER 39
0 > 2 GATHER 39
1 > 2 GATHER 4
1 > 2 GATHER 4

Since this format produces two completely identical events for each message (once when the message is sent, and once when the message is received), you may wish to eliminate the duplication, e.g. by only logging messages when they’re sent. As you might guess, this is a matter of simply removing the transcript.ShowReceivedMessages filter:

[8]:
def main(communicator):
    handler = logging.StreamHandler()

    msgfmt="{message.src} > {message.dst} {message.tag} {message.payload}"
    handler.setFormatter(transcript.Formatter(msgfmt=msgfmt))

    handler.addFilter(transcript.ShowSentMessages())
    handler.addFilter(transcript.HideAllFunctions())
    transcript.logger.addHandler(handler)

    transcript.log("Let's setup additive sharing!")
    protocol = AdditiveProtocolSuite(communicator, order=127, encoding=FixedPoint(precision=2))
    transcript.log("Let's share some secrets!")
    a_share = protocol.share(src=0, secret=numpy.array(2), shape=())
    b_share = protocol.share(src=1, secret=numpy.array(3), shape=())
    transcript.log("Let's add some secrets!")
    c_share = protocol.add(a_share, b_share)
    transcript.log("Let's reveal the results!")
    c = protocol.reveal(c_share)

    handler.close()

with transcript.record():
    SocketCommunicator.run(fn=main, world_size=3)
Player 0: Let's setup additive sharing!
Player 1: Let's setup additive sharing!
Player 2: Let's setup additive sharing!
0 > 1 PRZS 8749477204076047156
1 > 2 PRZS 7732936837612642819
2 > 0 PRZS 4835145264246921601
Player 1: Let's share some secrets!
Player 0: Let's share some secrets!
Player 2: Let's share some secrets!
Player 1: Let's add some secrets!
Player 2: Let's add some secrets!
Player 0: Let's add some secrets!
Player 1: Let's reveal the results!
Player 2: Let's reveal the results!
Player 0: Let's reveal the results!
1 > 0 GATHER 54
2 > 0 GATHER 2
0 > 0 GATHER 91
1 > 1 GATHER 54
2 > 1 GATHER 2
0 > 1 GATHER 91
2 > 2 GATHER 2
0 > 2 GATHER 91
1 > 2 GATHER 54

Naturally, you could remove transcript.ShowSentMessages instead, if you prefer to only see messages when they’re received.

Tracing Function Calls

In addition to logging application and network message events, Cicada’s transcription framework can log function calls as they happen, which is why we’ve been using the transcript.HideAllFunctions filter. We need to be cautious about removing HideAllFunctions because unfiltered tracing will generate log entries for literally every function call in the entire Cicada library, which is extremely noisy and could easily swamp our notebook with output. Instead, we’ll use the transcript.basic_config function to replace HideAllFunctions with a curated set of filters tailored to achieve a reasonable balance between utility and verbosity:

[9]:
def main(communicator):
    handler = transcript.basic_config(logging.StreamHandler())
    transcript.logger.addHandler(handler)

    transcript.log("Let's setup additive sharing!")
    protocol = AdditiveProtocolSuite(communicator, order=127, encoding=FixedPoint(precision=2))
    transcript.log("Let's share some secrets!")
    a_share = protocol.share(src=0, secret=numpy.array(2), shape=())
    b_share = protocol.share(src=1, secret=numpy.array(3), shape=())
    transcript.log("Let's add some secrets!")
    c_share = protocol.add(a_share, b_share)
    transcript.log("Let's reveal the results!")
    c = protocol.reveal(c_share)

with transcript.record():
    SocketCommunicator.run(fn=main, world_size=3)
Player 0: Let's setup additive sharing!
Player 1: Let's setup additive sharing!
Player 2: Let's setup additive sharing!
Player 0: Let's share some secrets!
Player 2: Let's share some secrets!
Player 1: Let's share some secrets!
Player 0: AdditiveProtocolSuite.share(src=0, secret=array(2), shape=(), encoding=None)
Player 1: AdditiveProtocolSuite.share(src=0, secret=array(2), shape=(), encoding=None)
Player 2: AdditiveProtocolSuite.share(src=0, secret=array(2), shape=(), encoding=None)
Player 0:   Field.uniform(size=(), generator=Generator(PCG64) at 0x11570D7E0)
Player 2:   Field.uniform(size=(), generator=Generator(PCG64) at 0x11570D7E0)
Player 1:   Field.uniform(size=(), generator=Generator(PCG64) at 0x11570D7E0)
Player 0:     Field.uniform => 44
Player 1:     Field.uniform => 174
Player 2:     Field.uniform => 67
Player 0:   Field.uniform(size=(), generator=Generator(PCG64) at 0x11570D0E0)
Player 1:   Field.uniform(size=(), generator=Generator(PCG64) at 0x11570D0E0)
Player 2:   Field.uniform(size=(), generator=Generator(PCG64) at 0x11570D0E0)
Player 0:     Field.uniform => 67
Player 2:     Field.uniform => 174
Player 1:     Field.uniform => 44
Player 0:   Field.inplace_subtract(lhs=array(44, dtype=object), rhs=array(67, dtype=object))
Player 1:   Field.inplace_subtract(lhs=array(174, dtype=object), rhs=array(44, dtype=object))
Player 2:   Field.inplace_subtract(lhs=array(67, dtype=object), rhs=array(174, dtype=object))
Player 0:     Field.inplace_subtract => 104
Player 1:     Field.inplace_subtract => 3
Player 2:     Field.inplace_subtract => 20
Player 0:   FixedPoint.encode(array=array(2), field=cicada.arithmetic.Field(order=127))
Player 1:   AdditiveProtocolSuite.share => cicada.additive.AdditiveArrayShare(storage=3)
Player 2:   AdditiveProtocolSuite.share => cicada.additive.AdditiveArrayShare(storage=20)
Player 0:     FixedPoint.encode => 8
Player 1: AdditiveProtocolSuite.share(src=1, secret=array(3), shape=(), encoding=None)
Player 2: AdditiveProtocolSuite.share(src=1, secret=array(3), shape=(), encoding=None)
Player 0:   Field.inplace_add(lhs=array(104, dtype=object), rhs=array(8, dtype=object))
Player 1:   Field.uniform(size=(), generator=Generator(PCG64) at 0x11570D7E0)
Player 2:   Field.uniform(size=(), generator=Generator(PCG64) at 0x11570D7E0)
Player 0:     Field.inplace_add => 112
Player 1:     Field.uniform => 77
Player 2:     Field.uniform => 12
Player 0:   AdditiveProtocolSuite.share => cicada.additive.AdditiveArrayShare(storage=112)
Player 1:   Field.uniform(size=(), generator=Generator(PCG64) at 0x11570D0E0)
Player 2:   Field.uniform(size=(), generator=Generator(PCG64) at 0x11570D0E0)
Player 0: AdditiveProtocolSuite.share(src=1, secret=array(3), shape=(), encoding=None)
Player 1:     Field.uniform => 14
Player 2:     Field.uniform => 77
Player 1:   Field.inplace_subtract(lhs=array(77, dtype=object), rhs=array(14, dtype=object))
Player 0:   Field.uniform(size=(), generator=Generator(PCG64) at 0x11570D7E0)
Player 2:   Field.inplace_subtract(lhs=array(12, dtype=object), rhs=array(77, dtype=object))
Player 1:     Field.inplace_subtract => 63
Player 0:     Field.uniform => 14
Player 2:     Field.inplace_subtract => 62
Player 1:   FixedPoint.encode(array=array(3), field=cicada.arithmetic.Field(order=127))
Player 0:   Field.uniform(size=(), generator=Generator(PCG64) at 0x11570D0E0)
Player 2:   AdditiveProtocolSuite.share => cicada.additive.AdditiveArrayShare(storage=62)
Player 1:     FixedPoint.encode => 12
Player 0:     Field.uniform => 12
Player 2: Let's add some secrets!
Player 1:   Field.inplace_add(lhs=array(63, dtype=object), rhs=array(12, dtype=object))
Player 0:   Field.inplace_subtract(lhs=array(14, dtype=object), rhs=array(12, dtype=object))
Player 2: AdditiveProtocolSuite.add(lhs=cicada.additive.AdditiveArrayShare(storage=20), rhs=cicada.additive.AdditiveArrayShare(storage=62), encoding=None)
Player 1:     Field.inplace_add => 75
Player 0:     Field.inplace_subtract => 2
Player 2:   AdditiveProtocolSuite.field_add(lhs=cicada.additive.AdditiveArrayShare(storage=20), rhs=cicada.additive.AdditiveArrayShare(storage=62))
Player 1:   AdditiveProtocolSuite.share => cicada.additive.AdditiveArrayShare(storage=75)
Player 0:   AdditiveProtocolSuite.share => cicada.additive.AdditiveArrayShare(storage=2)
Player 2:     Field.add(lhs=array(20, dtype=object), rhs=array(62, dtype=object))
Player 1: Let's add some secrets!
Player 0: Let's add some secrets!
Player 2:       Field.add => 82
Player 0: AdditiveProtocolSuite.add(lhs=cicada.additive.AdditiveArrayShare(storage=112), rhs=cicada.additive.AdditiveArrayShare(storage=2), encoding=None)
Player 1: AdditiveProtocolSuite.add(lhs=cicada.additive.AdditiveArrayShare(storage=3), rhs=cicada.additive.AdditiveArrayShare(storage=75), encoding=None)
Player 2:     AdditiveProtocolSuite.field_add => cicada.additive.AdditiveArrayShare(storage=82)
Player 0:   AdditiveProtocolSuite.field_add(lhs=cicada.additive.AdditiveArrayShare(storage=112), rhs=cicada.additive.AdditiveArrayShare(storage=2))
Player 1:   AdditiveProtocolSuite.field_add(lhs=cicada.additive.AdditiveArrayShare(storage=3), rhs=cicada.additive.AdditiveArrayShare(storage=75))
Player 2:   AdditiveProtocolSuite.add => cicada.additive.AdditiveArrayShare(storage=82)
Player 0:     Field.add(lhs=array(112, dtype=object), rhs=array(2, dtype=object))
Player 1:     Field.add(lhs=array(3, dtype=object), rhs=array(75, dtype=object))
Player 2: Let's reveal the results!
Player 0:       Field.add => 114
Player 1:       Field.add => 78
Player 2: AdditiveProtocolSuite.reveal(share=cicada.additive.AdditiveArrayShare(storage=82), dst=None, encoding=None)
Player 0:     AdditiveProtocolSuite.field_add => cicada.additive.AdditiveArrayShare(storage=114)
Player 1:     AdditiveProtocolSuite.field_add => cicada.additive.AdditiveArrayShare(storage=78)
Player 0:   AdditiveProtocolSuite.add => cicada.additive.AdditiveArrayShare(storage=114)
Player 1:   AdditiveProtocolSuite.add => cicada.additive.AdditiveArrayShare(storage=78)
Player 0: Let's reveal the results!
Player 1: Let's reveal the results!
Player 0: AdditiveProtocolSuite.reveal(share=cicada.additive.AdditiveArrayShare(storage=114), dst=None, encoding=None)
Player 1: AdditiveProtocolSuite.reveal(share=cicada.additive.AdditiveArrayShare(storage=78), dst=None, encoding=None)
Player 0:   Field.inplace_add(lhs=array(114, dtype=object), rhs=array(78, dtype=object))
Player 0:     Field.inplace_add => 65
Player 0:   Field.inplace_add(lhs=array(65, dtype=object), rhs=array(82, dtype=object))
Player 0:     Field.inplace_add => 20
Player 0:   FixedPoint.decode(array=array(20, dtype=object), field=cicada.arithmetic.Field(order=127))
Player 1:   Field.inplace_add(lhs=array(114, dtype=object), rhs=array(78, dtype=object))
Player 0:     FixedPoint.decode => 5.0
Player 1:     Field.inplace_add => 65
Player 0:   AdditiveProtocolSuite.reveal => 5.0
Player 1:   Field.inplace_add(lhs=array(65, dtype=object), rhs=array(82, dtype=object))
Player 1:     Field.inplace_add => 20
Player 1:   FixedPoint.decode(array=array(20, dtype=object), field=cicada.arithmetic.Field(order=127))
Player 2:   Field.inplace_add(lhs=array(114, dtype=object), rhs=array(78, dtype=object))
Player 1:     FixedPoint.decode => 5.0
Player 2:     Field.inplace_add => 65
Player 1:   AdditiveProtocolSuite.reveal => 5.0
Player 2:   Field.inplace_add(lhs=array(65, dtype=object), rhs=array(82, dtype=object))
Player 2:     Field.inplace_add => 20
Player 2:   FixedPoint.decode(array=array(20, dtype=object), field=cicada.arithmetic.Field(order=127))
Player 2:     FixedPoint.decode => 5.0
Player 2:   AdditiveProtocolSuite.reveal => 5.0

Now, the transcript includes notable function calls made during execution of the script, along with matching function returns. The output for each call includes the arguments that were passed to the function, but with the self arguments of class methods omitted for clarity.

The log entries are indented based on stack depth, but this is difficult to see because the output from all the players is interleaved on-screen. To fix this, let’s send the players’ transcripts to separate files on disk instead:

[10]:
def main(communicator):
    handler = transcript.basic_config(logging.FileHandler(f"player-{communicator.rank}.log", mode="w"))
    transcript.logger.addHandler(handler)

    transcript.log("Let's setup additive sharing!")
    protocol = AdditiveProtocolSuite(communicator, order=127, encoding=FixedPoint(precision=2))
    transcript.log("Let's share some secrets!")
    a_share = protocol.share(src=0, secret=numpy.array(2), shape=())
    b_share = protocol.share(src=1, secret=numpy.array(3), shape=())
    transcript.log("Let's add some secrets!")
    c_share = protocol.add(a_share, b_share)
    transcript.log("Let's reveal the results!")
    c = protocol.reveal(c_share)

with transcript.record():
    SocketCommunicator.run(fn=main, world_size=3)

Now, we can print the file contents to the screen in rank order:

[11]:
for rank in range(3):
    with open(f"player-{rank}.log", "r") as stream:
        print(stream.read())
        print("\n" * 2)
Player 0: Let's setup additive sharing!
Player 0: Let's share some secrets!
Player 0: AdditiveProtocolSuite.share(src=0, secret=array(2), shape=(), encoding=None)
Player 0:   Field.uniform(size=(), generator=Generator(PCG64) at 0x11570D1C0)
Player 0:     Field.uniform => 244
Player 0:   Field.uniform(size=(), generator=Generator(PCG64) at 0x11570D8C0)
Player 0:     Field.uniform => 159
Player 0:   Field.inplace_subtract(lhs=array(244, dtype=object), rhs=array(159, dtype=object))
Player 0:     Field.inplace_subtract => 85
Player 0:   FixedPoint.encode(array=array(2), field=cicada.arithmetic.Field(order=127))
Player 0:     FixedPoint.encode => 8
Player 0:   Field.inplace_add(lhs=array(85, dtype=object), rhs=array(8, dtype=object))
Player 0:     Field.inplace_add => 93
Player 0:   AdditiveProtocolSuite.share => cicada.additive.AdditiveArrayShare(storage=93)
Player 0: AdditiveProtocolSuite.share(src=1, secret=array(3), shape=(), encoding=None)
Player 0:   Field.uniform(size=(), generator=Generator(PCG64) at 0x11570D1C0)
Player 0:     Field.uniform => 101
Player 0:   Field.uniform(size=(), generator=Generator(PCG64) at 0x11570D8C0)
Player 0:     Field.uniform => 121
Player 0:   Field.inplace_subtract(lhs=array(101, dtype=object), rhs=array(121, dtype=object))
Player 0:     Field.inplace_subtract => 107
Player 0:   AdditiveProtocolSuite.share => cicada.additive.AdditiveArrayShare(storage=107)
Player 0: Let's add some secrets!
Player 0: AdditiveProtocolSuite.add(lhs=cicada.additive.AdditiveArrayShare(storage=93), rhs=cicada.additive.AdditiveArrayShare(storage=107), encoding=None)
Player 0:   AdditiveProtocolSuite.field_add(lhs=cicada.additive.AdditiveArrayShare(storage=93), rhs=cicada.additive.AdditiveArrayShare(storage=107))
Player 0:     Field.add(lhs=array(93, dtype=object), rhs=array(107, dtype=object))
Player 0:       Field.add => 73
Player 0:     AdditiveProtocolSuite.field_add => cicada.additive.AdditiveArrayShare(storage=73)
Player 0:   AdditiveProtocolSuite.add => cicada.additive.AdditiveArrayShare(storage=73)
Player 0: Let's reveal the results!
Player 0: AdditiveProtocolSuite.reveal(share=cicada.additive.AdditiveArrayShare(storage=73), dst=None, encoding=None)
Player 0:   Field.inplace_add(lhs=array(73, dtype=object), rhs=array(59, dtype=object))
Player 0:     Field.inplace_add => 5
Player 0:   Field.inplace_add(lhs=array(5, dtype=object), rhs=array(15, dtype=object))
Player 0:     Field.inplace_add => 20
Player 0:   FixedPoint.decode(array=array(20, dtype=object), field=cicada.arithmetic.Field(order=127))
Player 0:     FixedPoint.decode => 5.0
Player 0:   AdditiveProtocolSuite.reveal => 5.0




Player 1: Let's setup additive sharing!
Player 1: Let's share some secrets!
Player 1: AdditiveProtocolSuite.share(src=0, secret=array(2), shape=(), encoding=None)
Player 1:   Field.uniform(size=(), generator=Generator(PCG64) at 0x11570D1C0)
Player 1:     Field.uniform => 143
Player 1:   Field.uniform(size=(), generator=Generator(PCG64) at 0x11570D8C0)
Player 1:     Field.uniform => 244
Player 1:   Field.inplace_subtract(lhs=array(143, dtype=object), rhs=array(244, dtype=object))
Player 1:     Field.inplace_subtract => 26
Player 1:   AdditiveProtocolSuite.share => cicada.additive.AdditiveArrayShare(storage=26)
Player 1: AdditiveProtocolSuite.share(src=1, secret=array(3), shape=(), encoding=None)
Player 1:   Field.uniform(size=(), generator=Generator(PCG64) at 0x11570D1C0)
Player 1:     Field.uniform => 249
Player 1:   Field.uniform(size=(), generator=Generator(PCG64) at 0x11570D8C0)
Player 1:     Field.uniform => 101
Player 1:   Field.inplace_subtract(lhs=array(249, dtype=object), rhs=array(101, dtype=object))
Player 1:     Field.inplace_subtract => 21
Player 1:   FixedPoint.encode(array=array(3), field=cicada.arithmetic.Field(order=127))
Player 1:     FixedPoint.encode => 12
Player 1:   Field.inplace_add(lhs=array(21, dtype=object), rhs=array(12, dtype=object))
Player 1:     Field.inplace_add => 33
Player 1:   AdditiveProtocolSuite.share => cicada.additive.AdditiveArrayShare(storage=33)
Player 1: Let's add some secrets!
Player 1: AdditiveProtocolSuite.add(lhs=cicada.additive.AdditiveArrayShare(storage=26), rhs=cicada.additive.AdditiveArrayShare(storage=33), encoding=None)
Player 1:   AdditiveProtocolSuite.field_add(lhs=cicada.additive.AdditiveArrayShare(storage=26), rhs=cicada.additive.AdditiveArrayShare(storage=33))
Player 1:     Field.add(lhs=array(26, dtype=object), rhs=array(33, dtype=object))
Player 1:       Field.add => 59
Player 1:     AdditiveProtocolSuite.field_add => cicada.additive.AdditiveArrayShare(storage=59)
Player 1:   AdditiveProtocolSuite.add => cicada.additive.AdditiveArrayShare(storage=59)
Player 1: Let's reveal the results!
Player 1: AdditiveProtocolSuite.reveal(share=cicada.additive.AdditiveArrayShare(storage=59), dst=None, encoding=None)
Player 1:   Field.inplace_add(lhs=array(73, dtype=object), rhs=array(59, dtype=object))
Player 1:     Field.inplace_add => 5
Player 1:   Field.inplace_add(lhs=array(5, dtype=object), rhs=array(15, dtype=object))
Player 1:     Field.inplace_add => 20
Player 1:   FixedPoint.decode(array=array(20, dtype=object), field=cicada.arithmetic.Field(order=127))
Player 1:     FixedPoint.decode => 5.0
Player 1:   AdditiveProtocolSuite.reveal => 5.0




Player 2: Let's setup additive sharing!
Player 2: Let's share some secrets!
Player 2: AdditiveProtocolSuite.share(src=0, secret=array(2), shape=(), encoding=None)
Player 2:   Field.uniform(size=(), generator=Generator(PCG64) at 0x11570D1C0)
Player 2:     Field.uniform => 159
Player 2:   Field.uniform(size=(), generator=Generator(PCG64) at 0x11570D8C0)
Player 2:     Field.uniform => 143
Player 2:   Field.inplace_subtract(lhs=array(159, dtype=object), rhs=array(143, dtype=object))
Player 2:     Field.inplace_subtract => 16
Player 2:   AdditiveProtocolSuite.share => cicada.additive.AdditiveArrayShare(storage=16)
Player 2: AdditiveProtocolSuite.share(src=1, secret=array(3), shape=(), encoding=None)
Player 2:   Field.uniform(size=(), generator=Generator(PCG64) at 0x11570D1C0)
Player 2:     Field.uniform => 121
Player 2:   Field.uniform(size=(), generator=Generator(PCG64) at 0x11570D8C0)
Player 2:     Field.uniform => 249
Player 2:   Field.inplace_subtract(lhs=array(121, dtype=object), rhs=array(249, dtype=object))
Player 2:     Field.inplace_subtract => 126
Player 2:   AdditiveProtocolSuite.share => cicada.additive.AdditiveArrayShare(storage=126)
Player 2: Let's add some secrets!
Player 2: AdditiveProtocolSuite.add(lhs=cicada.additive.AdditiveArrayShare(storage=16), rhs=cicada.additive.AdditiveArrayShare(storage=126), encoding=None)
Player 2:   AdditiveProtocolSuite.field_add(lhs=cicada.additive.AdditiveArrayShare(storage=16), rhs=cicada.additive.AdditiveArrayShare(storage=126))
Player 2:     Field.add(lhs=array(16, dtype=object), rhs=array(126, dtype=object))
Player 2:       Field.add => 15
Player 2:     AdditiveProtocolSuite.field_add => cicada.additive.AdditiveArrayShare(storage=15)
Player 2:   AdditiveProtocolSuite.add => cicada.additive.AdditiveArrayShare(storage=15)
Player 2: Let's reveal the results!
Player 2: AdditiveProtocolSuite.reveal(share=cicada.additive.AdditiveArrayShare(storage=15), dst=None, encoding=None)
Player 2:   Field.inplace_add(lhs=array(73, dtype=object), rhs=array(59, dtype=object))
Player 2:     Field.inplace_add => 5
Player 2:   Field.inplace_add(lhs=array(5, dtype=object), rhs=array(15, dtype=object))
Player 2:     Field.inplace_add => 20
Player 2:   FixedPoint.decode(array=array(20, dtype=object), field=cicada.arithmetic.Field(order=127))
Player 2:     FixedPoint.decode => 5.0
Player 2:   AdditiveProtocolSuite.reveal => 5.0




In addition to being more privacy preserving, no we can follow each player’s progress in detail. Notice that you can see values as they are generated and manipulated, including function return values, even for functions like inplace_subtract that operate on their arguments inline!

Let’s include message events alongside the function calls in our transcripts. This is as simple as adding the ShowSentMessages and ShowReceivedMessages filters, but with one caveat - these filters must be added to the handler before any other filters, which means adding them before the call to basic_config:

[12]:
def main(communicator):
    handler = logging.FileHandler(f"player-{communicator.rank}.log", mode="w")
    handler.addFilter(transcript.ShowSentMessages())
    handler.addFilter(transcript.ShowReceivedMessages())
    handler = transcript.basic_config(handler)
    transcript.logger.addHandler(handler)

    transcript.log("Let's setup additive sharing!")
    protocol = AdditiveProtocolSuite(communicator, order=127, encoding=FixedPoint(precision=2))
    transcript.log("Let's share some secrets!")
    a_share = protocol.share(src=0, secret=numpy.array(2), shape=())
    b_share = protocol.share(src=1, secret=numpy.array(3), shape=())
    transcript.log("Let's add some secrets!")
    c_share = protocol.add(a_share, b_share)
    transcript.log("Let's reveal the results!")
    c = protocol.reveal(c_share)

with transcript.record():
    SocketCommunicator.run(fn=main, world_size=3)

for rank in range(3):
    with open(f"player-{rank}.log", "r") as stream:
        print(stream.read())
        print("\n" * 2)
Player 0: Let's setup additive sharing!
Player 0: --> 1 PRZS 1249769366064882735
Player 0: <-- 2 PRZS 6279992657863198383
Player 0: Let's share some secrets!
Player 0: AdditiveProtocolSuite.share(src=0, secret=array(2), shape=(), encoding=None)
Player 0:   Field.uniform(size=(), generator=Generator(PCG64) at 0x11570D7E0)
Player 0:     Field.uniform => 72
Player 0:   Field.uniform(size=(), generator=Generator(PCG64) at 0x11570D8C0)
Player 0:     Field.uniform => 167
Player 0:   Field.inplace_subtract(lhs=array(72, dtype=object), rhs=array(167, dtype=object))
Player 0:     Field.inplace_subtract => 32
Player 0:   FixedPoint.encode(array=array(2), field=cicada.arithmetic.Field(order=127))
Player 0:     FixedPoint.encode => 8
Player 0:   Field.inplace_add(lhs=array(32, dtype=object), rhs=array(8, dtype=object))
Player 0:     Field.inplace_add => 40
Player 0:   AdditiveProtocolSuite.share => cicada.additive.AdditiveArrayShare(storage=40)
Player 0: AdditiveProtocolSuite.share(src=1, secret=array(3), shape=(), encoding=None)
Player 0:   Field.uniform(size=(), generator=Generator(PCG64) at 0x11570D7E0)
Player 0:     Field.uniform => 35
Player 0:   Field.uniform(size=(), generator=Generator(PCG64) at 0x11570D8C0)
Player 0:     Field.uniform => 42
Player 0:   Field.inplace_subtract(lhs=array(35, dtype=object), rhs=array(42, dtype=object))
Player 0:     Field.inplace_subtract => 120
Player 0:   AdditiveProtocolSuite.share => cicada.additive.AdditiveArrayShare(storage=120)
Player 0: Let's add some secrets!
Player 0: AdditiveProtocolSuite.add(lhs=cicada.additive.AdditiveArrayShare(storage=40), rhs=cicada.additive.AdditiveArrayShare(storage=120), encoding=None)
Player 0:   AdditiveProtocolSuite.field_add(lhs=cicada.additive.AdditiveArrayShare(storage=40), rhs=cicada.additive.AdditiveArrayShare(storage=120))
Player 0:     Field.add(lhs=array(40, dtype=object), rhs=array(120, dtype=object))
Player 0:       Field.add => 33
Player 0:     AdditiveProtocolSuite.field_add => cicada.additive.AdditiveArrayShare(storage=33)
Player 0:   AdditiveProtocolSuite.add => cicada.additive.AdditiveArrayShare(storage=33)
Player 0: Let's reveal the results!
Player 0: AdditiveProtocolSuite.reveal(share=cicada.additive.AdditiveArrayShare(storage=33), dst=None, encoding=None)
Player 0: --> 0 GATHER 33
Player 0: <-- 0 GATHER 33
Player 0: <-- 2 GATHER 112
Player 0: <-- 1 GATHER 2
Player 0:   Field.inplace_add(lhs=array(33, dtype=object), rhs=array(2, dtype=object))
Player 0:     Field.inplace_add => 35
Player 0:   Field.inplace_add(lhs=array(35, dtype=object), rhs=array(112, dtype=object))
Player 0:     Field.inplace_add => 20
Player 0: --> 1 GATHER 33
Player 0: --> 2 GATHER 33
Player 0:   FixedPoint.decode(array=array(20, dtype=object), field=cicada.arithmetic.Field(order=127))
Player 0:     FixedPoint.decode => 5.0
Player 0:   AdditiveProtocolSuite.reveal => 5.0




Player 1: Let's setup additive sharing!
Player 1: --> 2 PRZS 6976493060276260697
Player 1: <-- 0 PRZS 1249769366064882735
Player 1: Let's share some secrets!
Player 1: AdditiveProtocolSuite.share(src=0, secret=array(2), shape=(), encoding=None)
Player 1:   Field.uniform(size=(), generator=Generator(PCG64) at 0x11570D7E0)
Player 1:     Field.uniform => 96
Player 1:   Field.uniform(size=(), generator=Generator(PCG64) at 0x11570D8C0)
Player 1:     Field.uniform => 72
Player 1:   Field.inplace_subtract(lhs=array(96, dtype=object), rhs=array(72, dtype=object))
Player 1:     Field.inplace_subtract => 24
Player 1:   AdditiveProtocolSuite.share => cicada.additive.AdditiveArrayShare(storage=24)
Player 1: AdditiveProtocolSuite.share(src=1, secret=array(3), shape=(), encoding=None)
Player 1:   Field.uniform(size=(), generator=Generator(PCG64) at 0x11570D7E0)
Player 1:     Field.uniform => 128
Player 1:   Field.uniform(size=(), generator=Generator(PCG64) at 0x11570D8C0)
Player 1:     Field.uniform => 35
Player 1:   Field.inplace_subtract(lhs=array(128, dtype=object), rhs=array(35, dtype=object))
Player 1:     Field.inplace_subtract => 93
Player 1:   FixedPoint.encode(array=array(3), field=cicada.arithmetic.Field(order=127))
Player 1:     FixedPoint.encode => 12
Player 1:   Field.inplace_add(lhs=array(93, dtype=object), rhs=array(12, dtype=object))
Player 1:     Field.inplace_add => 105
Player 1:   AdditiveProtocolSuite.share => cicada.additive.AdditiveArrayShare(storage=105)
Player 1: Let's add some secrets!
Player 1: AdditiveProtocolSuite.add(lhs=cicada.additive.AdditiveArrayShare(storage=24), rhs=cicada.additive.AdditiveArrayShare(storage=105), encoding=None)
Player 1:   AdditiveProtocolSuite.field_add(lhs=cicada.additive.AdditiveArrayShare(storage=24), rhs=cicada.additive.AdditiveArrayShare(storage=105))
Player 1:     Field.add(lhs=array(24, dtype=object), rhs=array(105, dtype=object))
Player 1:       Field.add => 2
Player 1:     AdditiveProtocolSuite.field_add => cicada.additive.AdditiveArrayShare(storage=2)
Player 1:   AdditiveProtocolSuite.add => cicada.additive.AdditiveArrayShare(storage=2)
Player 1: Let's reveal the results!
Player 1: AdditiveProtocolSuite.reveal(share=cicada.additive.AdditiveArrayShare(storage=2), dst=None, encoding=None)
Player 1: --> 0 GATHER 2
Player 1: --> 1 GATHER 2
Player 1: <-- 2 GATHER 112
Player 1: <-- 1 GATHER 2
Player 1: <-- 0 GATHER 33
Player 1:   Field.inplace_add(lhs=array(33, dtype=object), rhs=array(2, dtype=object))
Player 1:     Field.inplace_add => 35
Player 1:   Field.inplace_add(lhs=array(35, dtype=object), rhs=array(112, dtype=object))
Player 1:     Field.inplace_add => 20
Player 1: --> 2 GATHER 2
Player 1:   FixedPoint.decode(array=array(20, dtype=object), field=cicada.arithmetic.Field(order=127))
Player 1:     FixedPoint.decode => 5.0
Player 1:   AdditiveProtocolSuite.reveal => 5.0




Player 2: Let's setup additive sharing!
Player 2: --> 0 PRZS 6279992657863198383
Player 2: <-- 1 PRZS 6976493060276260697
Player 2: Let's share some secrets!
Player 2: AdditiveProtocolSuite.share(src=0, secret=array(2), shape=(), encoding=None)
Player 2:   Field.uniform(size=(), generator=Generator(PCG64) at 0x11570D7E0)
Player 2:     Field.uniform => 167
Player 2:   Field.uniform(size=(), generator=Generator(PCG64) at 0x11570D8C0)
Player 2:     Field.uniform => 96
Player 2:   Field.inplace_subtract(lhs=array(167, dtype=object), rhs=array(96, dtype=object))
Player 2:     Field.inplace_subtract => 71
Player 2:   AdditiveProtocolSuite.share => cicada.additive.AdditiveArrayShare(storage=71)
Player 2: AdditiveProtocolSuite.share(src=1, secret=array(3), shape=(), encoding=None)
Player 2:   Field.uniform(size=(), generator=Generator(PCG64) at 0x11570D7E0)
Player 2:     Field.uniform => 42
Player 2:   Field.uniform(size=(), generator=Generator(PCG64) at 0x11570D8C0)
Player 2:     Field.uniform => 128
Player 2:   Field.inplace_subtract(lhs=array(42, dtype=object), rhs=array(128, dtype=object))
Player 2:     Field.inplace_subtract => 41
Player 2:   AdditiveProtocolSuite.share => cicada.additive.AdditiveArrayShare(storage=41)
Player 2: Let's add some secrets!
Player 2: AdditiveProtocolSuite.add(lhs=cicada.additive.AdditiveArrayShare(storage=71), rhs=cicada.additive.AdditiveArrayShare(storage=41), encoding=None)
Player 2:   AdditiveProtocolSuite.field_add(lhs=cicada.additive.AdditiveArrayShare(storage=71), rhs=cicada.additive.AdditiveArrayShare(storage=41))
Player 2:     Field.add(lhs=array(71, dtype=object), rhs=array(41, dtype=object))
Player 2:       Field.add => 112
Player 2:     AdditiveProtocolSuite.field_add => cicada.additive.AdditiveArrayShare(storage=112)
Player 2:   AdditiveProtocolSuite.add => cicada.additive.AdditiveArrayShare(storage=112)
Player 2: Let's reveal the results!
Player 2: AdditiveProtocolSuite.reveal(share=cicada.additive.AdditiveArrayShare(storage=112), dst=None, encoding=None)
Player 2: --> 0 GATHER 112
Player 2: --> 1 GATHER 112
Player 2: --> 2 GATHER 112
Player 2: <-- 2 GATHER 112
Player 2: <-- 0 GATHER 33
Player 2: <-- 1 GATHER 2
Player 2:   Field.inplace_add(lhs=array(33, dtype=object), rhs=array(2, dtype=object))
Player 2:     Field.inplace_add => 35
Player 2:   Field.inplace_add(lhs=array(35, dtype=object), rhs=array(112, dtype=object))
Player 2:     Field.inplace_add => 20
Player 2:   FixedPoint.decode(array=array(20, dtype=object), field=cicada.arithmetic.Field(order=127))
Player 2:     FixedPoint.decode => 5.0
Player 2:   AdditiveProtocolSuite.reveal => 5.0




As expected, we now see a combination of application, message, and function call logging.

So far, the default formatting for the transcript has been chosen for human readability, but we might want to generate a machine-readable transcript for further analysis, such as zero knowledge proofs. Fortunately, basic_config allows us to override the full set of format strings supported by transcript.Formatter, so we can generate nearly any output we like. For example, let’s try generating some comma-delimited output that’s more easily machine-parsable:

[16]:
def main(communicator):
    handler = logging.FileHandler(f"player-{communicator.rank}.log", mode="w")
    handler.addFilter(transcript.ShowSentMessages())
    handler.addFilter(transcript.ShowReceivedMessages())
    handler = transcript.basic_config(handler,
        fmt="{processName},app,\"{msg}\"",
        msgfmt="{processName},msg,{message.src},{message.dst},{message.tag},{message.payload}",
        callfmt="{processName},call,{trace.fqname},\"{trace.args}\"",
        retfmt="{processName},return,{trace.fqname},{trace.result}",
        )
    transcript.logger.addHandler(handler)

    transcript.log("setup additive sharing")
    protocol = AdditiveProtocolSuite(communicator, order=127, encoding=FixedPoint(precision=2))
    transcript.log("share secret inputs")
    a_share = protocol.share(src=0, secret=numpy.array(2), shape=())
    b_share = protocol.share(src=1, secret=numpy.array(3), shape=())
    transcript.log("add secret shares")
    c_share = protocol.add(a_share, b_share)
    transcript.log("reveal results")
    c = protocol.reveal(c_share)

with transcript.record():
    SocketCommunicator.run(fn=main, world_size=3)

for rank in range(3):
    with open(f"player-{rank}.log", "r") as stream:
        print(stream.read())
        print("\n" * 2)
Player 0,app,"setup additive sharing"
Player 0,msg,0,1,PRZS,1457158578725225636
Player 0,msg,2,0,PRZS,3892883952794749772
Player 0,app,"share secret inputs"
Player 0,call,cicada.additive.AdditiveProtocolSuite.share,"{'src': 0, 'secret': array(2), 'shape': (), 'encoding': None}"
Player 0,call,cicada.arithmetic.Field.uniform,"{'size': (), 'generator': Generator(PCG64) at 0x11570D700}"
Player 0,return,cicada.arithmetic.Field.uniform,10
Player 0,call,cicada.arithmetic.Field.uniform,"{'size': (), 'generator': Generator(PCG64) at 0x11570D8C0}"
Player 0,return,cicada.arithmetic.Field.uniform,12
Player 0,call,cicada.arithmetic.Field.inplace_subtract,"{'lhs': array(10, dtype=object), 'rhs': array(12, dtype=object)}"
Player 0,return,cicada.arithmetic.Field.inplace_subtract,125
Player 0,call,cicada.encoding.FixedPoint.encode,"{'array': array(2), 'field': cicada.arithmetic.Field(order=127)}"
Player 0,return,cicada.encoding.FixedPoint.encode,8
Player 0,call,cicada.arithmetic.Field.inplace_add,"{'lhs': array(125, dtype=object), 'rhs': array(8, dtype=object)}"
Player 0,return,cicada.arithmetic.Field.inplace_add,6
Player 0,return,cicada.additive.AdditiveProtocolSuite.share,cicada.additive.AdditiveArrayShare(storage=6)
Player 0,call,cicada.additive.AdditiveProtocolSuite.share,"{'src': 1, 'secret': array(3), 'shape': (), 'encoding': None}"
Player 0,call,cicada.arithmetic.Field.uniform,"{'size': (), 'generator': Generator(PCG64) at 0x11570D700}"
Player 0,return,cicada.arithmetic.Field.uniform,62
Player 0,call,cicada.arithmetic.Field.uniform,"{'size': (), 'generator': Generator(PCG64) at 0x11570D8C0}"
Player 0,return,cicada.arithmetic.Field.uniform,174
Player 0,call,cicada.arithmetic.Field.inplace_subtract,"{'lhs': array(62, dtype=object), 'rhs': array(174, dtype=object)}"
Player 0,return,cicada.arithmetic.Field.inplace_subtract,15
Player 0,return,cicada.additive.AdditiveProtocolSuite.share,cicada.additive.AdditiveArrayShare(storage=15)
Player 0,app,"add secret shares"
Player 0,call,cicada.additive.AdditiveProtocolSuite.add,"{'lhs': cicada.additive.AdditiveArrayShare(storage=6), 'rhs': cicada.additive.AdditiveArrayShare(storage=15), 'encoding': None}"
Player 0,call,cicada.additive.AdditiveProtocolSuite.field_add,"{'lhs': cicada.additive.AdditiveArrayShare(storage=6), 'rhs': cicada.additive.AdditiveArrayShare(storage=15)}"
Player 0,call,cicada.arithmetic.Field.add,"{'lhs': array(6, dtype=object), 'rhs': array(15, dtype=object)}"
Player 0,return,cicada.arithmetic.Field.add,21
Player 0,return,cicada.additive.AdditiveProtocolSuite.field_add,cicada.additive.AdditiveArrayShare(storage=21)
Player 0,return,cicada.additive.AdditiveProtocolSuite.add,cicada.additive.AdditiveArrayShare(storage=21)
Player 0,app,"reveal results"
Player 0,call,cicada.additive.AdditiveProtocolSuite.reveal,"{'share': cicada.additive.AdditiveArrayShare(storage=21), 'dst': None, 'encoding': None}"
Player 0,msg,0,0,GATHER,21
Player 0,msg,0,0,GATHER,21
Player 0,msg,2,0,GATHER,1
Player 0,msg,1,0,GATHER,125
Player 0,call,cicada.arithmetic.Field.inplace_add,"{'lhs': array(21, dtype=object), 'rhs': array(125, dtype=object)}"
Player 0,return,cicada.arithmetic.Field.inplace_add,19
Player 0,call,cicada.arithmetic.Field.inplace_add,"{'lhs': array(19, dtype=object), 'rhs': array(1, dtype=object)}"
Player 0,return,cicada.arithmetic.Field.inplace_add,20
Player 0,msg,0,1,GATHER,21
Player 0,msg,0,2,GATHER,21
Player 0,call,cicada.encoding.FixedPoint.decode,"{'array': array(20, dtype=object), 'field': cicada.arithmetic.Field(order=127)}"
Player 0,return,cicada.encoding.FixedPoint.decode,5.0
Player 0,return,cicada.additive.AdditiveProtocolSuite.reveal,5.0




Player 1,app,"setup additive sharing"
Player 1,msg,1,2,PRZS,2318016089712042391
Player 1,msg,0,1,PRZS,1457158578725225636
Player 1,app,"share secret inputs"
Player 1,call,cicada.additive.AdditiveProtocolSuite.share,"{'src': 0, 'secret': array(2), 'shape': (), 'encoding': None}"
Player 1,call,cicada.arithmetic.Field.uniform,"{'size': (), 'generator': Generator(PCG64) at 0x11570D700}"
Player 1,return,cicada.arithmetic.Field.uniform,252
Player 1,call,cicada.arithmetic.Field.uniform,"{'size': (), 'generator': Generator(PCG64) at 0x11570D8C0}"
Player 1,return,cicada.arithmetic.Field.uniform,10
Player 1,call,cicada.arithmetic.Field.inplace_subtract,"{'lhs': array(252, dtype=object), 'rhs': array(10, dtype=object)}"
Player 1,return,cicada.arithmetic.Field.inplace_subtract,115
Player 1,return,cicada.additive.AdditiveProtocolSuite.share,cicada.additive.AdditiveArrayShare(storage=115)
Player 1,call,cicada.additive.AdditiveProtocolSuite.share,"{'src': 1, 'secret': array(3), 'shape': (), 'encoding': None}"
Player 1,call,cicada.arithmetic.Field.uniform,"{'size': (), 'generator': Generator(PCG64) at 0x11570D700}"
Player 1,return,cicada.arithmetic.Field.uniform,187
Player 1,call,cicada.arithmetic.Field.uniform,"{'size': (), 'generator': Generator(PCG64) at 0x11570D8C0}"
Player 1,return,cicada.arithmetic.Field.uniform,62
Player 1,call,cicada.arithmetic.Field.inplace_subtract,"{'lhs': array(187, dtype=object), 'rhs': array(62, dtype=object)}"
Player 1,return,cicada.arithmetic.Field.inplace_subtract,125
Player 1,call,cicada.encoding.FixedPoint.encode,"{'array': array(3), 'field': cicada.arithmetic.Field(order=127)}"
Player 1,return,cicada.encoding.FixedPoint.encode,12
Player 1,call,cicada.arithmetic.Field.inplace_add,"{'lhs': array(125, dtype=object), 'rhs': array(12, dtype=object)}"
Player 1,return,cicada.arithmetic.Field.inplace_add,10
Player 1,return,cicada.additive.AdditiveProtocolSuite.share,cicada.additive.AdditiveArrayShare(storage=10)
Player 1,app,"add secret shares"
Player 1,call,cicada.additive.AdditiveProtocolSuite.add,"{'lhs': cicada.additive.AdditiveArrayShare(storage=115), 'rhs': cicada.additive.AdditiveArrayShare(storage=10), 'encoding': None}"
Player 1,call,cicada.additive.AdditiveProtocolSuite.field_add,"{'lhs': cicada.additive.AdditiveArrayShare(storage=115), 'rhs': cicada.additive.AdditiveArrayShare(storage=10)}"
Player 1,call,cicada.arithmetic.Field.add,"{'lhs': array(115, dtype=object), 'rhs': array(10, dtype=object)}"
Player 1,return,cicada.arithmetic.Field.add,125
Player 1,return,cicada.additive.AdditiveProtocolSuite.field_add,cicada.additive.AdditiveArrayShare(storage=125)
Player 1,return,cicada.additive.AdditiveProtocolSuite.add,cicada.additive.AdditiveArrayShare(storage=125)
Player 1,app,"reveal results"
Player 1,call,cicada.additive.AdditiveProtocolSuite.reveal,"{'share': cicada.additive.AdditiveArrayShare(storage=125), 'dst': None, 'encoding': None}"
Player 1,msg,1,0,GATHER,125
Player 1,msg,1,1,GATHER,125
Player 1,msg,2,1,GATHER,1
Player 1,msg,1,1,GATHER,125
Player 1,msg,0,1,GATHER,21
Player 1,call,cicada.arithmetic.Field.inplace_add,"{'lhs': array(21, dtype=object), 'rhs': array(125, dtype=object)}"
Player 1,return,cicada.arithmetic.Field.inplace_add,19
Player 1,call,cicada.arithmetic.Field.inplace_add,"{'lhs': array(19, dtype=object), 'rhs': array(1, dtype=object)}"
Player 1,return,cicada.arithmetic.Field.inplace_add,20
Player 1,msg,1,2,GATHER,125
Player 1,call,cicada.encoding.FixedPoint.decode,"{'array': array(20, dtype=object), 'field': cicada.arithmetic.Field(order=127)}"
Player 1,return,cicada.encoding.FixedPoint.decode,5.0
Player 1,return,cicada.additive.AdditiveProtocolSuite.reveal,5.0




Player 2,app,"setup additive sharing"
Player 2,msg,2,0,PRZS,3892883952794749772
Player 2,msg,1,2,PRZS,2318016089712042391
Player 2,app,"share secret inputs"
Player 2,call,cicada.additive.AdditiveProtocolSuite.share,"{'src': 0, 'secret': array(2), 'shape': (), 'encoding': None}"
Player 2,call,cicada.arithmetic.Field.uniform,"{'size': (), 'generator': Generator(PCG64) at 0x11570D700}"
Player 2,return,cicada.arithmetic.Field.uniform,12
Player 2,call,cicada.arithmetic.Field.uniform,"{'size': (), 'generator': Generator(PCG64) at 0x11570D8C0}"
Player 2,return,cicada.arithmetic.Field.uniform,252
Player 2,call,cicada.arithmetic.Field.inplace_subtract,"{'lhs': array(12, dtype=object), 'rhs': array(252, dtype=object)}"
Player 2,return,cicada.arithmetic.Field.inplace_subtract,14
Player 2,return,cicada.additive.AdditiveProtocolSuite.share,cicada.additive.AdditiveArrayShare(storage=14)
Player 2,call,cicada.additive.AdditiveProtocolSuite.share,"{'src': 1, 'secret': array(3), 'shape': (), 'encoding': None}"
Player 2,call,cicada.arithmetic.Field.uniform,"{'size': (), 'generator': Generator(PCG64) at 0x11570D700}"
Player 2,return,cicada.arithmetic.Field.uniform,174
Player 2,call,cicada.arithmetic.Field.uniform,"{'size': (), 'generator': Generator(PCG64) at 0x11570D8C0}"
Player 2,return,cicada.arithmetic.Field.uniform,187
Player 2,call,cicada.arithmetic.Field.inplace_subtract,"{'lhs': array(174, dtype=object), 'rhs': array(187, dtype=object)}"
Player 2,return,cicada.arithmetic.Field.inplace_subtract,114
Player 2,return,cicada.additive.AdditiveProtocolSuite.share,cicada.additive.AdditiveArrayShare(storage=114)
Player 2,app,"add secret shares"
Player 2,call,cicada.additive.AdditiveProtocolSuite.add,"{'lhs': cicada.additive.AdditiveArrayShare(storage=14), 'rhs': cicada.additive.AdditiveArrayShare(storage=114), 'encoding': None}"
Player 2,call,cicada.additive.AdditiveProtocolSuite.field_add,"{'lhs': cicada.additive.AdditiveArrayShare(storage=14), 'rhs': cicada.additive.AdditiveArrayShare(storage=114)}"
Player 2,call,cicada.arithmetic.Field.add,"{'lhs': array(14, dtype=object), 'rhs': array(114, dtype=object)}"
Player 2,return,cicada.arithmetic.Field.add,1
Player 2,return,cicada.additive.AdditiveProtocolSuite.field_add,cicada.additive.AdditiveArrayShare(storage=1)
Player 2,return,cicada.additive.AdditiveProtocolSuite.add,cicada.additive.AdditiveArrayShare(storage=1)
Player 2,app,"reveal results"
Player 2,call,cicada.additive.AdditiveProtocolSuite.reveal,"{'share': cicada.additive.AdditiveArrayShare(storage=1), 'dst': None, 'encoding': None}"
Player 2,msg,2,0,GATHER,1
Player 2,msg,2,1,GATHER,1
Player 2,msg,2,2,GATHER,1
Player 2,msg,2,2,GATHER,1
Player 2,msg,0,2,GATHER,21
Player 2,msg,1,2,GATHER,125
Player 2,call,cicada.arithmetic.Field.inplace_add,"{'lhs': array(21, dtype=object), 'rhs': array(125, dtype=object)}"
Player 2,return,cicada.arithmetic.Field.inplace_add,19
Player 2,call,cicada.arithmetic.Field.inplace_add,"{'lhs': array(19, dtype=object), 'rhs': array(1, dtype=object)}"
Player 2,return,cicada.arithmetic.Field.inplace_add,20
Player 2,call,cicada.encoding.FixedPoint.decode,"{'array': array(20, dtype=object), 'field': cicada.arithmetic.Field(order=127)}"
Player 2,return,cicada.encoding.FixedPoint.decode,5.0
Player 2,return,cicada.additive.AdditiveProtocolSuite.reveal,5.0




This is pretty good, and should work with most delimited text parsers, including Python’s csv module; however, the Python objects in the function traces aren’t very portable - it might be nice to convert them into something language agnostic. One option would be to use transcript.JSONArguments to replace them with JSON representations:

[18]:
def main(communicator):
    handler = logging.FileHandler(f"player-{communicator.rank}.log", mode="w")
    handler.addFilter(transcript.ShowSentMessages())
    handler.addFilter(transcript.ShowReceivedMessages())
    handler = transcript.basic_config(handler,
        fmt="{processName},app,\"{msg}\"",
        msgfmt="{processName},msg,{message.src},{message.dst},{message.tag},{message.payload}",
        callfmt="{processName},call,{trace.fqname},{trace.jsonargs}",
        retfmt="{processName},return,{trace.fqname},{trace.jsonresult}",
        )
    handler.addFilter(transcript.JSONArguments())
    transcript.logger.addHandler(handler)

    transcript.log("setup additive sharing")
    protocol = AdditiveProtocolSuite(communicator, order=127, encoding=FixedPoint(precision=2))
    transcript.log("share secret inputs")
    a_share = protocol.share(src=0, secret=numpy.array(2), shape=())
    b_share = protocol.share(src=1, secret=numpy.array(3), shape=())
    transcript.log("add secret shares")
    c_share = protocol.add(a_share, b_share)
    transcript.log("reveal results")
    c = protocol.reveal(c_share)

with transcript.record():
    SocketCommunicator.run(fn=main, world_size=3)

for rank in range(3):
    with open(f"player-{rank}.log", "r") as stream:
        print(stream.read())
        print("\n" * 2)
Player 0,app,"setup additive sharing"
Player 0,msg,0,1,PRZS,57912766695500320
Player 0,msg,2,0,PRZS,3042906077127007813
Player 0,app,"share secret inputs"
Player 0,call,cicada.additive.AdditiveProtocolSuite.share,src,0,secret,2,shape,[],encoding,null
Player 0,call,cicada.arithmetic.Field.uniform,size,[],generator,null
Player 0,return,cicada.arithmetic.Field.uniform,227
Player 0,call,cicada.arithmetic.Field.uniform,size,[],generator,null
Player 0,return,cicada.arithmetic.Field.uniform,186
Player 0,call,cicada.arithmetic.Field.inplace_subtract,lhs,227,rhs,186
Player 0,return,cicada.arithmetic.Field.inplace_subtract,41
Player 0,call,cicada.encoding.FixedPoint.encode,array,2,field,null
Player 0,return,cicada.encoding.FixedPoint.encode,8
Player 0,call,cicada.arithmetic.Field.inplace_add,lhs,41,rhs,8
Player 0,return,cicada.arithmetic.Field.inplace_add,49
Player 0,return,cicada.additive.AdditiveProtocolSuite.share,49
Player 0,call,cicada.additive.AdditiveProtocolSuite.share,src,1,secret,3,shape,[],encoding,null
Player 0,call,cicada.arithmetic.Field.uniform,size,[],generator,null
Player 0,return,cicada.arithmetic.Field.uniform,83
Player 0,call,cicada.arithmetic.Field.uniform,size,[],generator,null
Player 0,return,cicada.arithmetic.Field.uniform,206
Player 0,call,cicada.arithmetic.Field.inplace_subtract,lhs,83,rhs,206
Player 0,return,cicada.arithmetic.Field.inplace_subtract,4
Player 0,return,cicada.additive.AdditiveProtocolSuite.share,4
Player 0,app,"add secret shares"
Player 0,call,cicada.additive.AdditiveProtocolSuite.add,lhs,49,rhs,4,encoding,null
Player 0,call,cicada.additive.AdditiveProtocolSuite.field_add,lhs,49,rhs,4
Player 0,call,cicada.arithmetic.Field.add,lhs,49,rhs,4
Player 0,return,cicada.arithmetic.Field.add,53
Player 0,return,cicada.additive.AdditiveProtocolSuite.field_add,53
Player 0,return,cicada.additive.AdditiveProtocolSuite.add,53
Player 0,app,"reveal results"
Player 0,call,cicada.additive.AdditiveProtocolSuite.reveal,share,53,dst,null,encoding,null
Player 0,msg,0,0,GATHER,53
Player 0,msg,0,0,GATHER,53
Player 0,msg,2,0,GATHER,20
Player 0,msg,1,0,GATHER,74
Player 0,call,cicada.arithmetic.Field.inplace_add,lhs,53,rhs,74
Player 0,return,cicada.arithmetic.Field.inplace_add,0
Player 0,call,cicada.arithmetic.Field.inplace_add,lhs,0,rhs,20
Player 0,return,cicada.arithmetic.Field.inplace_add,20
Player 0,msg,0,1,GATHER,53
Player 0,msg,0,2,GATHER,53
Player 0,call,cicada.encoding.FixedPoint.decode,array,20,field,null
Player 0,return,cicada.encoding.FixedPoint.decode,5.0
Player 0,return,cicada.additive.AdditiveProtocolSuite.reveal,5.0




Player 1,app,"setup additive sharing"
Player 1,msg,1,2,PRZS,5174698084867923504
Player 1,msg,0,1,PRZS,57912766695500320
Player 1,app,"share secret inputs"
Player 1,call,cicada.additive.AdditiveProtocolSuite.share,src,0,secret,2,shape,[],encoding,null
Player 1,call,cicada.arithmetic.Field.uniform,size,[],generator,null
Player 1,return,cicada.arithmetic.Field.uniform,79
Player 1,call,cicada.arithmetic.Field.uniform,size,[],generator,null
Player 1,return,cicada.arithmetic.Field.uniform,227
Player 1,call,cicada.arithmetic.Field.inplace_subtract,lhs,79,rhs,227
Player 1,return,cicada.arithmetic.Field.inplace_subtract,106
Player 1,return,cicada.additive.AdditiveProtocolSuite.share,106
Player 1,call,cicada.additive.AdditiveProtocolSuite.share,src,1,secret,3,shape,[],encoding,null
Player 1,call,cicada.arithmetic.Field.uniform,size,[],generator,null
Player 1,return,cicada.arithmetic.Field.uniform,166
Player 1,call,cicada.arithmetic.Field.uniform,size,[],generator,null
Player 1,return,cicada.arithmetic.Field.uniform,83
Player 1,call,cicada.arithmetic.Field.inplace_subtract,lhs,166,rhs,83
Player 1,return,cicada.arithmetic.Field.inplace_subtract,83
Player 1,call,cicada.encoding.FixedPoint.encode,array,3,field,null
Player 1,return,cicada.encoding.FixedPoint.encode,12
Player 1,call,cicada.arithmetic.Field.inplace_add,lhs,83,rhs,12
Player 1,return,cicada.arithmetic.Field.inplace_add,95
Player 1,return,cicada.additive.AdditiveProtocolSuite.share,95
Player 1,app,"add secret shares"
Player 1,call,cicada.additive.AdditiveProtocolSuite.add,lhs,106,rhs,95,encoding,null
Player 1,call,cicada.additive.AdditiveProtocolSuite.field_add,lhs,106,rhs,95
Player 1,call,cicada.arithmetic.Field.add,lhs,106,rhs,95
Player 1,return,cicada.arithmetic.Field.add,74
Player 1,return,cicada.additive.AdditiveProtocolSuite.field_add,74
Player 1,return,cicada.additive.AdditiveProtocolSuite.add,74
Player 1,app,"reveal results"
Player 1,call,cicada.additive.AdditiveProtocolSuite.reveal,share,74,dst,null,encoding,null
Player 1,msg,1,0,GATHER,74
Player 1,msg,1,1,GATHER,74
Player 1,msg,1,1,GATHER,74
Player 1,msg,2,1,GATHER,20
Player 1,msg,0,1,GATHER,53
Player 1,call,cicada.arithmetic.Field.inplace_add,lhs,53,rhs,74
Player 1,return,cicada.arithmetic.Field.inplace_add,0
Player 1,call,cicada.arithmetic.Field.inplace_add,lhs,0,rhs,20
Player 1,return,cicada.arithmetic.Field.inplace_add,20
Player 1,msg,1,2,GATHER,74
Player 1,call,cicada.encoding.FixedPoint.decode,array,20,field,null
Player 1,return,cicada.encoding.FixedPoint.decode,5.0
Player 1,return,cicada.additive.AdditiveProtocolSuite.reveal,5.0




Player 2,app,"setup additive sharing"
Player 2,msg,2,0,PRZS,3042906077127007813
Player 2,msg,1,2,PRZS,5174698084867923504
Player 2,app,"share secret inputs"
Player 2,call,cicada.additive.AdditiveProtocolSuite.share,src,0,secret,2,shape,[],encoding,null
Player 2,call,cicada.arithmetic.Field.uniform,size,[],generator,null
Player 2,return,cicada.arithmetic.Field.uniform,186
Player 2,call,cicada.arithmetic.Field.uniform,size,[],generator,null
Player 2,return,cicada.arithmetic.Field.uniform,79
Player 2,call,cicada.arithmetic.Field.inplace_subtract,lhs,186,rhs,79
Player 2,return,cicada.arithmetic.Field.inplace_subtract,107
Player 2,return,cicada.additive.AdditiveProtocolSuite.share,107
Player 2,call,cicada.additive.AdditiveProtocolSuite.share,src,1,secret,3,shape,[],encoding,null
Player 2,call,cicada.arithmetic.Field.uniform,size,[],generator,null
Player 2,return,cicada.arithmetic.Field.uniform,206
Player 2,call,cicada.arithmetic.Field.uniform,size,[],generator,null
Player 2,return,cicada.arithmetic.Field.uniform,166
Player 2,call,cicada.arithmetic.Field.inplace_subtract,lhs,206,rhs,166
Player 2,return,cicada.arithmetic.Field.inplace_subtract,40
Player 2,return,cicada.additive.AdditiveProtocolSuite.share,40
Player 2,app,"add secret shares"
Player 2,call,cicada.additive.AdditiveProtocolSuite.add,lhs,107,rhs,40,encoding,null
Player 2,call,cicada.additive.AdditiveProtocolSuite.field_add,lhs,107,rhs,40
Player 2,call,cicada.arithmetic.Field.add,lhs,107,rhs,40
Player 2,return,cicada.arithmetic.Field.add,20
Player 2,return,cicada.additive.AdditiveProtocolSuite.field_add,20
Player 2,return,cicada.additive.AdditiveProtocolSuite.add,20
Player 2,app,"reveal results"
Player 2,call,cicada.additive.AdditiveProtocolSuite.reveal,share,20,dst,null,encoding,null
Player 2,msg,2,0,GATHER,20
Player 2,msg,2,1,GATHER,20
Player 2,msg,2,2,GATHER,20
Player 2,msg,2,2,GATHER,20
Player 2,msg,0,2,GATHER,53
Player 2,msg,1,2,GATHER,74
Player 2,call,cicada.arithmetic.Field.inplace_add,lhs,53,rhs,74
Player 2,return,cicada.arithmetic.Field.inplace_add,0
Player 2,call,cicada.arithmetic.Field.inplace_add,lhs,0,rhs,20
Player 2,return,cicada.arithmetic.Field.inplace_add,20
Player 2,call,cicada.encoding.FixedPoint.decode,array,20,field,null
Player 2,return,cicada.encoding.FixedPoint.decode,5.0
Player 2,return,cicada.additive.AdditiveProtocolSuite.reveal,5.0




… note that we added transcript.JSONArguments after transcript.basic_config so it doesn’t waste time converting arguments for events that will be filtered-out later anyway.

If you need to further customize the output, you have the full generality of the Python logging system at your fingertips. We encourage you to look at the many log filters defined in the transcript module for code examples and inspiration.

See also

Logging