Skip to content

Commit aa447e2

Browse files
trevor-eclaude
andcommitted
feat(devserver): Tee console output to a local dev log file
The devserver now mirrors all honcho-routed console output to a log file (truncated each run) so it can be inspected after the fact — for example by AI agents reading the dev logs. ANSI color codes are stripped from the file copy while the interactive console keeps its coloring (the tee delegates isatty()). The path defaults to <repo>/.artifacts/dev.log and is configurable via the SENTRY_DEV_LOG_FILE environment variable. Both .artifacts/ and *.log are already gitignored. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 7bd2a68 commit aa447e2

3 files changed

Lines changed: 49 additions & 5 deletions

File tree

AGENTS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ That is all that is required to run `pytest`.
5151

5252
`devservices serve` starts the development server.
5353

54+
When the devserver is running, its full console output (all honcho-managed processes — `server`, `taskworker`, kafka consumers, webpack/watchers, etc.) is teed to `.artifacts/dev.log`, ANSI-stripped and gitignored. Agents can't see the devserver terminal, so `tail`/`grep` this file to inspect what's happening (startup, reloads, request logs, tracebacks). The file is truncated on each devserver process start (in-process granian reloads keep appending); override its path with `SENTRY_DEV_LOG_FILE`. Dev-only — this teeing lives in `sentry devserver` and is not used in production.
55+
5456
#### Linting
5557

5658
prek is the single entrypoint for all lint, format, and type-checking tools.

src/sentry/runner/commands/devserver.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import os
44
import threading
55
from collections.abc import MutableSequence, Sequence
6+
from pathlib import Path
67
from typing import NoReturn
78

89
import click
@@ -461,9 +462,20 @@ def devserver(
461462

462463
cwd = os.path.realpath(os.path.join(settings.PROJECT_ROOT, os.pardir, os.pardir))
463464

464-
from sentry.runner.formatting import get_honcho_printer
465+
# Tee console output to a log file (truncated each run) so it can be
466+
# inspected after the fact (e.g. by AI agents reading the dev logs).
467+
# Path is configurable; defaults to <repo>/.artifacts/dev.log.
468+
from sentry.runner.formatting import TeeStream, get_honcho_printer
465469

466-
honcho_printer = get_honcho_printer(prefix=prefix, pretty=pretty)
470+
log_path = Path(
471+
os.environ.get("SENTRY_DEV_LOG_FILE", os.path.join(cwd, ".artifacts", "dev.log"))
472+
)
473+
log_path.parent.mkdir(parents=True, exist_ok=True)
474+
log_file = open(log_path, "w", encoding="utf-8")
475+
476+
honcho_printer = get_honcho_printer(
477+
prefix=prefix, pretty=pretty, output=TeeStream(sys.stdout, log_file)
478+
)
467479

468480
manager = Manager(honcho_printer)
469481
for name, cmd in daemons:

src/sentry/runner/formatting.py

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,37 @@
11
from __future__ import annotations
22

33
import re
4-
from typing import TYPE_CHECKING
4+
import sys
5+
from typing import IO, TYPE_CHECKING, cast
56

67
if TYPE_CHECKING:
78
import honcho.printer
89

10+
_ANSI_RE = re.compile(r"\x1b\[[0-9;]*m")
11+
12+
13+
class TeeStream:
14+
"""Mirror writes to a console stream verbatim and a log file with ANSI stripped."""
15+
16+
def __init__(self, console: IO[str], log_file: IO[str]) -> None:
17+
self._console = console
18+
self._log_file = log_file
19+
20+
def write(self, s: str) -> int:
21+
self._console.write(s)
22+
self._log_file.write(_ANSI_RE.sub("", s))
23+
self._log_file.flush()
24+
return len(s)
25+
26+
def flush(self) -> None:
27+
self._console.flush()
28+
self._log_file.flush()
29+
30+
def isatty(self) -> bool:
31+
# Delegate so honcho keeps coloring the console; the file copy is stripped above.
32+
return self._console.isatty()
33+
34+
935
# Sentry colors taken from our design system. Might not look good on all
1036
# terminal themes tbh
1137
COLORS = {
@@ -75,7 +101,9 @@ def colorize_traceback(pattern: re.Match[str]) -> str:
75101
)
76102

77103

78-
def get_honcho_printer(*, prefix: bool, pretty: bool) -> honcho.printer.Printer:
104+
def get_honcho_printer(
105+
*, prefix: bool, pretty: bool, output: IO[str] | TeeStream | None = None
106+
) -> honcho.printer.Printer:
79107
import honcho.printer
80108

81109
class SentryPrinter(honcho.printer.Printer):
@@ -117,4 +145,6 @@ def write(self, message: honcho.printer.Message) -> None:
117145
for line in string.splitlines():
118146
self.output.write(f"{prefix}{line}\n")
119147

120-
return SentryPrinter(prefix=prefix)
148+
return SentryPrinter(
149+
prefix=prefix, output=cast("IO[str]", output) if output is not None else sys.stdout
150+
)

0 commit comments

Comments
 (0)