Skip to content

Commit 7980214

Browse files
committed
Make log level better configurable in CLI. Propagate it from client to WM server.
1 parent d251320 commit 7980214

8 files changed

Lines changed: 44 additions & 40 deletions

File tree

docs/cli.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ python -m finecode run [options] <action> [<action> ...] [payload] [--config.<ke
5555
| `--project=<name>` | Run only in this project. Repeatable for multiple projects. |
5656
| `--concurrently` | Run actions concurrently within each project |
5757
| `--shared-server` | Connect to the shared persistent WM Server instead of starting a dedicated one |
58-
| `--trace` | Enable verbose (trace-level) logging |
58+
| `--log-level=<level>` | Set log level: `TRACE`, `DEBUG`, `INFO`, `WARNING`, `ERROR` (default: `INFO`) |
5959
| `--no-env-config` | Ignore `FINECODE_CONFIG_*` environment variables |
6060
| `--no-save-results` | Do not write action results to the cache directory |
6161

@@ -114,15 +114,15 @@ python -m finecode run lint --config.ruff.line_length=120
114114
Create and populate virtual environments for all handler dependencies.
115115

116116
```
117-
python -m finecode prepare-envs [--recreate] [--trace] [--debug]
117+
python -m finecode prepare-envs [--recreate] [--log-level=<level>] [--debug]
118118
```
119119

120120
Must be run from the workspace or project root. Creates venvs under `.venvs/<env_name>/` and installs each handler's declared dependencies.
121121

122122
| Option | Description |
123123
|---|---|
124124
| `--recreate` | Delete and recreate all venvs from scratch |
125-
| `--trace` | Enable verbose logging |
125+
| `--log-level=<level>` | Set log level: `TRACE`, `DEBUG`, `INFO`, `WARNING`, `ERROR` (default: `INFO`) |
126126
| `--debug` | Wait for a debugpy client on port 5680 before starting |
127127

128128
---
@@ -132,15 +132,15 @@ Must be run from the workspace or project root. Creates venvs under `.venvs/<env
132132
Dump the fully resolved configuration for a project to disk, useful for debugging preset and config merging.
133133

134134
```
135-
python -m finecode dump-config --project=<name> [--trace] [--debug]
135+
python -m finecode dump-config --project=<name> [--log-level=<level>] [--debug]
136136
```
137137

138138
Output is written to `<cwd>/finecode_config_dump/`.
139139

140140
| Option | Description |
141141
|---|---|
142142
| `--project=<name>` | **(Required)** Project to dump config for |
143-
| `--trace` | Enable verbose logging |
143+
| `--log-level=<level>` | Set log level: `TRACE`, `DEBUG`, `INFO`, `WARNING`, `ERROR` (default: `INFO`) |
144144
| `--debug` | Wait for a debugpy client on port 5680 |
145145

146146
---
@@ -160,7 +160,7 @@ python -m finecode start-lsp --stdio | --socket <port> | --ws [--host <host>] [-
160160
| `--ws` | Start a WebSocket server |
161161
| `--host <host>` | Host for TCP/WS server (default: 127.0.0.1 for TCP) |
162162
| `--port <port>` | Port for TCP/WS server |
163-
| `--trace` | Enable verbose logging |
163+
| `--log-level=<level>` | Set log level: `TRACE`, `DEBUG`, `INFO`, `WARNING`, `ERROR` (default: `INFO`) |
164164
| `--debug` | Wait for a debugpy client on port 5680 |
165165

166166
The LSP server connects to the **FineCode WM Server** on startup (starting one if needed). See [LSP and MCP Architecture](reference/lsp-mcp-architecture.md) for details.
@@ -172,13 +172,13 @@ The LSP server connects to the **FineCode WM Server** on startup (starting one i
172172
Start the FineCode MCP server on stdio. Connects to a running FineCode WM Server (or starts one) and exposes FineCode tools via the Model Context Protocol.
173173

174174
```text
175-
.venvs/dev_workspace/bin/python -m finecode start-mcp [--workdir=<path>] [--trace]
175+
.venvs/dev_workspace/bin/python -m finecode start-mcp [--workdir=<path>] [--log-level=<level>]
176176
```
177177

178178
| Option | Description |
179179
| --- | --- |
180180
| `--workdir=<path>` | Workspace root directory (default: current directory). |
181-
| `--trace` | Enable verbose logging |
181+
| `--log-level=<level>` | Set log level: `TRACE`, `DEBUG`, `INFO`, `WARNING`, `ERROR` (default: `INFO`) |
182182

183183
Typically started automatically by MCP-compatible clients (for example, Claude Code) — see [IDE and MCP Setup](getting-started-ide-mcp.md#mcp-setup-for-ai-clients).
184184

@@ -189,12 +189,12 @@ Typically started automatically by MCP-compatible clients (for example, Claude C
189189
Start the FineCode Workspace Manager Server standalone (TCP JSON-RPC), listen for client connections. Shuts down after the last client disconnects and the disconnect timeout expires.
190190

191191
```text
192-
python -m finecode start-wm-server [--trace] [--disconnect-timeout=<seconds>]
192+
python -m finecode start-wm-server [--log-level=<level>] [--disconnect-timeout=<seconds>]
193193
```
194194

195195
| Option | Description |
196196
| --- | --- |
197-
| `--trace` | Enable verbose logging |
197+
| `--log-level=<level>` | Set log level: `TRACE`, `DEBUG`, `INFO`, `WARNING`, `ERROR` (default: `INFO`) |
198198
| `--disconnect-timeout=<seconds>` | Seconds to wait after the last client disconnects before shutting down (default: 30) |
199199

200200
Usually started automatically by `start-lsp` or `start-mcp`. Can also be started manually for debugging.

src/finecode/cli.py

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ def cli(): ...
172172

173173

174174
@cli.command()
175-
@click.option("--trace", "trace", is_flag=True, default=False)
175+
@click.option("--log-level", "log_level", default="INFO", type=click.Choice(["TRACE", "DEBUG", "INFO", "WARNING", "ERROR"], case_sensitive=False), show_default=True)
176176
@click.option("--debug", "debug", is_flag=True, default=False)
177177
@click.option(
178178
"--socket", "tcp", default=None, type=int, help="start a TCP server"
@@ -186,7 +186,7 @@ def cli(): ...
186186
"--port", "port", default=None, type=int, help="Port for TCP and WS server"
187187
)
188188
def start_lsp(
189-
trace: bool,
189+
log_level: str,
190190
debug: bool,
191191
tcp: int | None,
192192
ws: bool,
@@ -215,7 +215,7 @@ def start_lsp(
215215
raise ValueError("Specify either --tcp, --ws or --stdio")
216216

217217
asyncio.run(
218-
wm_lsp_server.start(comm_type=comm_type, host=host, port=port, trace=trace)
218+
wm_lsp_server.start(comm_type=comm_type, host=host, port=port, log_level=log_level)
219219
)
220220

221221

@@ -247,7 +247,7 @@ def run(ctx) -> None:
247247
workdir_path: pathlib.Path = pathlib.Path(os.getcwd())
248248
processed_args_count: int = 0
249249
concurrently: bool = False
250-
trace: bool = False
250+
log_level: str = "INFO"
251251
no_env_config: bool = False
252252
save_results: bool = True
253253
map_payload_fields: set[str] = set()
@@ -272,8 +272,8 @@ def run(ctx) -> None:
272272
projects.append(project)
273273
elif arg == "--concurrently":
274274
concurrently = True
275-
elif arg == "--trace":
276-
trace = True
275+
elif arg.startswith("--log-level"):
276+
log_level = arg.removeprefix("--log-level=").upper()
277277
elif arg == "--no-env-config":
278278
no_env_config = True
279279
elif arg == "--no-save-results":
@@ -287,7 +287,7 @@ def run(ctx) -> None:
287287
break
288288
processed_args_count += 1
289289

290-
logger_utils.init_logger(log_name="cli", trace=trace, stdout=True)
290+
logger_utils.init_logger(log_name="cli", log_level=log_level, stdout=True)
291291

292292
# Parse handler config from env vars
293293
handler_config_overrides: dict[str, dict[str, dict[str, str]]] = {}
@@ -359,6 +359,7 @@ def run(ctx) -> None:
359359
save_results,
360360
map_payload_fields,
361361
own_server=not shared_server,
362+
log_level=log_level,
362363
)
363364
)
364365
click.echo(result.output)
@@ -384,11 +385,11 @@ def run(ctx) -> None:
384385

385386

386387
@cli.command()
387-
@click.option("--trace", "trace", is_flag=True, default=False)
388+
@click.option("--log-level", "log_level", default="INFO", type=click.Choice(["TRACE", "DEBUG", "INFO", "WARNING", "ERROR"], case_sensitive=False), show_default=True)
388389
@click.option("--debug", "debug", is_flag=True, default=False)
389390
@click.option("--recreate", "recreate", is_flag=True, default=False)
390391
@click.option("--shared-server", "shared_server", is_flag=True, default=False)
391-
def prepare_envs(trace: bool, debug: bool, recreate: bool, shared_server: bool) -> None:
392+
def prepare_envs(log_level: str, debug: bool, recreate: bool, shared_server: bool) -> None:
392393
"""
393394
`prepare-envs` should be called from workspace/project root directory.
394395
"""
@@ -402,7 +403,7 @@ def prepare_envs(trace: bool, debug: bool, recreate: bool, shared_server: bool)
402403
except Exception as e:
403404
logger.info(e)
404405

405-
logger_utils.init_logger(log_name="cli", trace=trace, stdout=True)
406+
logger_utils.init_logger(log_name="cli", log_level=log_level, stdout=True)
406407
user_messages._notification_sender = show_user_message
407408

408409
try:
@@ -411,6 +412,7 @@ def prepare_envs(trace: bool, debug: bool, recreate: bool, shared_server: bool)
411412
workdir_path=pathlib.Path(os.getcwd()),
412413
recreate=recreate,
413414
own_server=not shared_server,
415+
log_level=log_level,
414416
)
415417
)
416418
except prepare_envs_cmd.PrepareEnvsFailed as exception:
@@ -423,11 +425,11 @@ def prepare_envs(trace: bool, debug: bool, recreate: bool, shared_server: bool)
423425

424426

425427
@cli.command()
426-
@click.option("--trace", "trace", is_flag=True, default=False)
428+
@click.option("--log-level", "log_level", default="INFO", type=click.Choice(["TRACE", "DEBUG", "INFO", "WARNING", "ERROR"], case_sensitive=False), show_default=True)
427429
@click.option("--debug", "debug", is_flag=True, default=False)
428430
@click.option("--project", "project", type=str)
429431
@click.option("--shared-server", "shared_server", is_flag=True, default=False)
430-
def dump_config(trace: bool, debug: bool, project: str | None, shared_server: bool):
432+
def dump_config(log_level: str, debug: bool, project: str | None, shared_server: bool):
431433
if debug is True:
432434
import debugpy
433435

@@ -441,7 +443,7 @@ def dump_config(trace: bool, debug: bool, project: str | None, shared_server: bo
441443
click.echo("--project parameter is required", err=True)
442444
return
443445

444-
logger_utils.init_logger(log_name="cli", trace=trace, stdout=True)
446+
logger_utils.init_logger(log_name="cli", log_level=log_level, stdout=True)
445447
user_messages._notification_sender = show_user_message
446448

447449
try:
@@ -450,6 +452,7 @@ def dump_config(trace: bool, debug: bool, project: str | None, shared_server: bo
450452
workdir_path=pathlib.Path(os.getcwd()),
451453
project_name=project,
452454
own_server=not shared_server,
455+
log_level=log_level,
453456
)
454457
)
455458
except dump_config_cmd.DumpFailed as exception:
@@ -459,18 +462,18 @@ def dump_config(trace: bool, debug: bool, project: str | None, shared_server: bo
459462

460463
@cli.command()
461464
@click.option("--workdir", "workdir", default=None, type=str, help="Workspace root directory")
462-
@click.option("--trace", "trace", is_flag=True, default=False)
463-
def start_mcp(workdir: str | None, trace: bool):
465+
@click.option("--log-level", "log_level", default="INFO", type=click.Choice(["TRACE", "DEBUG", "INFO", "WARNING", "ERROR"], case_sensitive=False), show_default=True)
466+
def start_mcp(workdir: str | None, log_level: str):
464467
"""Start the FineCode MCP server (stdio). Connects to a running FineCode WM Server."""
465468
from finecode import mcp_server
466469

467-
logger_utils.init_logger(log_name="mcp_server", trace=trace, stdout=False)
470+
logger_utils.init_logger(log_name="mcp_server", log_level=log_level, stdout=False)
468471
workdir_path = pathlib.Path(workdir) if workdir else pathlib.Path(os.getcwd())
469472
mcp_server.start(workdir_path)
470473

471474

472475
@cli.command()
473-
@click.option("--trace", "trace", is_flag=True, default=False)
476+
@click.option("--log-level", "log_level", default="INFO", type=click.Choice(["TRACE", "DEBUG", "INFO", "WARNING", "ERROR"], case_sensitive=False), show_default=True)
474477
@click.option(
475478
"--port-file",
476479
"port_file",
@@ -487,11 +490,11 @@ def start_mcp(workdir: str | None, trace: bool):
487490
show_default=True,
488491
help="Seconds to wait after the last client disconnects before shutting down.",
489492
)
490-
def start_wm_server(trace: bool, port_file: str | None, disconnect_timeout: int):
493+
def start_wm_server(log_level: str, port_file: str | None, disconnect_timeout: int):
491494
"""Start the FineCode WM Server standalone (TCP JSON-RPC). Auto-stops when all clients disconnect."""
492495
from finecode.wm_server import wm_server
493496

494-
log_file_path = logger_utils.init_logger(log_name="wm_server", trace=trace, stdout=False)
497+
log_file_path = logger_utils.init_logger(log_name="wm_server", log_level=log_level, stdout=False)
495498
wm_server._log_file_path = log_file_path
496499
port_file_path = pathlib.Path(port_file) if port_file else None
497500
asyncio.run(wm_server.start_standalone(port_file=port_file_path, disconnect_timeout=disconnect_timeout))

src/finecode/cli_app/commands/dump_config_cmd.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ def __init__(self, message: str) -> None:
1010

1111

1212
async def dump_config(
13-
workdir_path: pathlib.Path, project_name: str, own_server: bool = True
13+
workdir_path: pathlib.Path, project_name: str, own_server: bool = True, log_level: str = "INFO"
1414
):
1515
port_file = None
1616
try:
1717
if own_server:
18-
port_file = wm_server.start_own_server(workdir_path)
18+
port_file = wm_server.start_own_server(workdir_path, log_level=log_level)
1919
try:
2020
port = await wm_server.wait_until_ready_from_file(port_file)
2121
except TimeoutError as exc:

src/finecode/cli_app/commands/prepare_envs_cmd.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def __init__(self, message: str) -> None:
1212

1313

1414
async def prepare_envs(
15-
workdir_path: pathlib.Path, recreate: bool, own_server: bool = True
15+
workdir_path: pathlib.Path, recreate: bool, own_server: bool = True, log_level: str = "INFO"
1616
) -> None:
1717
"""Prepare all virtual environments for a workspace.
1818
@@ -27,7 +27,7 @@ async def prepare_envs(
2727
port_file = None
2828
try:
2929
if own_server:
30-
port_file = wm_server.start_own_server(workdir_path)
30+
port_file = wm_server.start_own_server(workdir_path, log_level=log_level)
3131
try:
3232
port = await wm_server.wait_until_ready_from_file(port_file)
3333
except TimeoutError as exc:

src/finecode/cli_app/commands/run_cmd.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,12 @@ async def run_actions(
2626
save_results: bool = True,
2727
map_payload_fields: set[str] | None = None,
2828
own_server: bool = False,
29+
log_level: str = "INFO",
2930
) -> utils.RunActionsResult:
3031
port_file = None
3132
try:
3233
if own_server:
33-
port_file = wm_server.start_own_server(workdir_path)
34+
port_file = wm_server.start_own_server(workdir_path, log_level=log_level)
3435
try:
3536
port = await wm_server.wait_until_ready_from_file(port_file)
3637
except TimeoutError as exc:

src/finecode/logger_utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from finecode_extension_runner import logs
99

1010

11-
def init_logger(log_name: str, trace: bool, stdout: bool = False) -> Path:
11+
def init_logger(log_name: str, log_level: str = "INFO", stdout: bool = False) -> Path:
1212
venv_dir_path = Path(sys.executable).parent.parent
1313
logs_dir_path = venv_dir_path / "logs"
1414

@@ -25,7 +25,7 @@ def init_logger(log_name: str, trace: bool, stdout: bool = False) -> Path:
2525
logs.set_log_level_for_group(group="finecode_jsonrpc.client", level=logs.LogLevel.INFO)
2626
log_file_path = logs.save_logs_to_file(
2727
file_path=logs_dir_path / log_name / f"{log_name}.log",
28-
log_level="TRACE" if trace else "INFO",
28+
log_level=log_level,
2929
stdout=stdout,
3030
)
3131

src/finecode/lsp_server/main.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ async def start(
99
comm_type: communication_utils.CommunicationType,
1010
host: str | None = None,
1111
port: int | None = None,
12-
trace: bool = False,
12+
log_level: str = "INFO",
1313
) -> None:
14-
logger_utils.init_logger(log_name="lsp_server", trace=trace)
14+
logger_utils.init_logger(log_name="lsp_server", log_level=log_level)
1515
server = create_lsp_server()
1616
await server.start_io_async()

src/finecode/wm_server/wm_server.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1223,7 +1223,7 @@ async def wait_until_ready(timeout: float = 30) -> int:
12231223
)
12241224

12251225

1226-
def start_own_server(workdir: pathlib.Path) -> pathlib.Path:
1226+
def start_own_server(workdir: pathlib.Path, log_level: str = "INFO") -> pathlib.Path:
12271227
"""Start a dedicated WM server subprocess for exclusive use by one CLI call.
12281228
12291229
Unlike ``ensure_running()``, this always starts a *fresh* process and writes
@@ -1245,7 +1245,7 @@ def start_own_server(workdir: pathlib.Path) -> pathlib.Path:
12451245

12461246
logger.info(f"Starting dedicated FineCode WM server in {workdir}")
12471247
subprocess.Popen(
1248-
[sys.executable, "-m", "finecode", "start-wm-server", "--port-file", str(port_file)],
1248+
[sys.executable, "-m", "finecode", "start-wm-server", "--port-file", str(port_file), "--log-level", log_level],
12491249
cwd=str(workdir),
12501250
stdout=subprocess.DEVNULL,
12511251
stderr=subprocess.DEVNULL,

0 commit comments

Comments
 (0)