brawny 0.1.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- brawny/__init__.py +106 -0
- brawny/_context.py +232 -0
- brawny/_rpc/__init__.py +38 -0
- brawny/_rpc/broadcast.py +172 -0
- brawny/_rpc/clients.py +98 -0
- brawny/_rpc/context.py +49 -0
- brawny/_rpc/errors.py +252 -0
- brawny/_rpc/gas.py +158 -0
- brawny/_rpc/manager.py +982 -0
- brawny/_rpc/selector.py +156 -0
- brawny/accounts.py +534 -0
- brawny/alerts/__init__.py +132 -0
- brawny/alerts/abi_resolver.py +530 -0
- brawny/alerts/base.py +152 -0
- brawny/alerts/context.py +271 -0
- brawny/alerts/contracts.py +635 -0
- brawny/alerts/encoded_call.py +201 -0
- brawny/alerts/errors.py +267 -0
- brawny/alerts/events.py +680 -0
- brawny/alerts/function_caller.py +364 -0
- brawny/alerts/health.py +185 -0
- brawny/alerts/routing.py +118 -0
- brawny/alerts/send.py +364 -0
- brawny/api.py +660 -0
- brawny/chain.py +93 -0
- brawny/cli/__init__.py +16 -0
- brawny/cli/app.py +17 -0
- brawny/cli/bootstrap.py +37 -0
- brawny/cli/commands/__init__.py +41 -0
- brawny/cli/commands/abi.py +93 -0
- brawny/cli/commands/accounts.py +632 -0
- brawny/cli/commands/console.py +495 -0
- brawny/cli/commands/contract.py +139 -0
- brawny/cli/commands/health.py +112 -0
- brawny/cli/commands/init_project.py +86 -0
- brawny/cli/commands/intents.py +130 -0
- brawny/cli/commands/job_dev.py +254 -0
- brawny/cli/commands/jobs.py +308 -0
- brawny/cli/commands/logs.py +87 -0
- brawny/cli/commands/maintenance.py +182 -0
- brawny/cli/commands/migrate.py +51 -0
- brawny/cli/commands/networks.py +253 -0
- brawny/cli/commands/run.py +249 -0
- brawny/cli/commands/script.py +209 -0
- brawny/cli/commands/signer.py +248 -0
- brawny/cli/helpers.py +265 -0
- brawny/cli_templates.py +1445 -0
- brawny/config/__init__.py +74 -0
- brawny/config/models.py +404 -0
- brawny/config/parser.py +633 -0
- brawny/config/routing.py +55 -0
- brawny/config/validation.py +246 -0
- brawny/daemon/__init__.py +14 -0
- brawny/daemon/context.py +69 -0
- brawny/daemon/core.py +702 -0
- brawny/daemon/loops.py +327 -0
- brawny/db/__init__.py +78 -0
- brawny/db/base.py +986 -0
- brawny/db/base_new.py +165 -0
- brawny/db/circuit_breaker.py +97 -0
- brawny/db/global_cache.py +298 -0
- brawny/db/mappers.py +182 -0
- brawny/db/migrate.py +349 -0
- brawny/db/migrations/001_init.sql +186 -0
- brawny/db/migrations/002_add_included_block.sql +7 -0
- brawny/db/migrations/003_add_broadcast_at.sql +10 -0
- brawny/db/migrations/004_broadcast_binding.sql +20 -0
- brawny/db/migrations/005_add_retry_after.sql +9 -0
- brawny/db/migrations/006_add_retry_count_column.sql +11 -0
- brawny/db/migrations/007_add_gap_tracking.sql +18 -0
- brawny/db/migrations/008_add_transactions.sql +72 -0
- brawny/db/migrations/009_add_intent_metadata.sql +5 -0
- brawny/db/migrations/010_add_nonce_gap_index.sql +9 -0
- brawny/db/migrations/011_add_job_logs.sql +24 -0
- brawny/db/migrations/012_add_claimed_by.sql +5 -0
- brawny/db/ops/__init__.py +29 -0
- brawny/db/ops/attempts.py +108 -0
- brawny/db/ops/blocks.py +83 -0
- brawny/db/ops/cache.py +93 -0
- brawny/db/ops/intents.py +296 -0
- brawny/db/ops/jobs.py +110 -0
- brawny/db/ops/logs.py +97 -0
- brawny/db/ops/nonces.py +322 -0
- brawny/db/postgres.py +2535 -0
- brawny/db/postgres_new.py +196 -0
- brawny/db/queries.py +584 -0
- brawny/db/sqlite.py +2733 -0
- brawny/db/sqlite_new.py +191 -0
- brawny/history.py +126 -0
- brawny/interfaces.py +136 -0
- brawny/invariants.py +155 -0
- brawny/jobs/__init__.py +26 -0
- brawny/jobs/base.py +287 -0
- brawny/jobs/discovery.py +233 -0
- brawny/jobs/job_validation.py +111 -0
- brawny/jobs/kv.py +125 -0
- brawny/jobs/registry.py +283 -0
- brawny/keystore.py +484 -0
- brawny/lifecycle.py +551 -0
- brawny/logging.py +290 -0
- brawny/metrics.py +594 -0
- brawny/model/__init__.py +53 -0
- brawny/model/contexts.py +319 -0
- brawny/model/enums.py +70 -0
- brawny/model/errors.py +194 -0
- brawny/model/events.py +93 -0
- brawny/model/startup.py +20 -0
- brawny/model/types.py +483 -0
- brawny/networks/__init__.py +96 -0
- brawny/networks/config.py +269 -0
- brawny/networks/manager.py +423 -0
- brawny/obs/__init__.py +67 -0
- brawny/obs/emit.py +158 -0
- brawny/obs/health.py +175 -0
- brawny/obs/heartbeat.py +133 -0
- brawny/reconciliation.py +108 -0
- brawny/scheduler/__init__.py +19 -0
- brawny/scheduler/poller.py +472 -0
- brawny/scheduler/reorg.py +632 -0
- brawny/scheduler/runner.py +708 -0
- brawny/scheduler/shutdown.py +371 -0
- brawny/script_tx.py +297 -0
- brawny/scripting.py +251 -0
- brawny/startup.py +76 -0
- brawny/telegram.py +393 -0
- brawny/testing.py +108 -0
- brawny/tx/__init__.py +41 -0
- brawny/tx/executor.py +1071 -0
- brawny/tx/fees.py +50 -0
- brawny/tx/intent.py +423 -0
- brawny/tx/monitor.py +628 -0
- brawny/tx/nonce.py +498 -0
- brawny/tx/replacement.py +456 -0
- brawny/tx/utils.py +26 -0
- brawny/utils.py +205 -0
- brawny/validation.py +69 -0
- brawny-0.1.13.dist-info/METADATA +156 -0
- brawny-0.1.13.dist-info/RECORD +141 -0
- brawny-0.1.13.dist-info/WHEEL +5 -0
- brawny-0.1.13.dist-info/entry_points.txt +2 -0
- brawny-0.1.13.dist-info/top_level.txt +1 -0
brawny/logging.py
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
"""Structured logging for brawny.
|
|
2
|
+
|
|
3
|
+
Provides JSON-formatted logs with correlation IDs for request tracing.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import re
|
|
10
|
+
import sys
|
|
11
|
+
import uuid
|
|
12
|
+
from contextvars import ContextVar
|
|
13
|
+
from datetime import datetime, timezone
|
|
14
|
+
from typing import Any, Literal
|
|
15
|
+
|
|
16
|
+
import structlog
|
|
17
|
+
|
|
18
|
+
from brawny.model.enums import LogFormat
|
|
19
|
+
|
|
20
|
+
# Module-level state for mode switching
|
|
21
|
+
_runtime_log_level: str = "INFO"
|
|
22
|
+
_runtime_log_format: LogFormat = LogFormat.JSON
|
|
23
|
+
_runtime_chain_id: int | None = None
|
|
24
|
+
|
|
25
|
+
# Context variable for correlation ID propagation
|
|
26
|
+
_correlation_id: ContextVar[str | None] = ContextVar("correlation_id", default=None)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_correlation_id() -> str:
|
|
30
|
+
"""Get the current correlation ID or generate a new one."""
|
|
31
|
+
cid = _correlation_id.get()
|
|
32
|
+
if cid is None:
|
|
33
|
+
cid = generate_correlation_id()
|
|
34
|
+
_correlation_id.set(cid)
|
|
35
|
+
return cid
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def set_correlation_id(correlation_id: str) -> None:
|
|
39
|
+
"""Set the correlation ID for the current context."""
|
|
40
|
+
_correlation_id.set(correlation_id)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def generate_correlation_id() -> str:
|
|
44
|
+
"""Generate a new correlation ID."""
|
|
45
|
+
return f"req_{uuid.uuid4().hex[:12]}"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def add_correlation_id(
|
|
49
|
+
logger: logging.Logger, method_name: str, event_dict: dict[str, Any]
|
|
50
|
+
) -> dict[str, Any]:
|
|
51
|
+
"""Processor to add correlation_id to log entries."""
|
|
52
|
+
event_dict["correlation_id"] = get_correlation_id()
|
|
53
|
+
return event_dict
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def add_timestamp(
|
|
57
|
+
logger: logging.Logger, method_name: str, event_dict: dict[str, Any]
|
|
58
|
+
) -> dict[str, Any]:
|
|
59
|
+
"""Processor to add ISO8601 timestamp to log entries."""
|
|
60
|
+
event_dict["timestamp"] = datetime.now(timezone.utc).isoformat()
|
|
61
|
+
return event_dict
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def rename_event_to_event_key(
|
|
65
|
+
logger: logging.Logger, method_name: str, event_dict: dict[str, Any]
|
|
66
|
+
) -> dict[str, Any]:
|
|
67
|
+
"""Rename 'event' to match our log format."""
|
|
68
|
+
# structlog uses 'event' by default, we keep it
|
|
69
|
+
return event_dict
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def add_log_level(
|
|
73
|
+
logger: logging.Logger, method_name: str, event_dict: dict[str, Any]
|
|
74
|
+
) -> dict[str, Any]:
|
|
75
|
+
"""Add uppercase log level."""
|
|
76
|
+
event_dict["level"] = method_name.upper()
|
|
77
|
+
return event_dict
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
_URL_CRED_RE = re.compile(r"(https?://)([^/@\s]+):([^/@\s]+)@")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _redact_url_creds(value: str) -> str:
|
|
84
|
+
return _URL_CRED_RE.sub(r"\1***@", value)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _redact_value(value: Any) -> Any:
|
|
88
|
+
if isinstance(value, str):
|
|
89
|
+
return _redact_url_creds(value)
|
|
90
|
+
if isinstance(value, dict):
|
|
91
|
+
redacted: dict[str, Any] = {}
|
|
92
|
+
for key, item in value.items():
|
|
93
|
+
key_lower = str(key).lower()
|
|
94
|
+
if key_lower in {"authorization", "proxy-authorization"}:
|
|
95
|
+
redacted[key] = "***"
|
|
96
|
+
else:
|
|
97
|
+
redacted[key] = _redact_value(item)
|
|
98
|
+
return redacted
|
|
99
|
+
if isinstance(value, (list, tuple)):
|
|
100
|
+
return type(value)(_redact_value(item) for item in value)
|
|
101
|
+
return value
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def redact_sensitive(
|
|
105
|
+
logger: logging.Logger, method_name: str, event_dict: dict[str, Any]
|
|
106
|
+
) -> dict[str, Any]:
|
|
107
|
+
"""Redact credentials or auth tokens from log fields."""
|
|
108
|
+
return {key: _redact_value(value) for key, value in event_dict.items()}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def setup_logging(
|
|
112
|
+
log_level: str = "INFO",
|
|
113
|
+
log_format: LogFormat = LogFormat.JSON,
|
|
114
|
+
chain_id: int | None = None,
|
|
115
|
+
mode: Literal["startup", "runtime"] = "runtime",
|
|
116
|
+
) -> None:
|
|
117
|
+
"""Configure structured logging for the application.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
log_level: Log level (DEBUG, INFO, WARNING, ERROR)
|
|
121
|
+
log_format: Output format (json or text)
|
|
122
|
+
chain_id: Chain ID to include in all log entries
|
|
123
|
+
mode: "startup" uses human-readable ConsoleRenderer with WARNING level,
|
|
124
|
+
"runtime" uses configured format and level
|
|
125
|
+
"""
|
|
126
|
+
global _runtime_log_level, _runtime_log_format, _runtime_chain_id
|
|
127
|
+
|
|
128
|
+
# Store runtime config for later switch
|
|
129
|
+
_runtime_log_level = log_level
|
|
130
|
+
_runtime_log_format = log_format
|
|
131
|
+
_runtime_chain_id = chain_id
|
|
132
|
+
|
|
133
|
+
# Startup mode: only show warnings/errors, human-readable format
|
|
134
|
+
effective_level = "WARNING" if mode == "startup" else log_level
|
|
135
|
+
|
|
136
|
+
# Set up stdlib logging
|
|
137
|
+
logging.basicConfig(
|
|
138
|
+
format="%(message)s",
|
|
139
|
+
stream=sys.stdout,
|
|
140
|
+
level=getattr(logging, effective_level.upper()),
|
|
141
|
+
force=True, # Allow reconfiguration
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Suppress noisy HTTP client logs (they can leak tokens in URLs)
|
|
145
|
+
logging.getLogger("httpx").setLevel(logging.WARNING)
|
|
146
|
+
logging.getLogger("httpcore").setLevel(logging.WARNING)
|
|
147
|
+
|
|
148
|
+
# Common processors
|
|
149
|
+
processors: list[structlog.types.Processor] = [
|
|
150
|
+
structlog.stdlib.filter_by_level,
|
|
151
|
+
add_timestamp,
|
|
152
|
+
add_log_level,
|
|
153
|
+
add_correlation_id,
|
|
154
|
+
structlog.stdlib.add_logger_name,
|
|
155
|
+
structlog.stdlib.PositionalArgumentsFormatter(),
|
|
156
|
+
structlog.processors.format_exc_info,
|
|
157
|
+
redact_sensitive,
|
|
158
|
+
structlog.processors.StackInfoRenderer(),
|
|
159
|
+
structlog.processors.UnicodeDecoder(),
|
|
160
|
+
]
|
|
161
|
+
|
|
162
|
+
# Add chain_id to all log entries if provided
|
|
163
|
+
if chain_id is not None:
|
|
164
|
+
|
|
165
|
+
def add_chain_id(
|
|
166
|
+
logger: logging.Logger, method_name: str, event_dict: dict[str, Any]
|
|
167
|
+
) -> dict[str, Any]:
|
|
168
|
+
event_dict.setdefault("chain_id", chain_id)
|
|
169
|
+
return event_dict
|
|
170
|
+
|
|
171
|
+
processors.insert(3, add_chain_id)
|
|
172
|
+
|
|
173
|
+
# Choose renderer based on mode
|
|
174
|
+
if mode == "startup":
|
|
175
|
+
# Human-friendly with colors for startup warnings/errors
|
|
176
|
+
processors.append(structlog.dev.ConsoleRenderer(colors=True))
|
|
177
|
+
elif log_format == LogFormat.JSON:
|
|
178
|
+
processors.append(structlog.processors.JSONRenderer())
|
|
179
|
+
else:
|
|
180
|
+
processors.append(structlog.dev.ConsoleRenderer(colors=True))
|
|
181
|
+
|
|
182
|
+
structlog.configure(
|
|
183
|
+
processors=processors,
|
|
184
|
+
wrapper_class=structlog.stdlib.BoundLogger,
|
|
185
|
+
context_class=dict,
|
|
186
|
+
logger_factory=structlog.stdlib.LoggerFactory(),
|
|
187
|
+
cache_logger_on_first_use=False, # Allow reconfiguration
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def set_runtime_logging() -> None:
|
|
192
|
+
"""Switch from startup mode to runtime mode.
|
|
193
|
+
|
|
194
|
+
Call after "--- Starting brawny ---" to enable full structured logging.
|
|
195
|
+
"""
|
|
196
|
+
setup_logging(_runtime_log_level, _runtime_log_format, _runtime_chain_id, mode="runtime")
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def get_logger(name: str | None = None, **initial_context: Any) -> structlog.stdlib.BoundLogger:
|
|
200
|
+
"""Get a structured logger instance.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
name: Logger name (usually module name)
|
|
204
|
+
**initial_context: Initial context to bind to the logger
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
Bound logger instance
|
|
208
|
+
"""
|
|
209
|
+
logger = structlog.get_logger(name)
|
|
210
|
+
if initial_context:
|
|
211
|
+
logger = logger.bind(**initial_context)
|
|
212
|
+
return logger
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class LogContext:
|
|
216
|
+
"""Context manager for scoped logging context."""
|
|
217
|
+
|
|
218
|
+
def __init__(self, **context: Any) -> None:
|
|
219
|
+
self._context = context
|
|
220
|
+
self._token: Any = None
|
|
221
|
+
|
|
222
|
+
def __enter__(self) -> LogContext:
|
|
223
|
+
# Generate correlation ID if not present
|
|
224
|
+
if "correlation_id" not in self._context:
|
|
225
|
+
cid = generate_correlation_id()
|
|
226
|
+
self._context["correlation_id"] = cid
|
|
227
|
+
set_correlation_id(cid)
|
|
228
|
+
return self
|
|
229
|
+
|
|
230
|
+
def __exit__(self, *args: Any) -> None:
|
|
231
|
+
pass
|
|
232
|
+
|
|
233
|
+
@property
|
|
234
|
+
def correlation_id(self) -> str:
|
|
235
|
+
"""Get the correlation ID for this context."""
|
|
236
|
+
return self._context.get("correlation_id", get_correlation_id())
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
# Pre-defined log event names per SPEC §11
|
|
240
|
+
class LogEvents:
|
|
241
|
+
"""Standard log event names."""
|
|
242
|
+
|
|
243
|
+
# Block processing
|
|
244
|
+
BLOCK_INGEST_START = "block.ingest.start"
|
|
245
|
+
BLOCK_INGEST_DONE = "block.ingest.done"
|
|
246
|
+
BLOCK_REORG_DETECTED = "block.reorg.detected"
|
|
247
|
+
BLOCK_REORG_REWIND = "block.reorg.rewind"
|
|
248
|
+
BLOCK_REORG_DEEP = "block.reorg.deep"
|
|
249
|
+
|
|
250
|
+
# Job execution
|
|
251
|
+
JOB_CHECK_START = "job.check.start"
|
|
252
|
+
JOB_CHECK_SKIP = "job.check.skip"
|
|
253
|
+
JOB_CHECK_TRIGGERED = "job.check.triggered"
|
|
254
|
+
JOB_CHECK_TIMEOUT = "job.check.timeout"
|
|
255
|
+
|
|
256
|
+
# Intent lifecycle
|
|
257
|
+
INTENT_CREATE = "intent.create"
|
|
258
|
+
INTENT_DEDUPE = "intent.dedupe"
|
|
259
|
+
INTENT_CLAIM = "intent.claim"
|
|
260
|
+
INTENT_STATUS = "intent.status"
|
|
261
|
+
INTENT_REORG = "intent.reorg"
|
|
262
|
+
|
|
263
|
+
# Nonce management
|
|
264
|
+
NONCE_RESERVE = "nonce.reserve"
|
|
265
|
+
NONCE_RECONCILE = "nonce.reconcile"
|
|
266
|
+
NONCE_ORPHANED = "nonce.orphaned"
|
|
267
|
+
|
|
268
|
+
# Transaction lifecycle
|
|
269
|
+
TX_SIGN = "tx.sign"
|
|
270
|
+
TX_BROADCAST = "tx.broadcast"
|
|
271
|
+
TX_PENDING = "tx.pending"
|
|
272
|
+
TX_CONFIRMED = "tx.confirmed"
|
|
273
|
+
TX_FAILED = "tx.failed"
|
|
274
|
+
TX_REPLACED = "tx.replaced"
|
|
275
|
+
TX_ABANDONED = "tx.abandoned"
|
|
276
|
+
|
|
277
|
+
# Alerts
|
|
278
|
+
ALERT_SEND = "alert.send"
|
|
279
|
+
ALERT_ERROR = "alert.error"
|
|
280
|
+
|
|
281
|
+
# RPC
|
|
282
|
+
RPC_REQUEST = "rpc.request"
|
|
283
|
+
RPC_ERROR = "rpc.error"
|
|
284
|
+
RPC_ALL_ENDPOINTS_FAILED = "rpc.all_endpoints_failed"
|
|
285
|
+
RPC_CIRCUIT_BREAKER_OPEN = "rpc.circuit_breaker_open"
|
|
286
|
+
RPC_CIRCUIT_BREAKER_CLOSED = "rpc.circuit_breaker_closed"
|
|
287
|
+
|
|
288
|
+
# Shutdown
|
|
289
|
+
SHUTDOWN_INITIATED = "shutdown.initiated"
|
|
290
|
+
SHUTDOWN_COMPLETE = "shutdown.complete"
|