33import logging
44import os
55import pathlib
6+ from logging import StreamHandler
67from logging .handlers import TimedRotatingFileHandler
78from typing import Dict , Optional
89
2324class CLIConfiguration :
2425 """Configuration object used for the MetricFlow CLI."""
2526
27+ LOG_FILE_NAME = "metricflow.log"
2628 DBT_PROFILES_DIR_ENV_VAR_NAME = "DBT_PROFILES_DIR"
2729 DBT_PROJECT_DIR_ENV_VAR_NAME = "DBT_PROJECT_DIR"
2830
31+ # Message to log when logging has been set up. Useful for checking the logging in tests.
32+ LOG_SETUP_MESSAGE = "Set up MF CLI logging"
33+
2934 def __init__ (self ) -> None : # noqa: D107
3035 self .verbose = False
3136 self ._dbt_project_metadata : Optional [dbtProjectMetadata ] = None
@@ -121,28 +126,44 @@ def _configure_logging(self, log_file_path: pathlib.Path) -> None:
121126 we can swap this out and return to full lazy loading for any context attributes that are slow
122127 to initialize.
123128 """
124- logger .debug (LazyFormat ("Setting up logging to rotating file" , log_file_path = self .log_file_path ))
129+ root_logger = logging .getLogger ()
130+ previous_root_logger_level = root_logger .level
131+ previous_root_logger_handlers = tuple (root_logger .handlers )
125132
126- log_format = "%(asctime)s %(levelname)s %(filename)s:%(lineno)d [%(threadName)s] - %(message)s"
127- logging .basicConfig (level = logging .INFO , format = log_format )
133+ # Show >= CRITICAL logs in the console
134+ stream_handler = StreamHandler ()
135+ stream_handler_log_level = logging .CRITICAL
136+ stream_handler .setLevel (stream_handler_log_level )
128137
138+ # Show >= INFO logs in the log file
139+ log_file_handler_log_level = logging .INFO
129140 log_file_handler = TimedRotatingFileHandler (
130141 filename = log_file_path ,
131142 # Rotate every day to a new file, keep 7 days worth.
132143 when = "D" ,
133144 interval = 1 ,
134145 backupCount = 7 ,
135146 )
147+ log_file_handler .setLevel (log_file_handler_log_level )
136148
137- formatter = logging .Formatter (fmt = log_format )
138- log_file_handler .setFormatter (formatter )
139- log_file_handler .setLevel (logging .INFO )
149+ root_logger_level = logging .INFO
150+ log_format = "%(asctime)s %(levelname)s %(filename)s:%(lineno)d [%(threadName)s] - %(message)s"
151+ # `logging.basicConfig` is a no-op without the `force=True` if any handlers have been added to the root logger.
152+ # Imported modules might add handlers, so set the flag to ensure that the above handlers are used.
153+ logging .basicConfig (
154+ level = root_logger_level , format = log_format , handlers = [stream_handler , log_file_handler ], force = True
155+ )
140156
141- root_logger = logging .getLogger ()
142- # StreamHandler to the console would have been setup by logging.basicConfig
143- for handler in root_logger .handlers :
144- handler .setLevel (logging .CRITICAL )
145- root_logger .addHandler (log_file_handler )
157+ logger .info (
158+ LazyFormat (
159+ CLIConfiguration .LOG_SETUP_MESSAGE ,
160+ root_logger_level = logging .getLevelName (root_logger_level ),
161+ root_logger_handlers = root_logger .handlers ,
162+ previous_root_logger_level = logging .getLevelName (previous_root_logger_level ),
163+ previous_root_logger_handlers = previous_root_logger_handlers ,
164+ log_format = log_format ,
165+ )
166+ )
146167
147168 @property
148169 def dbt_project_metadata (self ) -> dbtProjectMetadata :
@@ -162,7 +183,7 @@ def log_file_path(self) -> pathlib.Path:
162183 # The dbt Project.log_path attribute is currently sourced from the final runtime config value accessible
163184 # through the CLI state flags. As such, it will deviate from the default based on the DBT_LOG_PATH environment
164185 # variable. Should this behavior change, we will need to update this call.
165- return pathlib .Path (self .dbt_project_metadata .project .log_path , "metricflow.log" )
186+ return pathlib .Path (self .dbt_project_metadata .project .log_path , CLIConfiguration . LOG_FILE_NAME )
166187
167188 @property
168189 def sql_client (self ) -> SqlClient :
0 commit comments