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 /opt/pontem/<service_name>/var/log/. The agent picks it up.
Already using stdlib
logging? Passstdlib_logging=Truetoinitand your existinglogging.getLogger(...).info(...)calls produce Pontem records — no call-site changes. See Use with stdlibloggingbelow.
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/debugrecords 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 /opt/pontem/config/config.json (or $PONTEM_CONFIG_DIR).
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
Lookup key is f"{namespace}:{key}". default is returned when the key is absent.
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 | /opt/pontem/<service_name>/var/log |
PONTEM_EMIT_TARGET |
"file" or "stdout" |
"file" |
PONTEM_CONFIG_DIR |
Directory containing config.json |
/opt/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 /opt/pontem/<service_name>/var/log/.
pontem.config(...) always returns default. Confirm the agent has written config.json: cat /opt/pontem/config/config.json. Verify namespace and key match what the agent provides (full key is "{namespace}:{key}"). 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pontem-0.2.1.tar.gz.
File metadata
- Download URL: pontem-0.2.1.tar.gz
- Upload date:
- Size: 23.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
28305d38f3392c9786c48026af74a250c75447004369860695212480cdd17912
|
|
| MD5 |
5e2877fedc38a3dd79323e765bc96e32
|
|
| BLAKE2b-256 |
40e78e3ae24375411deed39ebd1e060c9c43e60ef866afa72388e0102a0e3305
|
File details
Details for the file pontem-0.2.1-py3-none-any.whl.
File metadata
- Download URL: pontem-0.2.1-py3-none-any.whl
- Upload date:
- Size: 23.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
62e7e44cada65d025ec382af1de856f986db1c32ac70cc287cb5ebb94f9b0555
|
|
| MD5 |
92a172625e79ea3fb5afe58b550631cf
|
|
| BLAKE2b-256 |
57c80754498828ba874be4a6d2e978f701bec68dab8bdb23115e3b46b79ac893
|