-
Notifications
You must be signed in to change notification settings - Fork 1.3k
refactor(observability): trigger-driven trace updates — remove live streams and console polling #2088
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
refactor(observability): trigger-driven trace updates — remove live streams and console polling #2088
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
65 changes: 65 additions & 0 deletions
65
console/packages/console-frontend/src/api/consoleEvents.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| import { getConsoleEventsWs } from './config' | ||
|
|
||
| export interface ConsoleEventsHandlers { | ||
| /** A coalesced "these traces changed" tick. Carries ids only, never span | ||
| * data — consumers re-run their own filtered queries (notify-then-query), | ||
| * so the engine stays the single source of filter semantics. */ | ||
| onTracesChanged: (traceIds: string[]) => void | ||
| /** Fired on every (re)connect; consumers resync anything missed while | ||
| * disconnected with one refetch. */ | ||
| onConnect?: () => void | ||
| } | ||
|
|
||
| /** | ||
| * Live console-event feed (`/ws/console-events`): the console worker owns a | ||
| * `trace` trigger on the engine and forwards its coalesced ticks to every | ||
| * connected browser. Replaces the old 1s blind polling of the trace list — | ||
| * an idle engine produces no traffic at all. | ||
| */ | ||
| export function createConsoleEventsSubscription(handlers: ConsoleEventsHandlers): () => void { | ||
| let socket: WebSocket | null = null | ||
| let reconnectTimer: ReturnType<typeof setTimeout> | null = null | ||
| let disposed = false | ||
|
|
||
| const connect = () => { | ||
| if (disposed) return | ||
| socket = new WebSocket(getConsoleEventsWs()) | ||
|
|
||
| socket.onopen = () => { | ||
| handlers.onConnect?.() | ||
| } | ||
|
|
||
| socket.onmessage = (event) => { | ||
| try { | ||
| const message = JSON.parse(event.data) | ||
| if (message?.type === 'traces_changed') { | ||
| const ids = Array.isArray(message.trace_ids) | ||
| ? message.trace_ids.filter((id: unknown): id is string => typeof id === 'string') | ||
| : [] | ||
| handlers.onTracesChanged(ids) | ||
| } | ||
| } catch { | ||
| // Malformed frame — the next tick self-heals. | ||
| } | ||
| } | ||
|
|
||
| socket.onclose = () => { | ||
| socket = null | ||
| if (!disposed) { | ||
| reconnectTimer = setTimeout(connect, 3000) | ||
| } | ||
| } | ||
|
|
||
| socket.onerror = () => { | ||
| socket?.close() | ||
| } | ||
| } | ||
|
|
||
| connect() | ||
|
|
||
| return () => { | ||
| disposed = true | ||
| if (reconnectTimer) clearTimeout(reconnectTimer) | ||
| socket?.close() | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| use iii_sdk::protocol::RegisterTriggerInput; | ||
| use iii_sdk::{Error, IIIClient, RegisterFunction}; | ||
| use serde_json::{json, Value}; | ||
| use tracing::info; | ||
|
|
||
| /// Broadcast channel carrying serialized console events (currently only | ||
| /// `traces_changed` ticks) from the engine bridge to every browser connected | ||
| /// on `/ws/console-events`. | ||
| pub type ConsoleEvents = tokio::sync::broadcast::Sender<String>; | ||
|
|
||
| /// Register the trace-trigger handler and its `trace` trigger. | ||
| /// | ||
| /// The engine coalesces span activity into one `{trace_ids}` tick per | ||
| /// window and invokes the handler fire-and-forget; the handler fans the tick | ||
| /// out to the browsers, which re-run their own filtered queries | ||
| /// (notify-then-query — the engine stays the single source of filter | ||
| /// semantics, and an idle engine produces no traffic at all). The trigger is | ||
| /// owned by this bridge connection, so the engine unregisters it when the | ||
| /// console disconnects. | ||
| pub fn register_trace_events(bridge: &IIIClient, events: ConsoleEvents) -> Result<(), Error> { | ||
| let tick = events.clone(); | ||
| bridge.register_function( | ||
| "engine::console::traces_changed", | ||
| RegisterFunction::new_async(move |input: Value| { | ||
| let events = tick.clone(); | ||
| async move { | ||
| let trace_ids = input | ||
| .get("trace_ids") | ||
| .cloned() | ||
| .unwrap_or_else(|| Value::Array(Vec::new())); | ||
| let frame = json!({ "type": "traces_changed", "trace_ids": trace_ids }).to_string(); | ||
| // No receiver just means no console tab is open right now. | ||
| let _ = events.send(frame); | ||
| Ok(json!({ "delivered": true })) | ||
| } | ||
| }), | ||
| ); | ||
|
|
||
| info!("Registering trace trigger: engine::console::traces_changed"); | ||
| bridge.register_trigger(RegisterTriggerInput::new( | ||
| "trace", | ||
| "engine::console::traces_changed", | ||
| json!({}), | ||
| ))?; | ||
| Ok(()) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,8 @@ | ||
| mod error; | ||
| mod events; | ||
| mod functions; | ||
| mod triggers; | ||
|
|
||
| pub use events::{register_trace_events, ConsoleEvents}; | ||
| pub use functions::register_functions; | ||
| pub use triggers::register_triggers; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: iii-hq/iii
Length of output: 1184
🌐 Web query:
axum WebSocketUpgrade Origin validation CORS WebSocket handshake axum documentation💡 Result:
The axum WebSocketUpgrade extractor does not perform automatic Origin validation [1][2][3]. It focuses on validating the HTTP handshake requirements for WebSockets, such as verifying the correct HTTP method (GET for HTTP/1.1 or CONNECT for HTTP/2), connection headers, and protocol version [2][3]. Because web browsers do not apply CORS (Cross-Origin Resource Sharing) policies to WebSocket connections in the same way they do to standard HTTP requests, standard CORS middleware typically does not prevent unauthorized WebSocket handshakes [1]. To secure your WebSocket endpoint against Cross-Site WebSocket Hijacking (CSWSH), you must manually validate the Origin header [1][4]. You can implement this validation using a custom axum middleware [1]. The middleware should: 1. Identify WebSocket upgrade requests (e.g., by checking the Upgrade header) [1]. 2. Extract the Origin header from the request [1][4]. 3. Compare the Origin against a whitelist of allowed domains [1][4]. 4. Return a 403 Forbidden status if the Origin is unauthorized [1][4]. Example implementation pattern: async fn ws_origin_filter(request: Request, next: Next) -> Response { // Check if it is a websocket upgrade request if request.headers.get(http::header::UPGRADE).map(|u| u.as_bytes == b"websocket").unwrap_or(false) { let origin = request.headers.get(http::header::ORIGIN); // Validate origin against your whitelist if!is_authorized(origin) { return Response::builder.status(StatusCode::FORBIDDEN).body(Body::new("Forbidden origin".to_owned)).unwrap; } } next.run(request).await } Keep your standard CorsLayer in place for traditional HTTP requests, as it does not inherently protect WebSocket routes [1]. Always ensure that any Origin validation is performed for every connection attempt, as browsers attach the Origin header during the WebSocket handshake to prevent CSRF-like attacks [4].
Citations:
🌐 Web query:
github iii-hq iii console/packages/console-rust/src/server.rs console_events_handler traces_changed💡 Result:
The query concerns the
iii::console::traces_changedevent and its associated handler within theiii-hqecosystem, specifically as used in theiiiconsole application [1][2]. Theiii::console::traces_changedidentifier is an engine-internal function event used to trigger live updates in the console's trace viewer [1][3]. It is primarily implemented within theiii-hq/workersrepository, which contains the logic for the console's reactive trace and session-event live streams [1][4]. Key technical aspects include: 1. Function Registration: The console registers a trigger foriii::console::traces_changedvia theiiibrowser SDK [3][5]. By prefixing the function withiii::, it is marked as engine-internal (is_iii_builtin_function_id), which ensures that the spans produced by the event delivery are tagged asiii.function.kind=internal[1][3]. This prevents them from appearing in the user's trace list (by default, the UI hides internal spans) and avoids recursive triggering or event flooding [1][3][5]. 2. Handler Logic: ThemakeTracesChangedHandlerfunction (typically found inconsole/web/src/lib/traces-stream.tsor similar files in theworkersrepository) is used to handle these events [1][3][6]. When an event is received, the handler checks if the console is paused or if the document is hidden before invalidating relevant React Query keys (such astracesandtraceGroups), which prompts the UI to refresh [1][3][5]. 3. Server Interaction: Whileconsole/packages/console-rust/src/server.rsin theiii-hq/iiirepository acts as the Rust-based Axum server that bridges the console to theiiiengine [2][7], the reactive event handling logic is primarily driven by the browser-side SDK's interaction with the engine, rather than residing directly within the server's Rust code [3][6][4]. The server itself handles WebSocket proxying and serving the React frontend [4][8]. In summary,traces_changedis the mechanism for real-time trace updates in the console, relying on internal-tagged triggers to keep the UI synchronized with the engine without creating telemetry noise [1][3].Citations:
Validate the WebSocket origin before accepting the upgrade.
console_events_handleraccepts the AxumWebSocketUpgradewithout anOrigincheck. Axum validates the WebSocket handshake but does not validate origins, and HTTP CORS does not protect WebSocket handshakes. An unapproved browser origin that can reach this endpoint can therefore receivetraces_changedtrace IDs. Reject unapproved browser origins beforews.on_upgrade, while preserving supported non-browser clients.🤖 Prompt for AI Agents
Source: Path instructions