Skip to content

Commit 19216ce

Browse files
committed
feat: flip streaming logs to opt-in via --upload-logs
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.
1 parent 0e8746d commit 19216ce

9 files changed

Lines changed: 76 additions & 35 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# Changelog
22

3-
## 2.2.87
3+
## 2.2.88
44

5-
- Added a streaming log channel between the CLI and the Socket backend. Each CLI invocation now reports a per-run status (`in_progress` / `success` / `failure` / `cancelled`) and uploads a transcript of its own log output, visible in the Socket admin views. The transcript is captured regardless of the local `--enable-debug` state; the existing terminal verbosity is unchanged. The feature is best-effort — registration or upload failures silently degrade and never block the scan. Opt out with `--disable-server-log-streaming`.
5+
- Added an opt-in streaming log channel between the CLI and the Socket backend. When the new `--upload-logs` flag is set, each CLI invocation registers a run, reports a per-run status (`in_progress` / `success` / `failure` / `cancelled`), and uploads a transcript of its own log output to the Socket backend for that run, visible in the Socket admin views. The transcript is captured regardless of the local `--enable-debug` state; the existing terminal verbosity is unchanged. The Socket backend can also force-enable streaming for specific orgs regardless of the flag. The feature is best-effort — registration or upload failures silently degrade and never block the scan.
66

77
## 2.2.83
88

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
66

77
[project]
88
name = "socketsecurity"
9-
version = "2.2.87"
9+
version = "2.2.88"
1010
requires-python = ">= 3.11"
1111
license = {"file" = "LICENSE"}
1212
dependencies = [

socketsecurity/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
__author__ = 'socket.dev'
2-
__version__ = '2.2.87'
2+
__version__ = '2.2.88'
33
USER_AGENT = f'SocketPythonCLI/{__version__}'

socketsecurity/config.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ class CliConfig:
9292
ignore_commit_files: bool = False
9393
disable_blocking: bool = False
9494
disable_ignore: bool = False
95-
disable_server_log_streaming: bool = False
95+
upload_logs: bool = False
9696
strict_blocking: bool = False
9797
integration_type: IntegrationType = "api"
9898
integration_org_slug: Optional[str] = None
@@ -208,7 +208,7 @@ def from_args(cls, args_list: Optional[List[str]] = None) -> 'CliConfig':
208208
'ignore_commit_files': args.ignore_commit_files,
209209
'disable_blocking': args.disable_blocking,
210210
'disable_ignore': args.disable_ignore,
211-
'disable_server_log_streaming': args.disable_server_log_streaming,
211+
'upload_logs': args.upload_logs,
212212
'strict_blocking': args.strict_blocking,
213213
'integration_type': args.integration,
214214
'pending_head': args.pending_head,
@@ -719,14 +719,16 @@ def create_argument_parser() -> argparse.ArgumentParser:
719719
help=argparse.SUPPRESS
720720
)
721721
advanced_group.add_argument(
722-
"--disable-server-log-streaming",
723-
dest="disable_server_log_streaming",
722+
"--upload-logs",
723+
dest="upload_logs",
724724
action="store_true",
725-
help="Disable streaming server-side log lines to the terminal during long-running CLI operations."
725+
help="Upload the CLI's log output to the Socket backend for this run. "
726+
"When set, the CLI registers the run with share_logs=true and streams "
727+
"its log records in 5s batches. Default off."
726728
)
727729
advanced_group.add_argument(
728-
"--disable_server_log_streaming",
729-
dest="disable_server_log_streaming",
730+
"--upload_logs",
731+
dest="upload_logs",
730732
action="store_true",
731733
help=argparse.SUPPRESS
732734
)

