Files
prompts/docs/skills/python-logging/references/network-logging-minimal-example.md
T
2026-07-08 22:39:46 -05:00

5.6 KiB

Network Logging Minimal Example

Use this reference when an application should send logs over TCP to a local receiver and you want a complete, working baseline.

This page shows how the pieces fit together end to end:

  • application code logs with named loggers
  • startup applies one declarative LOGGING config
  • SocketHandler sends records to a receiver
  • receiver uses socketserver and local logging config for final routing

Source docs to keep nearby:

Minimal Topology

app module -> logger -> SocketHandler -> TCP receiver -> local handlers

1) Client Logging Config (Declarative)

import logging.config

LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,
    "formatters": {
        "console": {
            "format": "%(asctime)s %(levelname)s %(name)s %(message)s",
            "datefmt": "%Y-%m-%d %H:%M:%S",
        }
    },
    "handlers": {
        "network": {
            "class": "logging.handlers.SocketHandler",
            "host": "127.0.0.1",
            "port": 9020,
        },
        "console": {
            "class": "logging.StreamHandler",
            "formatter": "console",
            "level": "INFO",
            "stream": "ext://sys.stdout",
        },
    },
    "root": {
        "level": "INFO",
        "handlers": ["network", "console"],
    },
}


def configure_logging() -> None:
    logging.config.dictConfig(LOGGING)
import logging

logger = logging.getLogger(__name__)


def process_order(order_id: str) -> None:
    logger.info("Processing order %s", order_id)
from feature import process_order
from logging_config import configure_logging


def main() -> None:
    configure_logging()
    process_order("A-42")


if __name__ == "__main__":
    main()

2) Receiver Logging Config (Declarative)

import logging.config

LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,
    "formatters": {
        "console": {
            "format": "%(asctime)s %(levelname)s %(name)s %(message)s",
            "datefmt": "%Y-%m-%d %H:%M:%S",
        }
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "formatter": "console",
            "stream": "ext://sys.stdout",
        }
    },
    "root": {"level": "INFO", "handlers": ["console"]},
}


def configure_receiver_logging() -> None:
    logging.config.dictConfig(LOGGING)

3) Cookbook Receiver (socketserver) Implementation

This receiver follows the same structure as the Python logging cookbook example.

SocketHandler sends:

  • a 4-byte big-endian length prefix
  • a pickle payload containing a LogRecord dictionary
import logging
import pickle
import socketserver
import struct

from receiver_logging_config import configure_receiver_logging


class LogRecordStreamHandler(socketserver.StreamRequestHandler):
    def handle(self) -> None:
        while True:
            chunk = self.connection.recv(4)
            if len(chunk) < 4:
                break

            payload_len = struct.unpack(">L", chunk)[0]
            payload = self.connection.recv(payload_len)
            while len(payload) < payload_len:
                payload = payload + self.connection.recv(payload_len - len(payload))

            try:
                record_dict = pickle.loads(payload)
                record = logging.makeLogRecord(record_dict)
            except Exception:
                logging.getLogger(__name__).exception("Dropped malformed log record")
                continue

            self.handle_log_record(record)

    def handle_log_record(self, record: logging.LogRecord) -> None:
        logger = logging.getLogger(record.name)
        if logger.isEnabledFor(record.levelno):
            logger.handle(record)


class LogRecordSocketReceiver(socketserver.ThreadingTCPServer):
    allow_reuse_address = True


def main() -> None:
    configure_receiver_logging()
    with LogRecordSocketReceiver(("127.0.0.1", 9020), LogRecordStreamHandler) as server:
        logging.getLogger(__name__).info("Receiver listening on 127.0.0.1:9020")
        server.serve_forever()


if __name__ == "__main__":
    main()

4) How It Fits Together In Practice

  1. Start log_receiver.py.
  2. Start main.py from the client app.
  3. Client logs go to console and TCP.
  4. Receiver reconstructs records and emits them through its own handlers.

This split keeps app emission and receiver routing independent while still being fully runnable.

Important Security Note

SocketHandler uses pickle serialization. Treat this as trusted-network-only transport.

  • Bind receiver to localhost or a trusted private network.
  • Do not expose this receiver to untrusted clients.
  • For hostile boundaries, use JSON/TLS with authenticated ingestion instead of raw pickle.

Review Checklist

  1. Is there one LOGGING dict per process role (client and receiver)?
  2. Is dictConfig called once at each process startup?
  3. Does the receiver decode length-prefixed payloads correctly?
  4. Do modules only use logging.getLogger(__name__)?
  5. Is the receiver endpoint protected by trust boundaries?