33from __future__ import annotations
44
55import os
6+ import re
67import traceback
78from dataclasses import replace
89from datetime import datetime , timezone
4243app = Flask (__name__ )
4344register_health_endpoint (app ) # GET /health /healthz
4445
46+ _REDACTED = "<redacted>"
47+ _TELEGRAM_BOT_PATH_RE = re .compile (r"(?i)(/bot)([^/\s]+)" )
48+ _SENSITIVE_QUERY_RE = re .compile (
49+ r"(?i)([?&](?:access[_-]?token|api[_-]?key|auth[_-]?token|key|password|secret|signature|token)=)([^&\s]+)"
50+ )
51+ _AUTH_HEADER_RE = re .compile (r"(?i)\b(Bearer|Basic)\s+([A-Za-z0-9._~+/=-]{8,})" )
52+ _ASSIGNMENT_RE = re .compile (
53+ r"(?i)\b(api[_-]?key|auth[_-]?token|credential|password|private[_-]?key|secret|token)\s*[:=]\s*([\"']?)([^\"'\s,;]{8,})([\"']?)"
54+ )
55+
4556
4657def get_project_id () -> str | None :
4758 return os .getenv ("GOOGLE_CLOUD_PROJECT" )
@@ -59,6 +70,14 @@ def _split_env_list(value: str | None) -> tuple[str, ...]:
5970 )
6071
6172
73+ def redact_sensitive_text (value : object ) -> str :
74+ text = str (value )
75+ text = _TELEGRAM_BOT_PATH_RE .sub (r"\1" + _REDACTED , text )
76+ text = _SENSITIVE_QUERY_RE .sub (r"\1" + _REDACTED , text )
77+ text = _AUTH_HEADER_RE .sub (r"\1 " + _REDACTED , text )
78+ return _ASSIGNMENT_RE .sub (lambda match : f"{ match .group (1 )} ={ _REDACTED } " , text )
79+
80+
6281def _get_telegram_token () -> str :
6382 try :
6483 from quant_platform_kit .cloud import get_secret_store
@@ -86,7 +105,7 @@ def _telegram_notification_targets() -> tuple[tuple[str, str], ...]:
86105
87106
88107def _runtime_error_notification_message (exc : Exception ) -> str :
89- error_text = f" { type (exc ). __name__ } : { exc } "
108+ error_text = _safe_exception_text (exc , include_type = True )
90109 if len (error_text ) > 1200 :
91110 error_text = error_text [:1197 ] + "..."
92111 is_health_check = request .path == "/probe"
@@ -131,13 +150,22 @@ def _notify_runtime_error(exc: Exception) -> bool:
131150 try :
132151 build_sender (token , chat_id )(message )
133152 except Exception as send_exc : # pragma: no cover - build_sender normally handles this.
134- print (f"Firstrade runtime error Telegram send failed: { send_exc } " , flush = True )
153+ print (
154+ f"Firstrade runtime error Telegram send failed: { redact_sensitive_text (send_exc )} " ,
155+ flush = True ,
156+ )
135157 return attempted
136158
137159
160+ def _safe_exception_text (exc : Exception , * , include_type : bool = False ) -> str :
161+ text = redact_sensitive_text (exc )
162+ return f"{ type (exc ).__name__ } : { text } " if include_type else text
163+
164+
138165def _handle_strategy_run_exception (exc : Exception ) -> bool :
139- print (f"Firstrade strategy run failed: { type (exc ).__name__ } : { exc } " , flush = True )
140- traceback .print_exc ()
166+ print (f"Firstrade strategy run failed: { _safe_exception_text (exc , include_type = True )} " , flush = True )
167+ for line in traceback .format_exception (type (exc ), exc , exc .__traceback__ ):
168+ print (redact_sensitive_text (line .rstrip ()), flush = True )
141169 return _notify_runtime_error (exc )
142170
143171
@@ -412,7 +440,7 @@ def smoke():
412440 }
413441 )
414442 except FirstradePlatformError as exc :
415- return jsonify ({"ok" : False , "error" : str (exc )}), 500
443+ return jsonify ({"ok" : False , "error" : _safe_exception_text (exc )}), 500
416444
417445
418446def session_check ():
@@ -437,7 +465,7 @@ def session_check():
437465 jsonify (
438466 {
439467 "ok" : False ,
440- "error" : str (exc ),
468+ "error" : _safe_exception_text (exc ),
441469 "runtime_error_notification_attempted" : notification_attempted ,
442470 }
443471 ),
@@ -449,7 +477,7 @@ def session_check():
449477 jsonify (
450478 {
451479 "ok" : False ,
452- "error" : f" { type (exc ). __name__ } : { exc } " ,
480+ "error" : _safe_exception_text (exc , include_type = True ) ,
453481 "runtime_error_notification_attempted" : notification_attempted ,
454482 }
455483 ),
@@ -487,7 +515,7 @@ def run_strategy():
487515 jsonify (
488516 {
489517 "ok" : False ,
490- "error" : str (exc ),
518+ "error" : _safe_exception_text (exc ),
491519 "runtime_error_notification_attempted" : notification_attempted ,
492520 }
493521 ),
@@ -499,7 +527,7 @@ def run_strategy():
499527 jsonify (
500528 {
501529 "ok" : False ,
502- "error" : f" { type (exc ). __name__ } : { exc } " ,
530+ "error" : _safe_exception_text (exc , include_type = True ) ,
503531 "runtime_error_notification_attempted" : notification_attempted ,
504532 }
505533 ),
@@ -527,7 +555,7 @@ def dry_run():
527555 jsonify (
528556 {
529557 "ok" : False ,
530- "error" : str (exc ),
558+ "error" : _safe_exception_text (exc ),
531559 "runtime_error_notification_attempted" : notification_attempted ,
532560 }
533561 ),
@@ -539,7 +567,7 @@ def dry_run():
539567 jsonify (
540568 {
541569 "ok" : False ,
542- "error" : f" { type (exc ). __name__ } : { exc } " ,
570+ "error" : _safe_exception_text (exc , include_type = True ) ,
543571 "runtime_error_notification_attempted" : notification_attempted ,
544572 }
545573 ),
@@ -566,7 +594,7 @@ def monitor_dispatch():
566594 jsonify (
567595 {
568596 "ok" : False ,
569- "error" : f" { type (exc ). __name__ } : { exc } " ,
597+ "error" : _safe_exception_text (exc , include_type = True ) ,
570598 "runtime_error_notification_attempted" : notification_attempted ,
571599 }
572600 ),
0 commit comments