@@ -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