Skip to content

Commit 7d24730

Browse files
committed
feat: Spring Boot-style startup logging with runtime and server info
- starting_application now includes python version and PID - New runtime_environment log: OS, arch, CPUs, Python implementation - New server_started log: server type/version, host, port, workers, event loop, HTTP mode (like Spring Boot's "Netty started on port 8080") - application_started moved to LAST line (Spring Boot parity) - CLI passes server config to workers via env vars
1 parent 557fb70 commit 7d24730

2 files changed

Lines changed: 75 additions & 12 deletions

File tree

src/pyfly/cli/run.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,13 @@ def run_command(
9797
os.environ["_PYFLY_BANNER_PRINTED"] = "1"
9898
os.environ["_PYFLY_WORKERS"] = str(config.workers)
9999

100+
# Pass server configuration to workers so they can log server info
101+
os.environ["_PYFLY_SERVER_TYPE"] = config.type
102+
os.environ["_PYFLY_SERVER_HOST"] = host
103+
os.environ["_PYFLY_SERVER_PORT"] = str(port)
104+
os.environ["_PYFLY_EVENT_LOOP"] = config.event_loop
105+
os.environ["_PYFLY_HTTP"] = config.http
106+
100107
server_adapter.serve(app_path, config)
101108

102109

src/pyfly/core/application.py

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ def startup_time_seconds(self) -> float:
115115
async def startup(self) -> None:
116116
"""Start the application with full Spring Boot-style startup sequence."""
117117
import os
118+
import platform
118119

119120
start = time.perf_counter()
120121

@@ -139,11 +140,25 @@ async def startup(self) -> None:
139140
sys.stdout.flush()
140141

141142
if not suppress_logs:
142-
# Log startup info
143-
start_fields: dict[str, Any] = {"app": self._name, "version": self._version}
144-
if self._description:
145-
start_fields["description"] = self._description
146-
self._logger.info("starting_application", **start_fields)
143+
# "Starting X using Python 3.13.9 with PID 12345" (mirrors Spring Boot)
144+
py_version = platform.python_version()
145+
self._logger.info(
146+
"starting_application",
147+
app=self._name,
148+
version=self._version,
149+
python=py_version,
150+
pid=os.getpid(),
151+
)
152+
153+
# Runtime environment info
154+
self._logger.info(
155+
"runtime_environment",
156+
os=platform.system(),
157+
os_version=platform.release(),
158+
arch=platform.machine(),
159+
cpus=os.cpu_count() or 1,
160+
python_impl=platform.python_implementation(),
161+
)
147162

148163
profiles = self._context.environment.active_profiles
149164
if profiles:
@@ -175,14 +190,20 @@ async def startup(self) -> None:
175190
self._startup_time = time.perf_counter() - start
176191

177192
if not suppress_logs:
178-
# Log comprehensive startup summary
193+
# Log comprehensive startup summary (beans, wiring)
179194
self._log_startup_summary()
180195

181196
# Log routes and API documentation URLs
182197
self._log_routes_and_docs()
183198

199+
# Log server info — like Spring Boot's "Netty started on port 8080"
200+
self._log_server_info()
201+
202+
# Final "Started X in Y seconds" — LAST line (Spring Boot parity)
203+
self._log_started()
204+
184205
def _log_startup_summary(self) -> None:
185-
"""Log a comprehensive startup report (Spring Boot style)."""
206+
"""Log bean and wiring summary."""
186207
# Bean type breakdown
187208
stereotype_counts = self._context.get_bean_counts_by_stereotype()
188209
self._logger.info(
@@ -207,19 +228,54 @@ def _log_startup_summary(self) -> None:
207228
post_processors=wiring.get("post_processors", 0),
208229
)
209230

210-
# Active profiles
211-
profiles = self._context.environment.active_profiles
212-
if profiles:
213-
self._logger.info("active_profiles_summary", profiles=profiles)
231+
def _log_server_info(self) -> None:
232+
"""Log server configuration — like Spring Boot's 'Netty started on port 8080'."""
233+
import os
234+
235+
server_type = os.environ.get("_PYFLY_SERVER_TYPE", "unknown")
236+
host = os.environ.get("_PYFLY_SERVER_HOST", "0.0.0.0")
237+
port = os.environ.get("_PYFLY_SERVER_PORT", "8080")
238+
workers = os.environ.get("_PYFLY_WORKERS", "1")
239+
event_loop = os.environ.get("_PYFLY_EVENT_LOOP", "auto")
240+
http = os.environ.get("_PYFLY_HTTP", "auto")
241+
242+
# Resolve server version at runtime
243+
server_version = self._resolve_server_version(server_type)
214244

215-
# Final started message
245+
self._logger.info(
246+
"server_started",
247+
server=server_type,
248+
server_version=server_version,
249+
host=host,
250+
port=int(port),
251+
workers=int(workers),
252+
event_loop=event_loop,
253+
http=http,
254+
)
255+
256+
def _log_started(self) -> None:
257+
"""Log final 'Started X in Y seconds' — the LAST startup line (Spring Boot parity)."""
216258
self._logger.info(
217259
"application_started",
218260
app=self._name,
219261
startup_time_s=round(self._startup_time, 3),
220262
beans_initialized=self._context.bean_count,
221263
)
222264

265+
@staticmethod
266+
def _resolve_server_version(server_type: str) -> str:
267+
"""Resolve the installed version of the server library."""
268+
try:
269+
from importlib.metadata import version
270+
271+
name_map = {"granian": "granian", "uvicorn": "uvicorn", "hypercorn": "hypercorn"}
272+
pkg = name_map.get(server_type)
273+
if pkg:
274+
return version(pkg)
275+
except Exception:
276+
pass
277+
return "unknown"
278+
223279
def _log_routes_and_docs(self) -> None:
224280
"""Log mapped endpoints and documentation URLs (Spring Boot style)."""
225281
route_metadata = getattr(self, "_route_metadata", [])

0 commit comments

Comments
 (0)