Skip to content

Commit 5f420f8

Browse files
authored
Chore: Make the debug mode easier to use (#1501)
1 parent 6b55581 commit 5f420f8

File tree

4 files changed

+79
-26
lines changed

4 files changed

+79
-26
lines changed

sqlmesh/__init__.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import os
88
import sys
99
import typing as t
10+
from datetime import datetime
1011
from enum import Enum
1112

1213
from sqlmesh.core.dialect import extend_sqlglot
@@ -88,6 +89,9 @@ def is_notebook(self) -> bool:
8889
pass
8990

9091

92+
LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s (%(filename)s:%(lineno)d)"
93+
94+
9195
# SO: https://stackoverflow.com/questions/384076/how-can-i-color-python-logging-output
9296
class CustomFormatter(logging.Formatter):
9397
"""Custom logging formatter."""
@@ -97,14 +101,13 @@ class CustomFormatter(logging.Formatter):
97101
red = "\x1b[31;20m"
98102
bold_red = "\x1b[31;1m"
99103
reset = "\x1b[0m"
100-
log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s (%(filename)s:%(lineno)d)"
101104

102105
FORMATS = {
103-
logging.DEBUG: grey + log_format + reset,
104-
logging.INFO: grey + log_format + reset,
105-
logging.WARNING: yellow + log_format + reset,
106-
logging.ERROR: red + log_format + reset,
107-
logging.CRITICAL: bold_red + log_format + reset,
106+
logging.DEBUG: grey + LOG_FORMAT + reset,
107+
logging.INFO: grey + LOG_FORMAT + reset,
108+
logging.WARNING: yellow + LOG_FORMAT + reset,
109+
logging.ERROR: red + LOG_FORMAT + reset,
110+
logging.CRITICAL: bold_red + LOG_FORMAT + reset,
108111
}
109112

110113
def format(self, record: logging.LogRecord) -> str:
@@ -113,7 +116,7 @@ def format(self, record: logging.LogRecord) -> str:
113116
return formatter.format(record)
114117

115118

116-
def enable_logging(level: t.Optional[int] = None) -> None:
119+
def enable_logging(level: t.Optional[int] = None, write_to_file: bool = False) -> None:
117120
"""Enable logging to send to stdout and color different levels"""
118121
level = level or (logging.DEBUG if debug_mode_enabled() else logging.INFO)
119122
logger = logging.getLogger()
@@ -123,3 +126,10 @@ def enable_logging(level: t.Optional[int] = None) -> None:
123126
handler.setLevel(level)
124127
handler.setFormatter(CustomFormatter())
125128
logger.addHandler(handler)
129+
130+
if write_to_file:
131+
filename = f"sqlmesh_{datetime.now().strftime('%Y_%m_%d_%H_%M_%S')}.log"
132+
file_handler = logging.FileHandler(filename, mode="w", encoding="utf-8")
133+
file_handler.setLevel(level)
134+
file_handler.setFormatter(logging.Formatter(LOG_FORMAT))
135+
logger.addHandler(file_handler)

sqlmesh/cli/__init__.py

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import logging
12
import typing as t
23
from functools import wraps
34

@@ -11,17 +12,35 @@
1112
DECORATOR_RETURN_TYPE = t.TypeVar("DECORATOR_RETURN_TYPE")
1213

1314

15+
logger = logging.getLogger(__name__)
16+
17+
1418
def error_handler(
1519
func: t.Callable[..., DECORATOR_RETURN_TYPE]
1620
) -> t.Callable[..., DECORATOR_RETURN_TYPE]:
1721
@wraps(func)
1822
def wrapper(*args: t.Any, **kwargs: t.Any) -> DECORATOR_RETURN_TYPE:
19-
try:
20-
return func(*args, **kwargs)
21-
except NodeExecutionFailedError as ex:
22-
cause = ex.__cause__
23-
raise click.ClickException(f"Failed processing {ex.node}. {cause}")
24-
except (SQLMeshError, SqlglotError, ValueError) as ex:
25-
raise click.ClickException(str(ex))
26-
27-
return wrapper if not debug_mode_enabled() else func
23+
handler = _debug_exception_handler if debug_mode_enabled() else _default_exception_handler
24+
return handler(lambda: func(*args, **kwargs))
25+
26+
return wrapper
27+
28+
29+
def _default_exception_handler(
30+
func: t.Callable[[], DECORATOR_RETURN_TYPE]
31+
) -> DECORATOR_RETURN_TYPE:
32+
try:
33+
return func()
34+
except NodeExecutionFailedError as ex:
35+
cause = ex.__cause__
36+
raise click.ClickException(f"Failed processing {ex.node}. {cause}")
37+
except (SQLMeshError, SqlglotError, ValueError) as ex:
38+
raise click.ClickException(str(ex))
39+
40+
41+
def _debug_exception_handler(func: t.Callable[[], DECORATOR_RETURN_TYPE]) -> DECORATOR_RETURN_TYPE:
42+
try:
43+
return func()
44+
except Exception:
45+
logger.exception("Unhandled exception")
46+
raise

sqlmesh/cli/main.py

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@
1212
from sqlmesh.cli import options as opt
1313
from sqlmesh.cli.example_project import ProjectTemplate, init_example_project
1414
from sqlmesh.core.context import Context
15-
from sqlmesh.utils import debug_mode_enabled
15+
from sqlmesh.utils import debug_mode_enabled, enable_debug_mode
1616
from sqlmesh.utils.date import TimeLike
1717
from sqlmesh.utils.errors import MissingDependencyError
1818

19+
logger = logging.getLogger(__name__)
20+
1921

2022
def _sqlmesh_version() -> str:
2123
try:
@@ -40,6 +42,11 @@ def _sqlmesh_version() -> str:
4042
is_flag=True,
4143
help="Ignore warnings.",
4244
)
45+
@click.option(
46+
"--debug",
47+
is_flag=True,
48+
help="Enable debug mode.",
49+
)
4350
@click.pass_context
4451
@error_handler
4552
def cli(
@@ -48,6 +55,7 @@ def cli(
4855
config: t.Optional[str] = None,
4956
gateway: t.Optional[str] = None,
5057
ignore_warnings: bool = False,
58+
debug: bool = False,
5159
) -> None:
5260
"""SQLMesh command line tool."""
5361
if ctx.invoked_subcommand == "version":
@@ -67,25 +75,33 @@ def cli(
6775
if "--help" in sys.argv:
6876
return
6977

70-
if debug_mode_enabled():
78+
debug = debug or debug_mode_enabled()
79+
if debug:
7180
import faulthandler
7281
import signal
7382

83+
enable_debug_mode()
84+
7485
# Enable threadumps.
7586
faulthandler.enable()
7687
# Windows doesn't support register so we check for it here
7788
if hasattr(faulthandler, "register"):
7889
faulthandler.register(signal.SIGUSR1.value)
79-
enable_logging(level=logging.DEBUG)
90+
enable_logging(level=logging.DEBUG, write_to_file=True)
8091
elif ignore_warnings:
8192
logging.getLogger().setLevel(logging.ERROR)
8293

83-
context = Context(
84-
paths=paths,
85-
config=config,
86-
gateway=gateway,
87-
load=load,
88-
)
94+
try:
95+
context = Context(
96+
paths=paths,
97+
config=config,
98+
gateway=gateway,
99+
load=load,
100+
)
101+
except Exception:
102+
if debug:
103+
logger.exception("Failed to initialize SQLMesh context")
104+
raise
89105

90106
if load and not context.models:
91107
raise click.ClickException(

sqlmesh/utils/__init__.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,16 @@ def str_to_bool(s: t.Optional[str]) -> bool:
190190
return s.lower() in ("true", "1", "t", "y", "yes", "on")
191191

192192

193+
_debug_mode_enabled: bool = False
194+
195+
196+
def enable_debug_mode() -> None:
197+
global _debug_mode_enabled
198+
_debug_mode_enabled = True
199+
200+
193201
def debug_mode_enabled() -> bool:
194-
return str_to_bool(os.environ.get("SQLMESH_DEBUG"))
202+
return _debug_mode_enabled or str_to_bool(os.environ.get("SQLMESH_DEBUG"))
195203

196204

197205
def ttl_cache(ttl: int = 60, maxsize: int = 128000) -> t.Callable:

0 commit comments

Comments
 (0)