feat: stream CLI log transcripts and run status to Socket backend#201
Draft
Benjamin Barslev Nielsen (barslev) wants to merge 9 commits into
Draft
feat: stream CLI log transcripts and run status to Socket backend#201Benjamin Barslev Nielsen (barslev) wants to merge 9 commits into
Benjamin Barslev Nielsen (barslev) wants to merge 9 commits into
Conversation
Buffer the CLI's own log records and POST them in 5s batches to a new register/upload/finalize lifecycle so the admin dashboard renders what the user saw in their terminal alongside the run's terminal status. New modules: - core/cli_run.py — register_cli_run / finalize_cli_run helpers - core/log_uploader.py — BatchedLogUploader (daemon-thread flusher, chunked under the 256KB cap, swallows network errors, drains on shutdown) and UploadingLogHandler routing log records to it - core/streaming.py — setup_streaming() wires both into the socketcli and socketdev loggers, forces them to DEBUG so uploads capture the full history regardless of local terminal verbosity, and returns a teardown callable for the caller to register with atexit - set_run_status() propagates the terminal status through the teardown; socketcli.py exception handlers call it for KeyboardInterrupt (cancelled), uncaught Exception (failure), and any SystemExit with a non-zero code (failure) so sys.exit() paths inside main_code surface correctly instead of defaulting to success Best-effort end-to-end: registration failures fall back to no-streaming and never block the scan. Opt out with --disable-server-log-streaming. Tested against local depscan with the matching /v0/python-cli-runs/* endpoints; 173 unit tests pass.
|
🚀 Preview package published! Install with: pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple socketsecurity==2.4.8.dev7Docker image: |
The 256 KB ceiling I added speculatively when the server cap was 256 KB no longer matches the reference implementation we're mirroring, which sends each flush as a single POST regardless of size. With the server cap now well above any plausible single-flush volume, chunking is unnecessary and divergent — drop it. Removes _chunk_by_size, _MAX_BATCH_BYTES, and the four chunking tests. _flush now POSTs the entire buffered batch as one request.
The server-side handler now rejects unknown fields and the integration column has been removed from the schema (it was plumbed end-to-end but never displayed, filtered, or grouped on). Stop sending it. Removes the integration parameter from register_cli_run and setup_streaming, drops the corresponding wiring in socketcli.py, and prunes the now-pointless test_register_cli_run_omits_integration_when_falsy case.
The depscan side now joins cli_run → full_scans → repositories via the report_run_id field to surface the scanned repo in the admin dashboard view of each CLI run. Wire the CLI to send the full_scan_id (== the report_run_id depscan expects) when it has one. - finalize_cli_run accepts an optional report_run_id and includes it (nullable) in the POST body. - streaming.py adds a module-level _report_run_id holder and a set_report_run_id() setter; teardown passes it through to finalize. - socketcli.py captures diff.id at a single chokepoint after the diff-producing branches converge, guarded against the NO_DIFF_RAN / NO_SCAN_RAN sentinel values. The field is nullable end-to-end so CLI invocations that fail before producing a diff (or are run in modes that don't create one) still finalize cleanly.
- socketsecurity/__init__.py: __version__ → 2.2.87 - pyproject.toml: version → 2.2.87 - CHANGELOG.md: new 2.2.87 entry describing the streaming-logs feature Required by .github/workflows/version-check.yml, which fails the PR if the version isn't incremented relative to main.
The Socket backend changed its register contract so that log streaming
is now opt-in rather than default-on. The CLI always calls register
(cheap, lets the server force-enable for specific orgs) and gates the
downstream upload/finalize lifecycle on the response.
Wire changes:
- POST /v0/python-cli-runs body adds a required `share_logs` field.
- Response: { log_streaming_enabled: bool, run_id: <uuid|null> }.
When log_streaming_enabled is false, run_id is null and the CLI
skips the upload + finalize calls entirely.
CLI changes:
- New `--upload-logs` flag (default off). When set, the CLI sends
share_logs=true on register.
- Removed `--disable-server-log-streaming` — default is off, so an
opt-out flag no longer makes sense.
- register_cli_run takes a required share_logs arg and returns None
whenever log_streaming_enabled is false (whatever the reason: client
opted out, server denied, server unreachable).
Bumps version to 2.2.88 and updates the CHANGELOG entry to reflect
the opt-in shape.
# Conflicts: # CHANGELOG.md # pyproject.toml # socketsecurity/__init__.py # socketsecurity/socketcli.py
The version-check workflow added in main now requires uv.lock to be updated whenever pyproject.toml changes, and the SFW smoke jobs run `uv sync --locked`, which fails on an out-of-sync lockfile.
Backend now distinguishes "user wants out" from "user said nothing": - `decline_logs: true` (the new flag) overrides every other signal including the server-side org-level override, so users with a legal/consent reason for no upload get a guaranteed off. - `share_logs: true` (the existing --upload-logs) opts in. - Otherwise the server applies its own policy. Argparse enforces that --upload-logs and --no-upload-logs are mutually exclusive (post-parse check via parser.error so dash/underscore aliases on either side still coexist with the same dests). register_cli_run now sends both `share_logs` and `decline_logs` in the payload; setup_streaming forwards both. CHANGELOG 2.4.8 entry updated to call out --no-upload-logs alongside --upload-logs.
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Adds an opt-in streaming log channel between the Python CLI and the
Socket backend so the CLI run's terminal status (
in_progress/success/failure/cancelled) and a transcript of its own logoutput are visible in the Socket admin views when the user opts in
with
--upload-logs. The Socket backend may also force-enablestreaming for specific orgs regardless of the flag.
Why?
The Socket backend currently has no visibility into what happens
inside a CLI invocation — there's no record of whether a scan ran
to completion or what was logged along the way. This PR opens a
bounded, opt-in side-channel that uploads the CLI's own log records
to the backend and reports the run's terminal status, without
changing any existing CLI request on the wire.
Changes
New modules
socketsecurity/core/cli_run.py—register_cli_runandfinalize_cli_runlifecycle helpers, both best-effort.register_cli_runalways callsPOST /v0/python-cli-runswith theuser's
share_logschoice; the server responds withlog_streaming_enabledand a nullablerun_id. The CLI gates therest of the lifecycle on
log_streaming_enabled(the server canforce-enable for an org even when the client didn't ask).
finalize_cli_runaccepts an optionalreport_run_idthat linksthe run to the full-scan it produced.
socketsecurity/core/log_uploader.py—BatchedLogUploader(daemon-thread flusher, 5s flush interval, swallows all network
errors at-most-once, skips empty buffers, drains on shutdown) plus
UploadingLogHandler— alogging.Handlerthat routes recordsto the uploader. Includes a thread-local recursion guard so the
uploader's own request-error logs aren't re-enqueued mid-flush.
socketsecurity/core/streaming.py—setup_streaming()wires theuploader and a terminal handler into the
socketcliandsocketdevloggers, forces both loggers toDEBUGso the uploadcaptures the full history regardless of
--enable-debugstate,and returns a teardown callable for the caller to register with
atexit. Module-level settersset_run_status()andset_report_run_id()propagate the terminal status and theassociated full-scan id into
finalize_cli_run.Existing files
socketsecurity/config.py— adds--upload-logs/--upload_logsopt-in flag (advanced group, default off). Whenset, the CLI sends
share_logs=truein the register payload.socketsecurity/socketcli.py— always callssetup_streaming()after
CliClientinit (register is cheap and the server mayforce-enable for an org); registers the teardown via
atexitonlywhen the server returned a run_id. Captures
diff.idat a singlechokepoint after the diff-producing branches converge and threads
it through
set_report_run_id(), guarded against theNO_DIFF_RAN/NO_SCAN_RANsentinel values. Exception handlersin
cli()now callset_run_status(...):KeyboardInterrupt→cancelledSystemExitwith non-zero code →failureException→failuresuccessThe
SystemExithandling is load-bearing: several failure pathsin
main_code()callsys.exit(3)directly, which bypassesexcept Exception(sinceSystemExitis aBaseExceptionsubclass, not
Exception).Test plan
uv run pytest tests/unit— 171 unit tests pass, includingthe new tests in
test_cli_run.py,test_log_uploader.py, andtest_streaming.py(covering opt-in/opt-out register paths andthe disabled-by-server response shape).
endpoints applied:
--upload-logsset, server says enabled → full CLI log captured,run reports
status: success,report_run_idpopulated withthe full-scan id and joinable to the underlying
full_scansrow--upload-logsset, simulatedRuntimeError→ run reportsstatus: failure, log captured up to the crashintervals while the CLI is still running
--upload-logs(and no org-side override) → CLI callsregister, gets
log_streaming_enabled: false, sends no furtherstreaming requests; scan otherwise unchanged
degrades, scan continues normally, no exception surfaces
Public Changelog
A new opt-in
--upload-logsflag uploads the CLI's log output to the Socket backend for the duration of the run, alongside a per-run status (in_progress/success/failure/cancelled). The transcript is captured regardless of the local--enable-debugstate; the existing terminal verbosity is unchanged. The Socket backend can also force-enable streaming for specific orgs regardless of the flag. Default off; CLI runs without the flag are unaffected.