Observability¶
Cross-cutting utility namespaces: structured logging (log), the named cache
backends (cache), call detail records (cdr), custom Prometheus metrics
(metrics), and periodic / one-shot timers (timer).
log namespace¶
Mock logging namespace — captures log messages for test assertions.
Access captured messages via log.messages::
from siphon import log
log.info("hello")
assert ("info", "hello") in log.messages
messages
instance-attribute
¶
List of (level, message) tuples captured during the test.
cache namespace¶
Named cache backends (Redis + local LRU) from the cache: config list.
Mock cache namespace with an in-memory dict backend.
Pre-populate::
from siphon import cache
cache.set_data("cnam", {"msisdn_display:1234": "Sales"})
Then await cache.fetch("cnam", "msisdn_display:1234") returns "Sales".
fetch
async
¶
Fetch a value from a named cache.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Cache name (from |
required |
key
|
str
|
Cache key string. |
required |
Returns:
| Type | Description |
|---|---|
Optional[str]
|
Cached value or |
store
async
¶
Store a value in a named cache with optional TTL.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Cache name. |
required |
key
|
str
|
Cache key. |
required |
value
|
str
|
Value to store. |
required |
ttl
|
Optional[int]
|
Optional TTL in seconds. Mirrors the real |
None
|
Returns:
| Type | Description |
|---|---|
bool
|
|
list_push
async
¶
Append item to a list under key. Returns the new
length, or None if the cache name is unknown.
The mock stores lists as Python list[str] under the same
keyspace as scalars — fine for tests, but a real script would
not mix scalar and list values on the same key.
list_pop_all
async
¶
Atomically read and clear a list. Returns the items in FIFO order, empty list when the key was absent or the cache is unknown.
list_len
async
¶
Return the length of the list under key (0 for a
missing key), or None if the cache name is unknown.
list_len_sum
async
¶
Sum the lengths of every list whose key starts with
prefix. Returns 0 when nothing matches, None if the
cache name is unknown. Raises ValueError on an empty prefix
(which would scan the entire keyspace).
expire
async
¶
Mock TTL — records the call on self.expirations for
assertions and returns True when the key currently exists
in the cache. The mock does not actually time out entries.
set_data
¶
Create/replace a named cache with test data (test helper).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Cache name. |
required |
data
|
Optional[dict[str, str]]
|
Initial key-value pairs (default: empty dict). |
None
|
cdr namespace¶
Call detail record writing from scripts.
Mock cdr namespace — call detail record writing from scripts.
Usage::
from siphon import cdr
cdr.write(request, extra={"billing_id": "B-12345"}) # proxy handler
cdr.write(call, extra={"billing_id": "B-12345"}) # b2bua handler
cdr.enabled # True if CDR system is active
Test helper::
from siphon_sdk.mock_module import get_cdr
cdrs = get_cdr().records # list of written CDR dicts
write
¶
Write a CDR for the given request or B2BUA call.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
'Any'
|
The SIP |
required |
extra
|
'dict[str, str] | None'
|
Optional dict of extra fields to include in the CDR. |
None
|
Returns:
| Type | Description |
|---|---|
bool
|
True if the CDR was queued successfully. |
Raises:
| Type | Description |
|---|---|
TypeError
|
if |
Example::
from siphon import cdr
@proxy.on_request("INVITE")
def route(request):
cdr.write(request, extra={"billing_id": "B-12345"})
@b2bua.on_answer
def answered(call, reply):
cdr.write(call, extra={"billing_id": "B-12345"})
metrics namespace¶
Custom Prometheus counters, gauges, and histograms that appear on /metrics.
Mock metrics namespace — custom Prometheus metrics from scripts.
Usage::
from siphon import metrics
counter = metrics.counter("bgcf_calls_total", "Total calls",
labels=["direction"])
counter.labels(direction="outbound").inc()
gauge = metrics.gauge("bgcf_calls_active", "Active calls")
gauge.inc()
hist = metrics.histogram("bgcf_setup_seconds", "Setup time",
buckets=[0.1, 0.5, 1.0])
hist.observe(0.3)
Test helper::
from siphon_sdk.mock_module import get_metrics
m = get_metrics()
c = m.counter("test_total", "Test")
c.inc()
assert c._value == 1.0
counter
¶
Create a new counter metric.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Metric name (e.g. |
required |
help
|
str
|
Description string. |
required |
labels
|
'list[str] | None'
|
Optional list of label names. |
None
|
Returns:
| Type | Description |
|---|---|
MockCounter
|
A MockCounter handle. |
gauge
¶
Create a new gauge metric.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Metric name (e.g. |
required |
help
|
str
|
Description string. |
required |
labels
|
'list[str] | None'
|
Optional list of label names. |
None
|
Returns:
| Type | Description |
|---|---|
MockGauge
|
A MockGauge handle. |
histogram
¶
histogram(
name: str,
help: str,
labels: "list[str] | None" = None,
buckets: "list[float] | None" = None,
) -> MockHistogram
Create a new histogram metric.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Metric name (e.g. |
required |
help
|
str
|
Description string. |
required |
labels
|
'list[str] | None'
|
Optional list of label names. |
None
|
buckets
|
'list[float] | None'
|
Optional list of bucket boundaries. |
None
|
Returns:
| Type | Description |
|---|---|
MockHistogram
|
A MockHistogram handle. |
Counter¶
Mock Prometheus counter.
Usage::
from siphon import metrics
c = metrics.counter("my_total", "My counter")
c.inc()
c.inc(5)
With labels::
c = metrics.counter("my_total", "My counter", labels=["method"])
c.labels(method="INVITE").inc()
Gauge¶
Mock Prometheus gauge.
Usage::
from siphon import metrics
g = metrics.gauge("my_active", "Active things")
g.inc()
g.dec()
g.set(42)
Histogram¶
Mock Prometheus histogram.
Usage::
from siphon import metrics
h = metrics.histogram("my_duration_seconds", "Duration",
buckets=[0.1, 0.5, 1.0])
h.observe(0.3)
timer namespace¶
Periodic (@timer.every) and one-shot (timer.set) callbacks.
Mock timer namespace for periodic timer callbacks.
Timer handlers run on a Tokio interval in the Rust runtime. They receive no SIP request/call context but can use all other namespaces (registrar, cache, gateway, log, etc.).
Example::
from siphon import timer
@timer.every(seconds=30)
async def health_check():
for dest in gateway.list("carriers"):
if not dest.healthy:
log.warn(f"Gateway {dest.uri} is down")
@timer.every(seconds=300, name="stats_push", jitter=10)
def push_stats():
log.info("pushing stats")
scheduled
property
¶
Map of active one-shot timer keys → scheduled delay (ms).
every
¶
Register a periodic timer callback.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
seconds
|
int
|
Interval between invocations. |
required |
name
|
str | None
|
Optional name for logging (defaults to function name). |
None
|
jitter
|
int
|
Random jitter in seconds added to each interval (default 0). |
0
|
Returns:
| Type | Description |
|---|---|
Callable
|
Decorator that registers the function as a timer handler. |
Example::
@timer.every(seconds=60)
def cleanup():
presence.expire_stale()
set
¶
Schedule a one-shot callback under key to fire after delay_ms.
Setting the same key twice cancels the previous timer and reschedules.
In the mock, no tokio runtime fires the callback — tests call
:meth:fire with the key to invoke the handler manually.
cancel
¶
Cancel the one-shot timer registered under key. Returns
True if a timer was cancelled, False if no timer matched.
fire
¶
Test helper: fire the one-shot timer registered under key.
Raises KeyError if no timer matches. Pops the timer so a
subsequent fire for the same key raises.
TimerHandle¶
Returned by timer.set(...) — cancel a scheduled one-shot.
Mock of the TimerHandle returned by timer.set().