Agnostic observability (spans + custom metrics) for RPA executions
Project description
Observallize
Instrumentação agnóstica para execuções (principalmente RPAs) baseada em spans (eventos), com:
- medição de tempo e árvore pai/filho (
parent_span_id) - memória RSS (início/fim/delta e pico aproximado por amostragem)
- métricas customizadas durante a execução (
obs.add_metric(...)) - captura de exceções por span (
exception_type/message/stacktrace)
Este README (raiz) é propositalmente direto e focado em uso. A documentação detalhada por módulo fica em observallize/README.md (link será apontado no GitHub).
Instalação
pip install observallize
Organização de imports (projetos com muitos módulos)
Você pode usar a instância pronta obs, mas o padrão recomendado é criar sua própria instância de Observability por execução/processo, para ter um correlation_id estável do início ao fim do .py (mais simples do que depender só de contexto).
Para respeitar SOLID e evitar criar instâncias em vários lugares, um padrão comum é criar/reexportar uma única instância de Observability a partir de um módulo do seu projeto (ex.: src/infra/observability.py) e importar esse ponto único em todos os Services/Controllers.
Exemplo completo (modelo Controller/Service + executor)
O exemplo abaixo segue o mesmo padrão do main.py.
- O executor define o
execution_iduma vez para o processo inteiro - Cada controller reutiliza esse
execution_idpara associar tudo ao mesmo “job” - O executor envolve o
handlercomuse_exportere persiste nofinally - O
handleré observado para registrar “métricas macro” do controller
from pathlib import Path
from time import sleep
from typing import Any
from observallize.retry import retry
from observallize import Observability
obs = Observability(Path("events.json"))
class Repository:
@staticmethod
def insert_batch(events: list[dict[str, Any]]):
print(f"Inserindo batch no DB: {len(events)} eventos")
class Service:
@staticmethod
@obs.observe(name="login")
def login(user: str, password: str):
sleep(2)
obs.add_metric("auth_provider", "site_x")
obs.add_metric("attempt", 1)
return "A", 30, {"result": "...", "status": True}
@staticmethod
@obs.observe(name="baixar_relatorio")
def baixar_relatorio(dia: int):
return {"dia": dia, "rows": 123}
@staticmethod
@obs.observe(name="processamento_do_pedido")
def processar_pedido(algo: str, outro: str, alguma_coisa: int):
obs.add_metric("parametro", outro)
return True
class DataService:
@staticmethod
@obs.observe(name="tratando_dados")
def treat_records(): ...
class Controller:
def __init__(self, service: Service, data: DataService, db: Repository):
self.correlation_id = obs.correlation_id
self.export = obs.create_exporter(also_queue=False)
self.service = service
self.data = data
self.db = db
@obs.observe(name="controller.handler", capture_io=False, capture_result=False)
def handler(self):
obs.add_metric("controller_name", self.__class__.__name__)
obs.add_metric("rpa_name", "meu_rpa")
obs.add_metric("batch_id", "2026-04-11_001")
obs.add_metric("ambiente", "prod")
self.service.login("usuario", "senha")
self.service.baixar_relatorio(15)
self.service.processar_pedido("algo", "outro", 100)
@retry(3)
def executor(controllers, execution_id: str | None = None):
obs.set_execution_context(execution_id)
obs.activate()
for controller_cls, params in controllers:
controller = controller_cls(**params)
with obs.use_exporter(controller.export):
try:
controller.handler()
finally:
events = obs.span_store.get(controller.correlation_id)
rows = obs.to_rows(events, include_io=False, include_result=False)
controller.db.insert_batch(rows)
obs.span_store.clear(controller.correlation_id)
def run():
controllers = [
(Controller, {"service": Service, "data": DataService, "db": Repository}),
]
executor(controllers)
Persistência (modos)
- Store + rows (normalizado):
create_exporter(also_queue=False)→events = obs.span_store.get(execution_id)→rows = obs.to_rows(events)→insert_batch(rows) - Fila + flush (eventos crus):
create_exporter(also_queue=True)→obs.flush(insert_batch)
Inspeção (durante desenvolvimento)
events = obs.span_store.get(obs.get_execution_id())
print(obs.to_json(events, include_io=False, include_result=False, datetime_format="%d/%m/%Y %H:%M:%S"))
Boas práticas (para não perder spans)
- Envolva a chamada da função decorada com
with obs.use_exporter(...)(ex.:controller.handler()). - Evite setar exporter “por dentro” do handler decorado.
- Deixe o
try/exceptno executor e faça a persistência nofinally.
Erros: o que acontece com try/finally
- Se
controller.handler()levantar uma exceção, ela sobe normalmente (não é “engolida”). - O bloco
finallysempre roda antes da exceção continuar subindo, então você persiste os eventos e depois a execução aborta (a menos que você capture e trate). - Se você estiver usando
@retry(...), a exceção dispara nova tentativa até esgotarattempts; na última falha, a exceção é propagada.
Documentação detalhada
- Detalhes por módulo: README
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 observallize-0.1.1.tar.gz.
File metadata
- Download URL: observallize-0.1.1.tar.gz
- Upload date:
- Size: 16.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fd4f345a5d7bd0c9943bbc940608d7ffa0802214be45261339e303903a08a2b6
|
|
| MD5 |
c101260cd85f8bcd6d8d05f03dece42c
|
|
| BLAKE2b-256 |
0a2b05edeb3d10c73e683f9f2fd0dc08c58d01b5bc8d4e1fe6256b988f40438f
|
File details
Details for the file observallize-0.1.1-py3-none-any.whl.
File metadata
- Download URL: observallize-0.1.1-py3-none-any.whl
- Upload date:
- Size: 19.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e17692f67f77ac9b8fd4bc1e2ccdff50966395473dca0fdfe26bfe704131c410
|
|
| MD5 |
b6d679735ebc8ff9135df832507e4cd6
|
|
| BLAKE2b-256 |
c03605d27fbb66e1cd513ccafc0910c9d0e9ded495d86f6812704588eaf773fd
|