IPC and logging#

Under every portal, context and stream sits a per-peer Channel: a msgpack-typed messaging link wrapping one OS transport connection. Transports are pluggable per actor via enable_transports=['tcp' | 'uds'] — TCP is the default, UDS (unix domain sockets) gives you port-less, same-host IPC with kernel-provided peer credentials for free — and exactly one transport may currently be enabled per actor.

layered runtime stack from app code down to transports

Where Channel sits in the runtime stack.#

Addresses are “unwrapped” tuples at the API edges: ('host', port) for TCP, filesystem-path pairs for UDS. For the full layering story — transport protocols, the IPC server, address types and the msg loop — see Anatomy of the runtime.

Channel#

class tractor.Channel(transport=None)[source]#

An inter-process channel for communication between (remote) actors.

Wraps a MsgStream: transport + encoding IPC connection.

Currently we only support trio.SocketStream for transport (aka TCP) and the msgpack interchange format via the msgspec codec libary.

Parameters:

transport (MsgTransport | None)

property closed: bool#

Was .aclose() successfully called?

property aid: Aid#

Peer actor’s ID.

apply_codec(codec)[source]#

Temporarily override the underlying IPC msg codec for dynamic enforcement of messaging schema.

Parameters:

codec (MsgCodec)

Return type:

None

async send(payload, hide_tb=False)[source]#

Send a coded msg-blob over the underlying IPC transport.

This fn raises TransportClosed on comms failures and is normally handled by higher level runtime machinery for the expected-graceful cases, normally ephemercal (re/dis)connects.

Parameters:
Return type:

None

async recv()[source]#

Receive the latest (queued) msg-blob from the underlying IPC transport.

Return type:

Any

connected()[source]#

Predicate whether underlying IPC tpt is connected.

Return type:

bool

Deprecated since version 0.1.0a6: Channel.uid warns; use Channel.aid which carries richer (optional) identity fields beyond the legacy (name, uuid) pair.

Note

You rarely construct a Channel yourself — the runtime hands them out via Portal.chan and Context.chan. Treat the send/recv surface as advanced API: normal apps should speak MsgStream instead.

Choosing a transport#

examples/uds_transport_actor_tree.py#
'''
Demonstrate an actor tree which talks over unix-domain-socket
(UDS) transport instead of the default TCP: pass
`enable_transports=['uds']` when opening the root and every
subactor inherits the preference.

Every channel address is a filesystem socket path (no TCP port
in sight!) and, as a kernel-provided bonus, the peer's pid is
exchanged for free via `SO_PEERCRED`.

'''
import os

import trio
import tractor


async def report_addr() -> str:
    '''
    Return this actor's own accept (bind) addr + pid.

    '''
    actor = tractor.current_actor()
    addr: tuple = actor.accept_addr
    pid: int = os.getpid()
    return f'{actor.name}@{addr} pid={pid}'


async def main() -> None:
    async with tractor.open_nursery(
        enable_transports=['uds'],
    ) as an:
        portal = await an.start_actor(
            'uds_child',
            enable_modules=[__name__],
        )
        # the channel's remote addr is a `UDSAddress`: a
        # filesystem socket path, NOT a (host, port) pair!
        raddr = portal.chan.raddr
        assert raddr.proto_key == 'uds'
        # NOTE, `.sockpath` is the *shared listener* socket file
        # (named for the root registrar) this channel rode in
        # on, NOT a per-child path; the child-specific identity
        # we get for free is the kernel-reported peer pid (via
        # `SO_PEERCRED`).
        print(
            f'portal chan tpt proto: {raddr.proto_key!r}\n'
            f'listener sock file: {raddr.sockpath}\n'
            f'kernel-reported peer pid: {raddr.maybe_pid}\n'
        )
        # ask the child for its OWN distinct bind addr: another
        # socket-file path under the runtime dir.
        print(f'child says: {await portal.run(report_addr)}')
        await portal.cancel_actor()


if __name__ == '__main__':
    trio.run(main)

Logging#

tractor.log provides the structured, colorized console logging used across the runtime — with actor-name + task-aware record headers and extra log levels below logging.DEBUG ('transport', 'runtime', 'cancel', 'devx') for spelunking the runtime itself. Use it for your app too: it’s already distributed-system aware.

tractor.log.get_logger(name=None, pkg_name='tractor', _root_name=None, logger=None, subsys_spec=None, mk_sublog=True, _strict_debug=False)[source]#

Return the tractor-library root logger or a sub-logger for name if provided.

When name is left null we try to auto-detect the caller’s mod.__name__ and use that as a the sub-logger key. This allows for example creating a module level instance like,

log = tractor.log.get_logger(_root_name='mylib')

and by default all console record headers will show the caller’s (of any log.<level>()-method) correct sub-pkg’s + py-module-file.

Parameters:
  • name (str | None)

  • pkg_name (str)

  • _root_name (str | None)

  • logger (Logger | None)

  • subsys_spec (str | None)

  • mk_sublog (bool)

  • _strict_debug (bool)

Return type:

StackLevelAdapter

tractor.log.get_console_log(level=None, logger=None, **kwargs)[source]#

Get a tractor-style logging instance: a Logger wrapped in a StackLevelAdapter which injects various concurrency-primitive (process, thread, task) fields and enables a StreamHandler that writes on stderr using colorlog formatting.

Yeah yeah, i know we can use logging.config.dictConfig(). You do it.

Parameters:
  • level (str | None)

  • logger (Logger | StackLevelAdapter | None)

Return type:

LoggerAdapter

Note

The TRACTOR_LOGLEVEL env var overrides any caller-passed loglevel (e.g. to open_root_actor()) so you can crank console verbosity without touching code; subactors inherit the root’s level by default.

See also

Anatomy of the runtime for the transport/server internals, Discovery and the registrar for how channel addresses get registered and found, and Typed messaging: tractor.msg for the codec layer every channel speaks.