Skip to content

feat(dgw): search session logs across recordings - #2112

Open
irvingouj@Devolutions (irvingoujAtDevolution) wants to merge 3 commits into
masterfrom
feat/recording-search
Open

irvingouj@Devolutions (irvingoujAtDevolution) wants to merge 3 commits into
masterfrom
feat/recording-search

Conversation

@irvingoujAtDevolution

@irvingoujAtDevolution irvingouj@Devolutions (irvingoujAtDevolution) commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Add POST /jet/jrec/search, so DVLS can search the .slog logs of recordings without downloading every log.

The body is { "query": "...", "recordings": "all" | ["<id>", ...] }, guarded by a new gateway.recordings.search scope, which Devolutions.Gateway.Utils gets too. The existing gateway.recordings.read only lists IDs, so reusing it would let every read token see log contents.

The search reads the .slog files and AI analysis logs listed in recording.json, with the rules of the log viewer's parser: a case-insensitive substring over the shown fields and parameters, entries the viewer drops are skipped, and long strings match on their shown part only. Hits are the original lines, newest recording first for "all". A search stops at 1000 hits, 16 MiB of hits or 256 MiB read, and reports it with limitReached.

api/jrec.rs moves to api/jrec/mod.rs to hold the search submodule.

🤖 Generated with Claude Code

Issue: DGW-463

Add `POST /jet/jrec/search`, so DVLS can search the `.slog` logs of
recordings without downloading every log.

The body is `{ "query": "...", "recordings": "all" | ["<id>", ...] }`,
guarded by a new `gateway.recordings.search` scope. The existing
`gateway.recordings.read` only lists IDs, so reusing it would let every
read token see log contents.

The search reads the `.slog` files and AI analysis logs listed in
`recording.json`, with the rules of the log viewer's parser: a
case-insensitive substring over the shown fields and parameters,
entries the viewer drops are skipped, and long strings match on their
shown part only. Hits are the original lines. A search stops at 1000
hits, 16 MiB of hits or 256 MiB read, and reports it with
`limitReached`.

`api/jrec.rs` moves to `api/jrec/mod.rs` to hold the `search`
submodule.

Issue: DGW-463

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 19, 2026 14:13
@github-actions

Copy link
Copy Markdown

Let maintainers know that an action is required on their side

  • Add the label release-required Please cut a new release (Devolutions Gateway, Devolutions Agent, Jetsocat, PowerShell module) when you request a maintainer to cut a new release (Devolutions Gateway, Devolutions Agent, Jetsocat, PowerShell module)

  • Add the label release-blocker Follow-up is required before cutting a new release if a follow-up is required before cutting a new release

  • Add the label publish-required Please publish libraries (`Devolutions.Gateway.Utils`, OpenAPI clients, etc) when you request a maintainer to publish libraries (Devolutions.Gateway.Utils, OpenAPI clients, etc.)

  • Add the label publish-blocker Follow-up is required before publishing libraries if a follow-up is required before publishing libraries

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The scan-byte limit can be bypassed, and parsing differences cause missed viewer-visible matches.

4 open issues
What changed in this PR

Adds server-side recording-log search so DVLS can find matches without downloading each log.

Changes:

  • Adds POST /jet/jrec/search with a dedicated authorization scope.
  • Searches recording and artifact logs using viewer-compatible matching and resource limits.
  • Adds OpenAPI definitions, .NET scope support, and tests.
File Description
utils/​dotnet/​Devolutions.Gateway.Utils/​src/​AccessScope.cs Adds the search scope.
utils/​dotnet/​Devolutions.Gateway.Utils.Tests/​JsonSerializationTests.cs Tests scope serialization.
testsuite/​tests/​cli/​dgw/​recording_search.rs Tests search results, ordering, deduplication, and authorization.
testsuite/​tests/​cli/​dgw/​mod.rs Registers search integration tests.
devolutions-gateway/​src/​token.rs Defines the search token scope.
devolutions-gateway/​src/​openapi.rs Registers the endpoint and schemas.
devolutions-gateway/​src/​extract.rs Enforces search authorization.
devolutions-gateway/​src/​api/​jrec/​search.rs Implements log discovery, matching, limits, and unit tests.
devolutions-gateway/​src/​api/​jrec/​mod.rs Relocates the recording API module and registers the route.
devolutions-gateway/​Cargo.toml Enables raw JSON values for returned entries.

Review effort: Balanced

let mut parsed_entries = 0;
let mut retained_bytes = 0;

while let Some(read) = read_line(&mut reader, &mut line).await? {
Comment on lines +345 to +353
// A `serde_json::Map` sorts its keys, and which parameters the viewers keep depends on their order.
#[derive(Deserialize)]
struct LineParameters {
parameters: OrderedObject,
}

let Ok(LineParameters { parameters }) = serde_json::from_slice(line) else {
return Some(false);
};
Comment on lines +361 to +362
.filter_map(|(name, value)| Some((visible_text(name), value.as_str()?)))
.filter(|(name, _)| kept_names.insert(*name))
Comment on lines +566 to +570
if line.len() + chunk.len() <= MAX_LINE_BYTES {
line.extend_from_slice(chunk);
} else {
read.too_long = true;
line.clear();
The log viewer strips a trailing `\r` before it checks a line against
the 256 KiB limit, so a CRLF line of exactly 256 KiB is shown there.
The search now strips it first too, also on a last line without `\n`.

Mark the search invariants with `INVARIANT:` and give each recording
selection case its own test.

Issue: DGW-463

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@irvingoujAtDevolution

Copy link
Copy Markdown
Contributor Author

Untested properties (marked UNTESTED: in search.rs):

  • The 256 MiB scanned-bytes budget stops the search and sets limitReached.
  • The 16 MiB hit-bytes budget stops the search and sets limitReached.

The OpenAPI document and the clients come in a separate PR, like the AI analysis endpoints in #2054.

The derived schema put `"all"` in an `AllRecordings` schema of its
own, and the C# client generator (7.9.0) turns that `oneOf` member
into a constructor that does not compile. The schema now lists
`"all"` inline, like `MonitoringProbeType`; the JSON is unchanged.

Also test the line limit one byte over it and on a last line ending
with `\r`, and keep `INVARIANT:` for definitions only.

Issue: DGW-463

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants