ARC-28: Structured event logging

ARC-28 provides a methodology for structured logging by Algorand smart contracts. It introduces the concept of Events, where data contained in logs may be categorized and structured.

Each Event is identified by a unique 4-byte identifier derived from its Event Signature. The Event Signature is a UTF-8 string comprised of the event’s name, followed by the names of the ARC-4 data types contained in the event, all enclosed in parentheses (EventName(type1,type2,...)) e.g.:

Swapped(uint64,uint64)

Events are emitting by including them in the log output. The metadata that identifies the event should then be included in the ARC-4 contract output so that a calling client can parse the logs to parse the structured data out. This part of the ARC-28 spec isn’t yet implemented in Algorand Python, but it’s on the roadmap.

Emitting Events

To emit an ARC-28 event in Algorand Python you can use the emit function, which appears in the algopy.arc4 namespace for convenience since it heavily uses ARC-4 types and is essentially an extension of the ARC-4 specification. This function takes care of encoding the event payload to conform to the ARC-28 specification and there are 3 overloads:

Here’s an example contract that emits events:

from algopy import ARC4Contract, arc4

class Swapped(arc4.Struct):
    a: arc4.UInt64
    b: arc4.UInt64

class EventEmitter(ARC4Contract):
    @arc4.abimethod
    def emit_swapped(self, a: arc4.UInt64, b: arc4.UInt64) -> None:
        arc4.emit(Swapped(b, a))
        arc4.emit("Swapped(uint64,uint64)", b, a)
        arc4.emit("Swapped", b, a)

It’s worth noting that the ARC-28 event signature needs to be known at compile time so the event name can’t be a dynamic type and must be a static string literal or string module constant. If you want to emit dynamic events you can do so using the log method, but you’d need to manually construct the correct series of bytes and the compiler won’t be able to emit the ARC-28 metadata so you’ll need to also manually parse the logs in your client.

Examples of manually constructing an event:

# This is essentially what the `emit` method is doing, noting that a,b need to be encoded
#   as a tuple so below (simple concat) only works for static ARC-4 types
log(arc4.arc4_signature("Swapped(uint64,uint64)"), a, b)

# or, if you wanted it to be truly dynamic for some reason,
#   (noting this has a non-trivial opcode cost) and assuming in this case
#   that `event_suffix` is already defined as a `String`:
event_name = String("Event") + event_suffix
event_selector = op.sha512_256((event_name + "(uint64)").bytes)[:4]
log(event_selector, UInt64(6))