Skip to content

Commit d93c1eb

Browse files
[Feature]: Default CLI log level is DEBUG; WARNING and above go to STDOUT, DEBUG logs to a file #2939
1 parent 5b8ce35 commit d93c1eb

File tree

4 files changed

+70
-8
lines changed

4 files changed

+70
-8
lines changed

docs/docs/guides/troubleshooting.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,14 @@ If you have a question or need help, feel free to ask it in our [Discord server]
1414
Make sure to provide clear, detailed steps to reproduce the issue.
1515
Include server logs, CLI outputs, and configuration samples. Avoid using screenshots for logs or errors—use text instead.
1616

17-
To get more detailed logs, make sure to set the `DSTACK_CLI_LOG_LEVEL` and `DSTACK_SERVER_LOG_LEVEL`
18-
environment variables to `debug` when running the CLI and the server, respectively.
17+
#### Server logs
18+
19+
To get more detailed server logs, set the `DSTACK_SERVER_LOG_LEVEL`
20+
environment variables to `debug`. By default, it is set to `INFO`.
21+
22+
#### CLI logs
23+
24+
CLI logs are located in `~/.dstack/logs/cli`, and the default log level is `DEBUG`.
1925

2026
> See these examples for well-reported issues: [this :material-arrow-top-right-thin:{ .external }](https://github.com/dstackai/dstack/issues/1640){:target="_blank"}
2127
and [this :material-arrow-top-right-thin:{ .external }](https://github.com/dstackai/dstack/issues/1551){:target="_blank"}.

docs/docs/reference/environment-variables.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ For more details on the options below, refer to the [server deployment](../guide
145145

146146
The following environment variables are supported by the CLI.
147147

148-
- `DSTACK_CLI_LOG_LEVEL`{ #DSTACK_CLI_LOG_LEVEL } – Configures CLI logging level. Defaults to `INFO`.
148+
- `DSTACK_CLI_LOG_LEVEL`{ #DSTACK_CLI_LOG_LEVEL } – Sets the logging level for CLI output to stdout. Defaults to `INFO`.
149149

150150
Example:
151151

@@ -157,4 +157,17 @@ $ DSTACK_CLI_LOG_LEVEL=debug dstack apply -f .dstack.yml
157157

158158
</div>
159159

160+
- `DSTACK_CLI_FILE_LOG_LEVEL`{ #DSTACK_CLI_FILE_LOG_LEVEL } – Sets the logging level for CLI log files. Defaults to `DEBUG`.
161+
162+
<div class="termy">
163+
164+
```shell
165+
$ find ~/.dstack/logs/cli/
166+
167+
~/.dstack/logs/cli/latest.log
168+
~/.dstack/logs/cli/2025-07-31.log
169+
```
170+
171+
</div>
172+
160173
- `DSTACK_PROJECT`{ #DSTACK_PROJECT } – Has the same effect as `--project`. Defaults to `None`.

src/dstack/_internal/cli/utils/common.py

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import logging
22
import os
3+
from datetime import datetime, timezone
34
from typing import Any, Dict, Union
45

56
from rich.console import Console
@@ -9,6 +10,7 @@
910

1011
from dstack._internal.cli.utils.rich import DstackRichHandler
1112
from dstack._internal.core.errors import CLIError, DstackError
13+
from dstack._internal.utils.common import get_dstack_dir
1214

1315
_colors = {
1416
"secondary": "grey58",
@@ -37,10 +39,51 @@ def cli_error(e: DstackError) -> CLIError:
3739

3840
def configure_logging():
3941
dstack_logger = logging.getLogger("dstack")
40-
dstack_logger.setLevel(os.getenv("DSTACK_CLI_LOG_LEVEL", "INFO").upper())
41-
handler = DstackRichHandler(console=console)
42-
handler.setFormatter(logging.Formatter(fmt="%(message)s", datefmt="[%X]"))
43-
dstack_logger.addHandler(handler)
42+
dstack_logger.handlers.clear()
43+
44+
log_dir = get_dstack_dir() / "logs" / "cli"
45+
log_file = log_dir / "latest.log"
46+
47+
if log_file.exists():
48+
file_mtime = datetime.fromtimestamp(log_file.stat().st_mtime, tz=timezone.utc)
49+
current_date = datetime.now(timezone.utc).date()
50+
51+
if file_mtime.date() < current_date:
52+
date_str = file_mtime.strftime("%Y-%m-%d")
53+
rotated_file = log_dir / f"{date_str}.log"
54+
55+
counter = 1
56+
while rotated_file.exists():
57+
rotated_file = log_dir / f"{date_str}-{counter}.log"
58+
counter += 1
59+
60+
log_file.rename(rotated_file)
61+
62+
level_names = logging.getLevelNamesMapping()
63+
stdout_level_name = os.getenv("DSTACK_CLI_LOG_LEVEL", "INFO").upper()
64+
stdout_level = level_names[stdout_level_name]
65+
dstack_logger.setLevel(stdout_level)
66+
67+
stdout_handler = DstackRichHandler(console=console)
68+
stdout_handler.setFormatter(logging.Formatter(fmt="%(message)s", datefmt="[%X]"))
69+
stdout_handler.setLevel(stdout_level)
70+
dstack_logger.addHandler(stdout_handler)
71+
72+
file_level_name = os.getenv("DSTACK_CLI_FILE_LOG_LEVEL", "DEBUG").upper()
73+
file_level = level_names[file_level_name]
74+
75+
log_dir.mkdir(parents=True, exist_ok=True)
76+
77+
file_handler = logging.FileHandler(log_file)
78+
file_handler.setFormatter(
79+
logging.Formatter(
80+
fmt="%(asctime)s - %(name)s - %(levelname)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S"
81+
)
82+
)
83+
file_handler.setLevel(file_level)
84+
dstack_logger.addHandler(file_handler)
85+
86+
dstack_logger.setLevel(min(stdout_level, file_level))
4487

4588

4689
def confirm_ask(prompt, **kwargs) -> bool:

src/dstack/api/server/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ def _request(
173173
raise ClientError(
174174
f"Unexpected error: status code {resp.status_code}"
175175
f" when requesting {resp.request.url}."
176-
" Check server logs or run with DSTACK_CLI_LOG_LEVEL=DEBUG to see more details"
176+
" Check the server logs for backend issues, and the CLI logs at (~/.dstack/logs/cli/latest.log) local CLI output"
177177
)
178178
return resp
179179

0 commit comments

Comments
 (0)