@@ -1366,11 +1366,6 @@ route Api = AuthApi | ClientApi | FederationApi | FederationVerifyApi | Federati
1366-- Helpers
1366-- Helpers
1367-- ============================================
1367-- ============================================
1369−-- Structured log helpers (severity-prefixed for operator filtering)
1370−logInfo = \msg -> println ("[INFO] " ++ msg)
1371−logWarn = \msg -> println ("[WARN] " ++ msg)
1372−logError = \msg -> println ("[ERROR] " ++ msg)
1374-- JSON structured log helper for machine-parseable events.
1369-- JSON structured log helper for machine-parseable events.
1375-- Outputs: {"level":"<level>","event":"<event>","msg":"<message>"}
1370-- Outputs: {"level":"<level>","event":"<event>","msg":"<message>"}
1376-- Use for key operational events that monitoring pipelines need to parse
1371-- Use for key operational events that monitoring pipelines need to parse
@@ -1378,7 +1373,11 @@ logError = \msg -> println ("[ERROR] " ++ msg)
1378-- Plain-text logInfo/logWarn/logError remain for human-readable context.
1373-- Plain-text logInfo/logWarn/logError remain for human-readable context.
1379-- Values are escaped (quotes and backslashes) to produce valid JSON even
1374-- Values are escaped (quotes and backslashes) to produce valid JSON even
1380-- when msg contains user-controlled data (e.g., error strings, server names).
1375-- when msg contains user-controlled data (e.g., error strings, server names).
1376+logEvent = \level event msg ->
1381−logEvent = \level event msg -> println ("{\"level\":\"" ++ escapeJsonString level ++ "\",\"event\":\"" ++ escapeJsonString event ++ "\",\"msg\":\"" ++ escapeJsonString msg ++ "\"}")
1377+ let formatted = "{\"level\":\"" ++ escapeJsonString level ++ "\",\"event\":\"" ++ escapeJsonString event ++ "\",\"msg\":\"" ++ escapeJsonString msg ++ "\"}"
1378+ in if level == "error" then logError formatted
1379+ else if level == "warn" then logWarn formatted
1380+ else logInfo formatted
1383-- Structured key=value logging: formats message as "k1=v1 k2=v2 ..." for consistent
1382-- Structured key=value logging: formats message as "k1=v1 k2=v2 ..." for consistent
1384-- machine-parseable log entries. Wraps logEvent with key-value pair formatting.
1383-- machine-parseable log entries. Wraps logEvent with key-value pair formatting.
@@ -8244,7 +8243,7 @@ handleCancelMessage = \messageId to authorization clientIp respond -> do
8244 -- from all dedup sets (messages, ackedIds, deliveredIds).
8243 -- from all dedup sets (messages, ackedIds, deliveredIds).
8245 nonceResult <- updateForwardNonce b messageId to t
8244 nonceResult <- updateForwardNonce b messageId to t
8246 yield {cancelled: True {}, cancelServers: allCancelServers, nonceLruEvicted: nonceResult.lruEvicted, nonceOldestAge: nonceResult.oldestRetainedAge}
8245 yield {cancelled: True {}, cancelServers: allCancelServers, nonceLruEvicted: nonceResult.lruEvicted, nonceOldestAge: nonceResult.oldestRetainedAge}
8247− -- Log nonce eviction outside atomic block (println can't run inside STM)
8246+ -- Log nonce eviction outside atomic block (IO can't run inside STM)
8248 logNonceEviction (pubkeyBucket to) cancelResult.nonceLruEvicted cancelResult.nonceOldestAge
8247 logNonceEviction (pubkeyBucket to) cancelResult.nonceLruEvicted cancelResult.nonceOldestAge
8249 -- Notify recipient that this message was retracted (delivered via next poll)
8248 -- Notify recipient that this message was retracted (delivered via next poll)
8250 when cancelResult.cancelled (do
8249 when cancelResult.cancelled (do
@@ -8334,7 +8333,7 @@ sendBatchInner = \senderKey messages t acc ->
8334 sendBatchInner senderKey sameR.rest t (union acc results)
8333 sendBatchInner senderKey sameR.rest t (union acc results)
8336-- Pre-generate message IDs and validate timestamps for a group of messages.
8335-- Pre-generate message IDs and validate timestamps for a group of messages.
8337−-- IO actions (genToken, logWarn) can't run inside atomic, so this runs outside.
8336+-- IO actions (genToken, logging) can't run inside atomic, so this runs outside.
8338prepareBatchMessages = \messages t acc ->
8337prepareBatchMessages = \messages t acc ->
8339 case headOf messages of
8338 case headOf messages of
8340 Nothing {} -> yield acc
8339 Nothing {} -> yield acc
@@ -9119,11 +9118,11 @@ serve = \req -> do
9119 -- Polls only logged when slow (> slowRequestThreshold) to avoid log volume from long-poll traffic.
9118 -- Polls only logged when slow (> slowRequestThreshold) to avoid log volume from long-poll traffic.
9120 if path != ""
9119 if path != ""
9122− println ("{\"level\":\"info\",\"event\":\"access\",\"req\":\"" ++ escapeJsonString reqId ++ "\",\"path\":\"" ++ escapeJsonString path ++ "\",\"ms\":" ++ show latency ++ (if ip != "" then ",\"ip\":\"" ++ escapeJsonString ip ++ "\"" else "") ++ "}")
9121+ logInfo ("{\"event\":\"access\",\"req\":\"" ++ escapeJsonString reqId ++ "\",\"path\":\"" ++ escapeJsonString path ++ "\",\"ms\":" ++ show latency ++ (if ip != "" then ",\"ip\":\"" ++ escapeJsonString ip ++ "\"" else "") ++ "}")
9123 when (latency > slowRequestThreshold)
9122 when (latency > slowRequestThreshold)
9124 (logEvent "warn" "slow_request" (path ++ " " ++ show latency ++ "ms"))
9123 (logEvent "warn" "slow_request" (path ++ " " ++ show latency ++ "ms"))
9125 else when (latency > slowRequestThreshold) (do
9124 else when (latency > slowRequestThreshold) (do
9126− println ("{\"level\":\"warn\",\"event\":\"access\",\"req\":\"" ++ escapeJsonString reqId ++ "\",\"path\":\"poll\",\"ms\":" ++ show latency ++ (if ip != "" then ",\"ip\":\"" ++ escapeJsonString ip ++ "\"" else "") ++ ",\"slow\":true}")
9125+ logWarn ("{\"event\":\"access\",\"req\":\"" ++ escapeJsonString reqId ++ "\",\"path\":\"poll\",\"ms\":" ++ show latency ++ (if ip != "" then ",\"ip\":\"" ++ escapeJsonString ip ++ "\"" else "") ++ ",\"slow\":true}")
9127 logEvent "warn" "slow_request" ("poll " ++ show latency ++ "ms"))
9126 logEvent "warn" "slow_request" ("poll " ++ show latency ++ "ms"))
9129-- ============================================
9128-- ============================================