Skip to content

Commit 0e5d876

Browse files
feat: option to output json logs to console and env option to switch between json and pretty for console logging (#63)
1 parent fa6dc8e commit 0e5d876

3 files changed

Lines changed: 45 additions & 13 deletions

File tree

agents/standard_agent.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,14 @@
1313
from agents.llm.base_llm import BaseLLM
1414
from agents.tools.base import JustInTimeToolingBase
1515
from agents.goal_preprocessor.base import BaseGoalPreprocessor
16+
from agents.prompts import load_prompts
1617

1718
from uuid import uuid4
19+
import time
1820
from enum import Enum
19-
from agents.prompts import load_prompts
21+
from utils.logger import get_logger
22+
23+
logger = get_logger(__name__)
2024

2125
_PROMPTS = load_prompts("agent", required_prompts=["summarize"])
2226

@@ -68,6 +72,7 @@ def state(self) -> AgentState:
6872
def solve(self, goal: str) -> ReasoningResult:
6973
"""Solves a goal synchronously (library-style API)."""
7074
run_id = uuid4().hex
75+
start_time = time.perf_counter()
7176

7277
if self.goal_preprocessor:
7378
revised_goal, intervention_message = self.goal_preprocessor.process(goal, self.memory.get("conversation_history"))
@@ -86,6 +91,19 @@ def solve(self, goal: str) -> ReasoningResult:
8691
self.memory[f"result:{run_id}"] = result
8792
self.memory["conversation_history"].append({"goal": goal, "result": result.final_answer})
8893
self._state = AgentState.READY
94+
95+
duration_ms = int((time.perf_counter() - start_time) * 1000)
96+
logger.info(
97+
"final_result",
98+
run_id=run_id,
99+
success=result.success,
100+
iterations=result.iterations,
101+
tool_calls_count=len(result.tool_calls or []),
102+
duration_ms=duration_ms,
103+
goal=goal,
104+
final_answer_preview=((result.final_answer[:200] + "…") if (result.final_answer and len(result.final_answer) > 200) else result.final_answer),
105+
model=getattr(self.llm, "model", None),
106+
)
89107
return result
90108

91109
except Exception:

config.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,13 @@
44

55
"console": {
66
"enabled": true,
7-
"format": "%(asctime)s | %(levelname_coloured)s | %(name)s: %(message)s"
7+
"renderer": "pretty"
88
},
99

1010
"file": {
1111
"enabled": true,
1212
"level": "DEBUG",
1313
"path": "logs/standard_agent.log",
14-
"format": "%(asctime)s | %(levelname)s | %(name)s | %(message)s",
1514
"rotation": {
1615
"enabled": true,
1716
"max_bytes": 10485760,

utils/logger.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,23 @@ def _pre_chain() -> list:
6262
structlog.processors.TimeStamper(fmt="ISO", key=TIME_KEY),
6363
]
6464

65-
def _build_console_handler(level: int) -> logging.Handler:
66-
formatter = structlog.stdlib.ProcessorFormatter(
67-
foreign_pre_chain=_pre_chain(),
68-
processors=[
65+
def _build_console_handler(level: int, renderer: str) -> logging.Handler:
66+
"""Build a console handler with either pretty or JSON rendering."""
67+
68+
processors: list[Any]
69+
if renderer == "json":
70+
processors = [
71+
structlog.stdlib.ProcessorFormatter.remove_processors_meta,
72+
structlog.processors.EventRenamer(to="message"),
73+
structlog.processors.JSONRenderer(),
74+
]
75+
else: # pretty (default)
76+
processors = [
6977
structlog.stdlib.ProcessorFormatter.remove_processors_meta,
7078
structlog.dev.ConsoleRenderer(colors=_supports_colour(), timestamp_key=TIME_KEY),
71-
],
72-
)
79+
]
80+
81+
formatter = structlog.stdlib.ProcessorFormatter(foreign_pre_chain=_pre_chain(), processors=processors,)
7382
handler = logging.StreamHandler(sys.stdout)
7483
handler.setLevel(level)
7584
handler.setFormatter(formatter)
@@ -103,10 +112,13 @@ def _build_file_handler(file_cfg: Dict[str, Any]) -> logging.Handler:
103112

104113

105114
def init_logger(config_path: str | Path | None = None) -> None:
106-
"""Structured setup for hosted agents: JSON for files and non-TTY stdout.
115+
"""Structured logging setup.
116+
117+
Console renderer is configurable via config, with env override support when needed:
118+
- Config: logging.console.renderer = "json" | "pretty" (default: pretty)
119+
- Env (optional): LOG_CONSOLE_RENDERER=pretty|json (wins over config)
107120
108-
- Local TTY console: coloured ConsoleRenderer for readability
109-
- Non-TTY stdout and files: JSON for ingestion by log frameworks
121+
Files always use JSON with optional rotation.
110122
"""
111123
cfg = _read_cfg(config_path)
112124

@@ -125,7 +137,10 @@ def init_logger(config_path: str | Path | None = None) -> None:
125137
# Console handler enabled by default
126138
console_enabled = cfg.get("console", {}).get("enabled", True)
127139
if console_enabled:
128-
handlers.append(_build_console_handler(root_level))
140+
renderer = os.getenv("LOG_CONSOLE_RENDERER") or cfg.get("console", {}).get("renderer", "pretty").strip().lower()
141+
if renderer not in {"json", "pretty"}:
142+
raise ValueError(f"Invalid console logging renderer option: {renderer!r}. Allowed: json, pretty")
143+
handlers.append(_build_console_handler(root_level, renderer))
129144

130145
# File handler (JSON) if enabled
131146
file_cfg = cfg.get("file", {})

0 commit comments

Comments
 (0)