Skip to main content

Pontem edge SDK — logs, metrics, and config for edge devices

Project description

Pontem Python SDK

Logs, metrics, and config for processes running on Pontem-managed edge devices. Zero runtime dependencies — stdlib only.

Requirements

  • Python 3.9+.

Install

pip install pontem

Quick start

import pontem

pontem.init(service_name="my-service")

pontem.logger.info("model loaded", model="scoring_v3")

pontem.metrics.count("frames_processed")
with pontem.metrics.timer("model.inference"):
    result = model.predict(frame)

pontem.shutdown()  # also runs at process exit

That's it. Telemetry lands as JSONL under /var/lib/pontem/services/<service_name>/logs/. The agent picks it up.

Already using stdlib logging? Pass stdlib_logging=True to init and your existing logging.getLogger(...).info(...) calls produce Pontem records — no call-site changes. See Use with stdlib logging below.


Logging

OTel-aligned severity levels:

Method OTel SeverityNumber When to use
trace 1 Fine-grained debugging
debug 5 Diagnostic information
info 9 Normal operational events
warn 13 Unexpected but recoverable
error 17 Errors that need attention
fatal 21 Unrecoverable failures

The enum is at pontem.log.Level (Level.TRACE, Level.INFO, …).

Direct API

pontem.logger.info("model loaded", model="scoring_v3")
pontem.logger.warn("high latency", latency_ms=120, endpoint="/predict")
pontem.logger.error("inference failed", error=str(e), frame_id=frame.id)

Keyword arguments become structured attributes. Calls are non-blocking and queued; serialization and disk I/O run on the background thread.

This is what you want on hot paths.

Use with stdlib logging

If your code already uses logging.getLogger(...).info(...), enable the drop-in path:

import logging
import pontem

logging.basicConfig(level=logging.INFO)            # 1. set up your handlers first
pontem.init(service_name="my-service",             # 2. then init with the flag
            stdlib_logging=True)

logging.getLogger(__name__).info("model loaded", extra={"model": "v3"})

What it does: installs PontemFormatter on every handler currently on the root logger. Destinations, rotation policies, and filters stay intact — only the on-the-wire format changes.

Call order matters. The flag swaps formatters on handlers attached to root at the time of init. Run basicConfig / dictConfig / addHandler first; otherwise init raises. Handlers added after init are not picked up automatically — call PontemFormatter.install() again to apply to them.

You can mix paths freely: pontem.logger.* on hot inference loops, stdlib elsewhere. Both produce the same wire shape.

The SDK's own logs (pontem.sdk, pontem.emit, …) propagate to your chain too. Quiet them with stdlib mechanisms:

logging.getLogger("pontem").setLevel(logging.WARNING)  # WARN+ only
logging.getLogger("pontem").propagate = False          # drop entirely

Custom formatter setup

When you need finer control than the stdlib_logging=True flag — e.g. attaching the formatter to specific handlers, or wiring it through dictConfig:

from logging.handlers import RotatingFileHandler
from pontem.log import PontemFormatter

handler = RotatingFileHandler("/var/log/myapp/app.log", maxBytes=10_000_000)
handler.setFormatter(PontemFormatter(service_name="my-service"))
logging.getLogger().addHandler(handler)

dictConfig (YAML / JSON):

formatters:
  pontem:
    (): pontem.log.PontemFormatter
    service_name: my-service
handlers:
  console:
    class: logging.StreamHandler
    formatter: pontem
root:
  handlers: [console]
  level: INFO

Resource attributes (service.name, service.version, device.id, …) come from constructor kwargs first, falling back to whatever pontem.init() populated. service.name is required from at least one source.

For both formatter paths:

  • Set the root level. Stdlib defaults to WARNING; info/debug records are filtered before reaching any handler. basicConfig(level=logging.INFO) is the standard fix.
  • No emit pipeline. Records flow through your handler's I/O (sync FileHandler, etc.), not through Pontem's bounded queue + background writer. For non-blocking, queued, rotation-and-gzip behavior on hot paths, use the direct API.

Metrics

Aggregated in memory; the background thread flushes summaries to metrics.jsonl periodically. All public methods return in under 1µs.

Counter

Incremented each call; flushed as a single sum per (name, attrs).

pontem.metrics.count("detections", class_name="apple")
pontem.metrics.count("bytes_sent", len(payload))
metrics.count(name, amount=1, **attrs)

Histogram

Records count, sum, min, max per flush interval.

pontem.metrics.record("payload_size", len(data), unit="bytes")
pontem.metrics.record("confidence", score, model="v3")
metrics.record(name, value, *, unit="", **attrs)

Gauge

Last write wins per flush interval.

pontem.metrics.set_gauge("gpu_temp", 72.0, unit="celsius", gpu="0")
pontem.metrics.set_gauge("queue_depth", len(queue))
metrics.set_gauge(name, value, *, unit="", **attrs)

Timer

Context manager or decorator. Records elapsed time to a histogram.

with pontem.metrics.timer("model.inference"):
    result = model.predict(frame)

@pontem.metrics.timer("preprocessing")
def preprocess(frame):
    ...
metrics.timer(name, *, unit="s", **attrs)

Cardinality

Each unique (name, attributes) pair is its own aggregation bucket. Don't put unbounded values (request IDs, timestamps, user-supplied strings) in attributes — memory grows without bound.


Config

