-
Notifications
You must be signed in to change notification settings - Fork 27
feat(webapp): add session recording log parser package #1859
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
Krista House (kristahouse)
merged 13 commits into
master
from
kristahouse-dgw-405-recording-log-parser
Jul 20, 2026
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
16f17f1
feat(webapp): add session recording log parser package
kristahouse 865a2de
fix(webapp): address copilot parser review feedback
kristahouse af383d7
fix(webapp): harden parser option handling and search scaling
kristahouse 262fe45
docs(webapp): clarify ADR acronym in session recording log readme
kristahouse 092821c
docs(test): add library-only slog MVP examples and fixtures
kristahouse e21964a
chore(webapp): enforce LF endings for new package files
kristahouse 7bdfc69
fix(webapp): enforce biome formatting for vite config
kristahouse 663b10b
fix(webapp): tighten warning association and parser memory bounds
kristahouse b6e3534
fix(webapp): harden slog parser completion and warning bounds
kristahouse 7bcaa0c
ci(webapp): run slog package tests and align parser docs
kristahouse 23b0234
fix(webapp): tighten slog parser scan and sequence diagnostics
kristahouse f32b368
fix(webapp): harden slog parser truncation lifecycle semantics
kristahouse d345879
fix(webapp): cap retained slog source-text memory
kristahouse 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,16 +1,24 @@ | ||
| *.rs text eol=lf | ||
| *.toml text eol=lf | ||
| *.cs text eol=lf | ||
| *.js text eol=lf | ||
| *.ps1 text eol=lf | ||
| *.sln text eol=crlf | ||
| *.md text eol=lf | ||
| *.mustache text eol=lf | ||
| *.yaml text eol=lf | ||
|
|
||
| devolutions-gateway/openapi/doc/index.adoc linguist-generated merge=binary | ||
| devolutions-gateway/openapi/dotnet-client/src/** linguist-generated merge=binary | ||
| devolutions-gateway/openapi/dotnet-client/docs/** linguist-generated merge=binary | ||
| devolutions-gateway/openapi/dotnet-subscriber/src/** linguist-generated merge=binary | ||
| devolutions-gateway/openapi/ts-angular-client/api/** linguist-generated merge=binary | ||
| devolutions-gateway/openapi/ts-angular-client/model/** linguist-generated merge=binary | ||
| *.rs text eol=lf | ||
| *.toml text eol=lf | ||
| *.cs text eol=lf | ||
| *.js text eol=lf | ||
| *.ts text eol=lf | ||
| *.tsx text eol=lf | ||
| *.json text eol=lf | ||
| *.ps1 text eol=lf | ||
| *.sln text eol=crlf | ||
| *.md text eol=lf | ||
| *.mustache text eol=lf | ||
| *.yml text eol=lf | ||
| *.yaml text eol=lf | ||
| *.css text eol=lf | ||
| *.scss text eol=lf | ||
| *.html text eol=lf | ||
| *.slog text eol=lf | ||
|
|
||
| devolutions-gateway/openapi/doc/index.adoc linguist-generated merge=binary | ||
| devolutions-gateway/openapi/dotnet-client/src/** linguist-generated merge=binary | ||
| devolutions-gateway/openapi/dotnet-client/docs/** linguist-generated merge=binary | ||
| devolutions-gateway/openapi/dotnet-subscriber/src/** linguist-generated merge=binary | ||
| devolutions-gateway/openapi/ts-angular-client/api/** linguist-generated merge=binary | ||
| devolutions-gateway/openapi/ts-angular-client/model/** linguist-generated merge=binary |
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 |
|---|---|---|
| @@ -1,16 +1,17 @@ | ||
| # Editor configuration, see https://editorconfig.org | ||
| root = true | ||
|
|
||
| [*] | ||
| charset = utf-8 | ||
| indent_style = space | ||
| indent_size = 2 | ||
| insert_final_newline = true | ||
| trim_trailing_whitespace = true | ||
|
|
||
| [*.ts] | ||
| quote_type = single | ||
|
|
||
| [*.md] | ||
| max_line_length = off | ||
| trim_trailing_whitespace = false | ||
| # Editor configuration, see https://editorconfig.org | ||
| root = true | ||
|
|
||
| [*] | ||
| charset = utf-8 | ||
| indent_style = space | ||
| indent_size = 2 | ||
| end_of_line = lf | ||
| insert_final_newline = true | ||
| trim_trailing_whitespace = true | ||
|
|
||
| [*.ts] | ||
| quote_type = single | ||
|
|
||
| [*.md] | ||
| max_line_length = off | ||
| trim_trailing_whitespace = false |
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,178 @@ | ||
| # @devolutions/session-recording-log | ||
|
|
||
| Framework-agnostic models, parser, ordering, and in-file search for Session Recording Log (`.slog`) files. | ||
|
|
||
| ## Features | ||
|
|
||
| - NDJSON parser that keeps valid entries even when other lines are malformed. | ||
| - Structured warning codes for parser behavior and host branching. | ||
| - Source metadata (`sourceLineNumber`, `sourceIndex`, `sourceText`) preserved for each parsed entry. | ||
| - Non-mutating display-order helper (sort by `seq`, tie-break on `sourceIndex`). | ||
| - Bounded in-memory search over visible fields with optional event/warning-linked filtering. | ||
| - Hardened bounds for large/malformed inputs (line size, string length, parameter count, depth, entry/scan caps). | ||
|
|
||
| ## Usage | ||
|
|
||
| ### Basic usage | ||
|
|
||
| ```ts | ||
| import { parseSessionRecordingLog } from '@devolutions/session-recording-log'; | ||
|
|
||
| const result = parseSessionRecordingLog(slogText); | ||
| console.log(result.completionState); | ||
| console.log(result.entries.length); | ||
| console.log(result.warnings.map((warning) => warning.code)); | ||
| ``` | ||
|
|
||
| ### Full runnable example | ||
|
|
||
| ```ts | ||
| import { | ||
| getSessionRecordingLogDisplayEntries, | ||
| parseSessionRecordingLog, | ||
| searchSessionRecordingLogEntries, | ||
| } from '@devolutions/session-recording-log'; | ||
|
|
||
| const slogText = [ | ||
| '{"timestamp":"2026-07-15T21:17:49.777Z","seq":0,"event":"session.start","description":"Session started","actor":"Administrator","host":"IT-HELP-DC","sessionType":"ADConsole"}', | ||
| '{"timestamp":"2026-07-15T21:19:14.351Z","seq":1,"event":"session.action","description":"Renamed Object","object":"Help Desk Ottawa","parameters":{"Members added":"Sarah O\'Connor, Bob Smith"}}', | ||
| // Missing session.end on purpose. | ||
| ].join('\n'); | ||
|
|
||
| const parseResult = parseSessionRecordingLog(slogText); | ||
|
|
||
| // Parse status + warning surfacing. | ||
| console.log(parseResult.completionState); // 'ended-unexpectedly' | ||
| for (const warning of parseResult.warnings) { | ||
| console.log(`${warning.code} line=${warning.sourceLineNumber ?? '-'} seq=${warning.seq ?? '-'}`); | ||
| } | ||
|
|
||
| // Non-mutating display order by seq/sourceIndex. | ||
| const displayEntries = getSessionRecordingLogDisplayEntries(parseResult); | ||
| console.log(displayEntries.map((entry) => `${entry.entry.seq}:${entry.entry.event}`)); | ||
|
|
||
| // Basic search/filter. | ||
| const actionHits = searchSessionRecordingLogEntries(parseResult.entries, "o'connor", { | ||
| eventTypes: ['session.action'], | ||
| }); | ||
| const warningLinked = searchSessionRecordingLogEntries(parseResult.entries, '', { | ||
| onlyWithWarnings: true, | ||
| warnings: parseResult.warnings, | ||
| }); | ||
|
|
||
| console.log(actionHits.length); // 1 | ||
| console.log(warningLinked.length); // entries linked to parser warnings | ||
| ``` | ||
|
|
||
| ## API Reference | ||
|
|
||
| Public API: | ||
|
|
||
| - `parseSessionRecordingLog(text, options?)` | ||
| - `getSessionRecordingLogDisplayEntries(parseResult)` | ||
| - `searchSessionRecordingLogEntries(entries, query, options?)` | ||
| - `isSessionRecordingLogFileName(fileName)` | ||
|
|
||
| Host contract: | ||
|
|
||
| - Hosts detect `.slog` artifacts from `recording.json` file names (for example `recording-0.slog`). | ||
| - Hosts keep original bytes for download scenarios. | ||
| - Hosts decode UTF-8 text and pass the decoded NDJSON text to the parser. | ||
| - This package does not reconstruct downloadable `.slog` content. | ||
|
|
||
| Architecture Decision Record 2 (ADR-2) canonical contract: | ||
|
|
||
| - `parseSessionRecordingLog` returns: | ||
| - `entries: ParsedSessionRecordingLogEntry[]` in original file order. | ||
| - `warnings: SessionRecordingLogWarning[]` with structured `code`. | ||
| - `completionState: 'complete' | 'ended-unexpectedly'`. | ||
| - `ParsedSessionRecordingLogEntry` exposes `entry`, `sourceLineNumber`, and `sourceIndex`. | ||
| - Public warning codes follow ADR-2 categories: | ||
| - `malformed-line` | ||
| - `missing-session-start` | ||
| - `missing-session-end` | ||
| - `duplicate-sequence` | ||
| - `missing-sequence` | ||
| - `sequence-order-mismatch` | ||
| - `unknown-event-type` | ||
| - `invalid-field` | ||
| - `entry-limit-exceeded` | ||
| - `string-truncated` | ||
| - Additive warning: | ||
| - `unterminated-final-line` (informational; valid final line without newline is not semantically invalid). | ||
| - Intended for Architecture Decision Record (ADR) addendum documentation rather than replacement of canonical malformed handling. | ||
|
|
||
| Schema-limit options: | ||
|
|
||
| - `maxLineLengthBytes` | ||
| - `maxRetainedSourceTextBytes` | ||
| - `maxStringLength` | ||
| - `maxParameterCount` | ||
| - `maxObjectDepth` | ||
| - `maxParsedEntries` | ||
| - `maxScannedLines` | ||
| - `maxMissingSequenceWarnings` | ||
| - `maxUnknownFieldCount` | ||
| - `maxWarnings` | ||
|
|
||
| Ratified v1 defaults (review decision): | ||
|
|
||
| - `maxLineLengthBytes`: `262144` (256 KiB) | ||
| - `maxRetainedSourceTextBytes`: `8388608` (8 MiB) | ||
| - `maxStringLength`: `4096` | ||
| - `maxParameterCount`: `200` | ||
| - `maxObjectDepth`: `8` | ||
| - `maxParsedEntries`: `10000` | ||
| - `maxScannedLines`: `20000` | ||
| - `maxMissingSequenceWarnings`: `1000` | ||
| - `maxUnknownFieldCount`: `100` | ||
| - `maxWarnings`: `10000` | ||
| - search default limit: `100` | ||
| - search max limit: `1000` | ||
|
|
||
| ## Project Structure | ||
|
|
||
| ```txt | ||
| session-recording-log/ | ||
| src/ | ||
| fixtures/ # sample .slog fixtures used by package tests | ||
| parser.ts | ||
| ordering.ts | ||
| search.ts | ||
| manifest.ts | ||
| model.ts | ||
| index.ts | ||
| parser.test.ts | ||
| helpers.test.ts | ||
| README.md | ||
| package.json | ||
| package.dist.json | ||
| tsconfig.json | ||
| vite.config.ts | ||
| ``` | ||
|
|
||
| ## Dependencies | ||
|
|
||
| - Runtime dependencies: none | ||
| - Dev dependencies: TypeScript, Vite, Vitest, Biome, `vite-plugin-dts`, `vite-plugin-static-copy` | ||
|
|
||
| ## For Developers | ||
|
|
||
| From `webapp/`: | ||
|
|
||
| ```bash | ||
| pnpm --filter @devolutions/session-recording-log check:write | ||
| pnpm --filter @devolutions/session-recording-log check | ||
| pnpm --filter @devolutions/session-recording-log test | ||
| pnpm --filter @devolutions/session-recording-log build | ||
| ``` | ||
|
|
||
| ## Review-driven hardening updates (2026-07-17) | ||
|
|
||
| - Added bounded missing-sequence emission to prevent unbounded gap loops on sparse large `seq` ranges. | ||
| - Replaced recursive depth traversal with iterative depth checks to prevent stack-overflow failure on deeply nested JSON. | ||
| - Added malformed-heavy input bounding with `maxScannedLines`, and finite default `maxParsedEntries`. | ||
| - Fixed lifecycle classification so only empty/whitespace-only input is `complete`; non-empty input without `session.end` remains `ended-unexpectedly`. | ||
| - Added top-level unknown field cap (`maxUnknownFieldCount`) with `entry-limit-exceeded` warning when truncated. | ||
| - Split unknown future events into explicit `unknown-event-type` warning code. | ||
| - Clarified alias intent: `SessionRecordingLogRecord` is retained for AD historical naming compatibility. | ||
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,19 @@ | ||
| #!/usr/bin/env pwsh | ||
|
|
||
| $ErrorActionPreference = "Stop" | ||
|
|
||
| Push-Location -Path $PSScriptRoot | ||
|
|
||
| try | ||
| { | ||
| pnpm install | ||
|
|
||
| pnpm --filter @devolutions/session-recording-log... build | ||
|
|
||
| Set-Location -Path ./dist/ | ||
| npm pack | ||
| } | ||
| finally | ||
| { | ||
| Pop-Location | ||
| } |
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,30 @@ | ||
| { | ||
| "name": "@devolutions/session-recording-log", | ||
| "version": "0.1.0", | ||
| "description": "Framework-agnostic parser and models for Devolutions Gateway Session Recording Log NDJSON files", | ||
| "type": "module", | ||
| "main": "./index.js", | ||
| "module": "./index.js", | ||
| "types": "./index.d.ts", | ||
| "exports": { | ||
| ".": { | ||
| "import": "./index.js", | ||
| "types": "./index.d.ts" | ||
| } | ||
| }, | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "https://github.com/Devolutions/devolutions-gateway.git" | ||
| }, | ||
| "keywords": [ | ||
| "recording", | ||
| "session-recording", | ||
| "ndjson", | ||
| "slog", | ||
| "parser" | ||
| ], | ||
| "license": "MIT OR Apache-2.0", | ||
| "publishConfig": { | ||
| "access": "public" | ||
| } | ||
| } |
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,28 @@ | ||
| { | ||
| "name": "@devolutions/session-recording-log", | ||
| "private": true, | ||
| "version": "0.0.0", | ||
| "type": "module", | ||
| "main": "./dist/index.js", | ||
| "types": "./dist/index.d.ts", | ||
| "exports": { | ||
| ".": { | ||
| "import": "./dist/index.js", | ||
| "types": "./dist/index.d.ts" | ||
| } | ||
| }, | ||
| "scripts": { | ||
| "build": "tsc && vite build", | ||
| "check": "biome check ./src vite.config.ts", | ||
| "check:write": "biome check --write ./src vite.config.ts", | ||
| "test": "vitest run" | ||
|
kristahouse marked this conversation as resolved.
|
||
| }, | ||
| "devDependencies": { | ||
| "@types/node": "^20.19.0", | ||
| "typescript": "~5.6.2", | ||
| "vite": "^6.2.1", | ||
| "vite-plugin-dts": "^4.3.0", | ||
| "vite-plugin-static-copy": "^2.3.0", | ||
|
kristahouse marked this conversation as resolved.
|
||
| "vitest": "^3.1.1" | ||
| } | ||
| } | ||
3 changes: 3 additions & 0 deletions
3
webapp/packages/session-recording-log/src/fixtures/sample-complete.slog
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,3 @@ | ||
| {"timestamp":"2026-07-15T16:15:35.140Z","seq":0,"actor":"Administrator","locale":"en-US","host":"IT-HELP-DC","sessionType":"ADConsole","event":"session.start","description":"Session started"} | ||
| {"timestamp":"2026-07-15T16:15:41.054Z","seq":1,"event":"session.action","description":"Created User","object":"Help Desk Ottawa","parameters":{"New name":"Help Desk Ottawa/Hull"}} | ||
| {"timestamp":"2026-07-15T16:16:32.759Z","seq":2,"event":"session.end","description":"Session ended"} |
2 changes: 2 additions & 0 deletions
2
webapp/packages/session-recording-log/src/fixtures/sample-missing-end.slog
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,2 @@ | ||
| {"timestamp":"2026-07-15T21:17:49.777Z","seq":0,"actor":"Administrator","host":"IT-HELP-DC","sessionType":"ADConsole","event":"session.start","description":"Session started"} | ||
| {"timestamp":"2026-07-15T21:19:14.351Z","seq":1,"event":"session.action","description":"Renamed Object","object":"Help Desk Ottawa","parameters":{"New name":"Help Desk Ottawa/Hull","Members added":"Sarah O'Connor, Bob Smith"}} |
3 changes: 3 additions & 0 deletions
3
webapp/packages/session-recording-log/src/fixtures/sample-warning-linked.slog
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,3 @@ | ||
| {"timestamp":"2026-07-15T21:17:49.777Z","seq":0,"event":"session.start","description":"Session started"} | ||
| {"timestamp":"2026-07-15T21:19:14.351Z","seq":1,"event":"session.future","description":"Future action","parameters":{"Members added":"Sarah O'Connor, Bob Smith"}} | ||
| {"timestamp":"2026-07-15T21:20:00.000Z","seq":2,"event":"session.end","description":"Session ended"} |
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.
Uh oh!
There was an error while loading. Please reload this page.