socketsecurity/core/cli_run.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
"""Lifecycle helpers for a CLI run on the Socket backend.
22
33
A "run" represents a single CLI invocation. `register_cli_run` opens it and
4-
returns a server-issued `run_id`; `finalize_cli_run` closes it on exit. The
5-
run_id keys the rows that `BatchedLogUploader` POSTs to
4+
returns a server-issued `run_id` when streaming is enabled; `finalize_cli_run`
5+
closes it on exit. The run_id keys the rows that `BatchedLogUploader` POSTs to
66
`/python-cli-runs/<run_id>/logs` during the run so the dashboard can show
77
what the user saw in their terminal.
88
9+
Streaming is opt-in via the `share_logs` field on register. The server may
10+
also force-enable streaming for an org regardless of the client's request,
11+
so the CLI always calls register and gates on the response's
12+
`log_streaming_enabled` flag rather than the client's intent.
13+
914
Both calls are best-effort: failures fall back to no-streaming and never
1015
prevent the scan from running.
1116
"""
@@ -23,12 +28,13 @@
2328
def register_cli_run(
2429
client: CliClient,
2530
client_version: str,
31+
share_logs: bool,
2632
) -> Optional[str]:
2733
try:
2834
resp = client.request(
2935
path="python-cli-runs",
3036
method="POST",
31-
payload=json.dumps({"client_version": client_version}),
37+
payload=json.dumps({"client_version": client_version, "share_logs": share_logs}),
3238
)
3339
except APIFailure as e:
3440
log.debug(f"cli-run register failed (streaming disabled): {e}")
@@ -40,9 +46,13 @@ def register_cli_run(
4046
log.debug(f"cli-run register: bad JSON body: {e}")
4147
return None
4248

49+
if not body.get("log_streaming_enabled"):
50+
log.debug("cli-run register: log streaming not enabled by server")
51+
return None
52+
4353
run_id = body.get("run_id")
4454
if not isinstance(run_id, str) or not run_id:
45-
log.debug(f"cli-run register: missing run_id in response: {body!r}")
55+
log.debug(f"cli-run register: enabled but missing run_id in response: {body!r}")
4656
return None
4757
return run_id
4858

socketsecurity/core/streaming.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,16 @@ def setup_streaming(
3636
cli_logger: logging.Logger,
3737
sdk_logger: logging.Logger,
3838
client_version: str,
39+
share_logs: bool,
3940
enable_debug: bool,
4041
) -> Optional[Callable[[], None]]:
4142
run_id = register_cli_run(
4243
client,
4344
client_version=client_version,
45+
share_logs=share_logs,
4446
)
4547
if not run_id:
46-
cli_logger.debug("server log streaming disabled (register failed)")
48+
cli_logger.debug("server log streaming not active for this run")
4749
return None
4850

4951
log_uploader = BatchedLogUploader(client, run_id)

socketsecurity/socketcli.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -98,16 +98,16 @@ def main_code():
9898
sdk.api.api_url = socket_config.api_url
9999
log.debug("loaded client")
100100

101-
if not config.disable_server_log_streaming:
102-
teardown = setup_streaming(
103-
client=client,
104-
cli_logger=log,
105-
sdk_logger=socket_logger,
106-
client_version=config.version,
107-
enable_debug=config.enable_debug,
108-
)
109-
if teardown:
110-
atexit.register(teardown)
101+
teardown = setup_streaming(
102+
client=client,
103+
cli_logger=log,
104+
sdk_logger=socket_logger,
105+
client_version=config.version,
106+
share_logs=config.upload_logs,
107+
enable_debug=config.enable_debug,
108+
)
109+
if teardown:
110+
atexit.register(teardown)
111111

112112
core = Core(socket_config, sdk, config)
113113
log.debug("loaded core")

tests/unit/test_cli_run.py

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,32 +12,55 @@ def _resp(payload):
1212
return r
1313

1414

15-
def test_register_cli_run_returns_run_id():
15+
def test_register_cli_run_returns_run_id_when_enabled():
1616
client = Mock(spec=CliClient)
17-
client.request.return_value = _resp({"run_id": "srv-issued-123"})
17+
client.request.return_value = _resp({
18+
"log_streaming_enabled": True,
19+
"run_id": "srv-issued-123",
20+
})
1821

19-
run_id = register_cli_run(client, client_version="1.2.3")
22+
run_id = register_cli_run(client, client_version="1.2.3", share_logs=True)
2023

2124
assert run_id == "srv-issued-123"
2225
args, kwargs = client.request.call_args
2326
assert kwargs["path"] == "python-cli-runs"
2427
assert kwargs["method"] == "POST"
2528
body = json.loads(kwargs["payload"])
26-
assert body == {"client_version": "1.2.3"}
29+
assert body == {"client_version": "1.2.3", "share_logs": True}
30+
31+
32+
def test_register_cli_run_returns_none_when_disabled_by_server():
33+
client = Mock(spec=CliClient)
34+
client.request.return_value = _resp({
35+
"log_streaming_enabled": False,
36+
"run_id": None,
37+
})
38+
39+
assert register_cli_run(client, client_version="1.0.0", share_logs=False) is None
40+
41+
42+
def test_register_cli_run_sends_share_logs_false_when_not_opted_in():
43+
client = Mock(spec=CliClient)
44+
client.request.return_value = _resp({"log_streaming_enabled": False, "run_id": None})
45+
46+
register_cli_run(client, client_version="1.0.0", share_logs=False)
47+
48+
body = json.loads(client.request.call_args.kwargs["payload"])
49+
assert body == {"client_version": "1.0.0", "share_logs": False}
2750

2851

2952
def test_register_cli_run_returns_none_on_api_failure():
3053
client = Mock(spec=CliClient)
3154
client.request.side_effect = APIFailure("network down")
3255

33-
assert register_cli_run(client, client_version="1.0.0") is None
56+
assert register_cli_run(client, client_version="1.0.0", share_logs=True) is None
3457

3558

36-
def test_register_cli_run_returns_none_on_missing_run_id():
59+
def test_register_cli_run_returns_none_on_missing_run_id_when_enabled():
3760
client = Mock(spec=CliClient)
38-
client.request.return_value = _resp({})
61+
client.request.return_value = _resp({"log_streaming_enabled": True})
3962

40-
assert register_cli_run(client, client_version="1.0.0") is None
63+
assert register_cli_run(client, client_version="1.0.0", share_logs=True) is None
4164

4265

4366
def test_register_cli_run_returns_none_on_bad_json():
@@ -46,7 +69,7 @@ def test_register_cli_run_returns_none_on_bad_json():
4669
client = Mock(spec=CliClient)
4770
client.request.return_value = bad
4871

49-
assert register_cli_run(client, client_version="1.0.0") is None
72+
assert register_cli_run(client, client_version="1.0.0", share_logs=True) is None
5073

5174

5275
def test_finalize_cli_run_posts_status_and_null_report_run_id_by_default():

tests/unit/test_streaming.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def test_setup_streaming_returns_none_when_register_fails():
2727
cli_logger=logging.getLogger("t-fail"),
2828
sdk_logger=logging.getLogger("t-fail-sdk"),
2929
client_version="1.0",
30+
share_logs=True,
3031
enable_debug=False,
3132
)
3233
assert teardown is None
@@ -50,6 +51,7 @@ def fake_finalize(client, run_id, status="success", report_run_id=None):
5051
cli_logger=cli_logger,
5152
sdk_logger=sdk_logger,
5253
client_version="1.0",
54+
share_logs=True,
5355
enable_debug=False,
5456
)
5557
assert teardown is not None
@@ -79,6 +81,7 @@ def fake_finalize(client, run_id, status="success", report_run_id=None):
7981
cli_logger=cli_logger,
8082
sdk_logger=sdk_logger,
8183
client_version="1.0",
84+
share_logs=True,
8285
enable_debug=False,
8386
)
8487
teardown()
@@ -104,6 +107,7 @@ def test_setup_streaming_restores_logger_state_on_teardown():
104107
cli_logger=cli_logger,
105108
sdk_logger=sdk_logger,
106109
client_version="1.0",
110+
share_logs=True,
107111
enable_debug=False,
108112
)
109113
# During streaming: levels and propagate are forced

0 commit comments

Comments
 (0)