Reads agent-managed values from /var/lib/pontem/config/<namespace>.json (or $PONTEM_CONFIG_DIR). Each namespace is its own JSON file with a flat {key: value} map.

threshold = pontem.config("scoring", "confidence_threshold", default=0.85)
regions = pontem.config("detection", "enabled_regions", default=["all"])

pontem.config.reload()  # call after the agent signals an update

default is returned when either the namespace file or the key is absent. Files are loaded lazily on first lookup per namespace and cached until reload().


Deployment

Docker / compose (stdout emit)

For containerized deployments where a sidecar log collector tails stdout, switch the direct API to stdout mode:

pontem.init(service_name="my-service", emit_target="stdout")

Or via env var (lets the same image run on edge devices and compose hosts):

PONTEM_EMIT_TARGET=stdout

Precedence: init(emit_target=...) > PONTEM_EMIT_TARGET > default "file".

In stdout mode, emit_dir, file rotation, and gzip compression are no-ops — the docker daemon's json-file driver handles container log rotation. This setting affects the direct API only; formatter paths always go through your handler chain.

File output and rotation

In file mode (default):

$PONTEM_EMIT_DIR/
    logs.jsonl                   # active (SDK writes)
    logs.jsonl.1713100000.gz     # rotated + compressed (agent picks up + deletes)
    metrics.jsonl                # active
    metrics.jsonl.1713100000.gz  # rotated + compressed

Files rotate at 10 MB, are gzip-compressed on the background thread, and up to 5 rotated files are kept per channel.


Reference

init()

pontem.init(
    service_name="my-service",   # required — identifies the service in all telemetry
    service_version="1.2.0",     # auto-detected from package metadata if omitted
    emit_dir="/custom/path",     # overrides PONTEM_EMIT_DIR
    emit_target="file",          # "file" (default) or "stdout"
    stdlib_logging=False,        # True → install PontemFormatter on root handlers
)

init() kwargs take precedence over environment variables. Call once at startup.

shutdown()

Flushes aggregated metrics, drains the log queue, and closes files. Registered automatically via atexit; call explicitly if you need a deterministic flush.

Environment variables

Variable Purpose Default
PONTEM_EMIT_DIR JSONL output directory /var/lib/pontem/services/<service_name>/logs
PONTEM_EMIT_TARGET "file" or "stdout" "file"
PONTEM_CONFIG_DIR Directory containing per-namespace <namespace>.json files /var/lib/pontem/config
PONTEM_DEVICE_ID Device identifier (set by agent)
PONTEM_TENANT_ID Tenant identifier (set by agent)
PONTEM_CONFIG_VERSION Config version (set by agent)

Wire format

Log record:

{
  "timestamp": "2025-01-15T10:30:00.123456Z",
  "severityNumber": 9,
  "severityText": "INFO",
  "body": "model loaded",
  "attributes": {"model": "scoring_v3"},
  "resource": {"service.name": "my-service"}
}

Metric record:

{
  "timestamp": "2025-01-15T10:30:01.000000Z",
  "name": "detections",
  "type": "counter",
  "value": 47,
  "attributes": {"class_name": "apple"},
  "resource": {"service.name": "my-service"}
}

The full schema lives in SCHEMA.md.


Troubleshooting

No telemetry on disk. Confirm pontem.init() runs before any logger/metrics call. Check the emit directory exists and is writable: ls -la /var/lib/pontem/services/<service_name>/logs/.

pontem.config(...) always returns default. Confirm the agent has written the namespace file: cat /var/lib/pontem/config/<namespace>.json. Verify namespace matches the filename (without .json) and key matches a top-level key in that file. Call pontem.config.reload() after the agent updates the file.

info/debug logs missing from console output. Stdlib's default level is WARNING. Lower it: logging.basicConfig(level=logging.DEBUG).

Memory creeping up. You probably have unbounded attribute values (request IDs, timestamps) on a metric. Each unique (name, attributes) pair is a separate bucket — keep attribute values low-cardinality.

pontem.init(stdlib_logging=True) raises RuntimeError. Root has no handlers to swap. Run basicConfig / dictConfig / addHandler before init. If you add handlers after init, call PontemFormatter.install() to apply the formatter to them.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pontem-0.4.0.tar.gz (23.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pontem-0.4.0-py3-none-any.whl (23.4 kB view details)

Uploaded Python 3

File details

Details for the file pontem-0.4.0.tar.gz.

File metadata

  • Download URL: pontem-0.4.0.tar.gz
  • Upload date:
  • Size: 23.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pontem-0.4.0.tar.gz
Algorithm Hash digest
SHA256 6b155fc38309b83a87d0aeff1779bbbb11640b92c64f6d19b18965f56704b61a
MD5 799ff07f763a1da5eae02c64d5ccebf7
BLAKE2b-256 2b68a26baf3b3ec701e90dcbb3f0a591818582070d3510a7b7921933195eaa4c

See more details on using hashes here.

File details

Details for the file pontem-0.4.0-py3-none-any.whl.

File metadata

  • Download URL: pontem-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 23.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pontem-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2305401b3ca087445c51c70590490832553d23ef7609065ece46580d0c86bede
MD5 2a53168892a2fb44cf24591000aab38f
BLAKE2b-256 b12c95a773a8ecdc6db652fb4d992bac771fb9aa6dee75ead9d70f7c1279c1ca

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page