# --------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # --------------------------------------------------------- # pylint: disable=protected-access import http.client import logging import os import sys import time import traceback from collections import namedtuple from typing import List, Optional LOG_FILE = os.path.abspath("azureml.log") LOG_FORMAT = "%(asctime)s|%(name)s|%(levelname)s|%(message)s" INTERESTING_NAMESPACES = ["azureml", "msrest.http_logger", "urllib2", "azure"] module_logger = logging.getLogger(__name__) separator = "\n==================\n" ConnectionInfo = namedtuple("ConnectionInfo", ["host", "port", "hasSocket"]) def stack_info() -> list: main_stack = [] for threadId, stack in sys._current_frames().items(): for filename, lineno, name, line in traceback.extract_stack(stack): call = line.strip() if line is not None else None main_stack.append( { "ThreadID": threadId, "File": filename, "Line": lineno, "Name": name, "Call": call, } ) return main_stack def connection_info(gc_objects: list) -> List[ConnectionInfo]: connections = [obj for obj in gc_objects if isinstance(obj, http.client.HTTPConnection)] return [ConnectionInfo(host=c.host, port=c.port, hasSocket=c.sock is not None) for c in connections] # disable # pylint: disable=client-incorrect-naming-convention class diagnostic_log(object): """Directs debug logs to a specified file. :param log_path: A path with log file name. If None, a file named "azureml.log" is created. :type log_path: str :param namespaces: A list of namespaces to capture logs for. If None, the default is "azureml", "msrest.http_logger", "urllib2", and "azure". :type namespaces: builtin.list :param context_name: A name to identify the logging context. If None, the context of the calling stack frame is used. :type context_type: str """ def __init__( self, log_path: Optional[str] = None, namespaces: Optional[list] = None, context_name: Optional[str] = None ): self._namespaces = INTERESTING_NAMESPACES if namespaces is None else namespaces self._filename = LOG_FILE if log_path is None else log_path self._filename = os.path.abspath(self._filename) self._capturing = False if context_name is None: import inspect context_name = inspect.getouterframes(inspect.currentframe(), 2)[1].function self._context_name = context_name formatter = logging.Formatter(LOG_FORMAT) formatter.converter = time.gmtime file_handler = logging.FileHandler(filename=self._filename, encoding="utf-8") file_handler.setLevel(logging.DEBUG) file_handler.setFormatter(formatter) self._handler = file_handler def start_capture(self) -> None: """Start the capture of debug logs.""" if self._capturing: module_logger.warning("Debug logs are already enabled at %s", self._filename) return print("Debug logs are being sent to {}".format(self._filename)) for namespace in self._namespaces: module_logger.debug("Adding [%s] debug logs to this file", namespace) n_logger = logging.getLogger(namespace) n_logger.setLevel(logging.DEBUG) n_logger.addHandler(self._handler) # We do the below for strange environments like Revo + Jupyter # where root handlers appear to already be set. # We don't want to spew to those consoles with DEBUG emissions n_logger.propagate = False module_logger.info( "\n\n********** STARTING CAPTURE FOR [%s] **********\n\n", self._context_name, ) self._capturing = True def stop_capture(self) -> None: """Stop the capture of debug logs.""" if not self._capturing: module_logger.warning("Debug logs are already disabled.") return module_logger.info( "\n\n********** STOPPING CAPTURE FOR [%s] **********\n\n", self._context_name, ) print("Disabling log capture. Resulting file is at {}".format(self._filename)) for namespace in self._namespaces: module_logger.debug("Removing [%s] debug logs to this file", namespace) n_logger = logging.getLogger(namespace) n_logger.removeHandler(self._handler) self._capturing = False def __enter__(self) -> None: self.start_capture() def __exit__(self, exc_type, exc_value, exc_traceback) -> None: self.stop_capture() _debugging_enabled = False def debug_sdk() -> None: global _debugging_enabled # pylint: disable=global-statement if _debugging_enabled: module_logger.warning("Debug logs are already enabled at %s", LOG_FILE) return formatter = logging.Formatter(LOG_FORMAT) formatter.converter = time.gmtime file_handler = logging.FileHandler(filename=LOG_FILE, encoding="utf-8") file_handler.setLevel(logging.DEBUG) file_handler.setFormatter(formatter) module_logger.info("Debug logs are being sent to %s", LOG_FILE) for namespace in INTERESTING_NAMESPACES: module_logger.debug("Adding [%s] debug logs to this file", namespace) n_logger = logging.getLogger(namespace) n_logger.setLevel(logging.DEBUG) n_logger.addHandler(file_handler) # We do the below for strange environments like Revo + Jupyter # where root handlers appear to already be set. # We don't want to spew to those consoles with DEBUG emissions n_logger.propagate = 0 # type: ignore[assignment] _debugging_enabled = True