77import os
88import time
99from collections import deque
10- from datetime import UTC , datetime
10+ from dataclasses import asdict , dataclass
1111from typing import TYPE_CHECKING
1212
1313import structlog
@@ -77,6 +77,84 @@ def history(self, key: str, max_points: int = 60) -> list[float]:
7777_rate_tracker = RateTracker (window_seconds = 600 )
7878
7979
80+ # ---------------------------------------------------------------------------
81+ # Request log — ring buffer for live feed
82+ # ---------------------------------------------------------------------------
83+
84+
85+ @dataclass (slots = True , frozen = True )
86+ class RequestEntry :
87+ """Single request log entry for the live feed."""
88+
89+ timestamp : float
90+ method : str
91+ path : str
92+ operation : str
93+ status : int
94+ duration_ms : float
95+ size : int
96+ crypto : str
97+
98+
99+ class RequestLog :
100+ """Fixed-size ring buffer of recent requests for the live feed."""
101+
102+ ENCRYPT_OPS = frozenset ({
103+ "PutObject" , "UploadPart" , "UploadPartCopy" ,
104+ "CompleteMultipartUpload" , "CopyObject" ,
105+ })
106+ DECRYPT_OPS = frozenset ({"GetObject" })
107+
108+ def __init__ (self , maxlen : int = 200 ):
109+ self ._entries : deque [RequestEntry ] = deque (maxlen = maxlen )
110+
111+ def record (
112+ self ,
113+ method : str ,
114+ path : str ,
115+ operation : str ,
116+ status : int ,
117+ duration : float ,
118+ size : int ,
119+ ) -> None :
120+ crypto = ""
121+ if operation in self .ENCRYPT_OPS :
122+ crypto = "encrypt"
123+ elif operation in self .DECRYPT_OPS :
124+ crypto = "decrypt"
125+ self ._entries .append (RequestEntry (
126+ timestamp = time .time (),
127+ method = method ,
128+ path = path [:120 ],
129+ operation = operation ,
130+ status = status ,
131+ duration_ms = round (duration * 1000 , 1 ),
132+ size = size ,
133+ crypto = crypto ,
134+ ))
135+
136+ def recent (self , limit : int = 50 ) -> list [dict ]:
137+ """Return most recent entries as dicts, newest first."""
138+ entries = list (self ._entries )
139+ entries .reverse ()
140+ return [asdict (e ) for e in entries [:limit ]]
141+
142+
143+ _request_log = RequestLog (maxlen = 200 )
144+
145+
146+ def record_request (
147+ method : str ,
148+ path : str ,
149+ operation : str ,
150+ status : int ,
151+ duration : float ,
152+ size : int ,
153+ ) -> None :
154+ """Record a completed request to the live feed log."""
155+ _request_log .record (method , path , operation , status , duration , size )
156+
157+
80158# ---------------------------------------------------------------------------
81159# Prometheus helpers
82160# ---------------------------------------------------------------------------
@@ -206,23 +284,6 @@ def collect_throughput() -> dict:
206284 }
207285
208286
209- async def collect_upload_status (handler : S3ProxyHandler ) -> dict :
210- """Collect active multipart upload status with stale detection."""
211- uploads = await handler .multipart_manager .list_active_uploads ()
212- now = datetime .now (UTC )
213- for upload in uploads :
214- created = datetime .fromisoformat (upload ["created_at" ])
215- if created .tzinfo is None :
216- created = created .replace (tzinfo = UTC )
217- age_seconds = (now - created ).total_seconds ()
218- upload ["is_stale" ] = age_seconds > 1800
219- upload ["total_plaintext_size_formatted" ] = _format_bytes (upload ["total_plaintext_size" ])
220- return {
221- "active_count" : len (uploads ),
222- "uploads" : uploads ,
223- }
224-
225-
226287# ---------------------------------------------------------------------------
227288# Redis pod metrics publishing (multi-pod view)
228289# ---------------------------------------------------------------------------
@@ -301,7 +362,6 @@ async def collect_all(
301362 pod = collect_pod_identity (settings , start_time )
302363 health = collect_health ()
303364 throughput = collect_throughput ()
304- upload_status = await collect_upload_status (handler )
305365
306366 local_data = {
307367 "pod" : pod ,
@@ -328,6 +388,6 @@ async def collect_all(
328388
329389 return {
330390 ** local_data ,
331- "uploads " : upload_status ,
391+ "request_log " : _request_log . recent ( 50 ) ,
332392 "all_pods" : all_pods ,
333393 }
0 commit comments