diff --git a/openbb_terminal/core/log/generation/settings_logger.py b/openbb_terminal/core/log/generation/settings_logger.py index 4e243289348..42db57d77a9 100644 --- a/openbb_terminal/core/log/generation/settings_logger.py +++ b/openbb_terminal/core/log/generation/settings_logger.py @@ -15,6 +15,18 @@ from openbb_terminal.core.log.generation.path_tracking_file_handler import ( ) +SENSITIVE_WORDS = [ + "API", + "DG_", + "KEY", + "PASSWORD", + "SECRET", + "TOKEN", + "USER", + "USERNAME", + "ACCOUNT", +] + logger = logging.getLogger(__name__) @@ -63,8 +75,7 @@ def log_config_terminal() -> None: not item.startswith("__") and not isinstance(prop, FunctionType) and not isinstance(prop, ModuleType) - and "API" not in item - and "DG_" not in item + and not any(substring in item for substring in SENSITIVE_WORDS) ): config_terminal_dict[item] = str(prop) diff --git a/openbb_terminal/decorators.py b/openbb_terminal/decorators.py index 9a474b68084..ffd97adf53b 100644 --- a/openbb_terminal/decorators.py +++ b/openbb_terminal/decorators.py @@ -4,10 +4,13 @@ import functools import logging import os import json +import inspect +from typing import Callable import pandas as pd from openbb_terminal import feature_flags as obbff from openbb_terminal.rich_config import console # pragma: allowlist secret +from openbb_terminal.core.log.generation.settings_logger import log_keys logger = logging.getLogger(__name__) @@ -120,7 +123,7 @@ def disable_check_api(): obbff.ENABLE_CHECK_API = False -def sdk_arg_logger(func=None, log=None, virtual_path: str = ""): +def sdk_arg_logger(func=None, log=None, virtual_path: str = "", chart: bool = False): """ Wrap function to add the function args to the log entry when using the SDK. """ @@ -130,10 +133,13 @@ def sdk_arg_logger(func=None, log=None, virtual_path: str = ""): try: value = func(*args, **kwargs) - logging_info = {"INPUT": {"args": args, "kwargs": kwargs}} + merged_args = merge_function_args(func, args, kwargs) + merged_args = sdk_remove_key_and_log_state(func.__module__, merged_args) - if virtual_path: - logging_info["VIRTUAL_PATH"] = virtual_path + logging_info = {} + logging_info["INPUT"] = merged_args + logging_info["VIRTUAL_PATH"] = virtual_path + logging_info["CHART"] = chart log.info( f"{json.dumps(logging_info)}", @@ -146,3 +152,60 @@ def sdk_arg_logger(func=None, log=None, virtual_path: str = ""): raise return wrapper_decorator + + +def merge_function_args(func: Callable, args: tuple, kwargs: dict) -> dict: + """ + Merge user input args and kwargs with signature defaults into a dictionary. + + Parameters + ---------- + + func : Callable + Function to get the args from + args : tuple + Positional args + kwargs : dict + Keyword args + + Returns + ------- + dict + Merged user args and signature defaults + """ + + sig = inspect.signature(func) + sig_args = { + param.name: param.default + for param in sig.parameters.values() + if param.default is not inspect.Parameter.empty + } + # merge args with sig_args + sig_args.update(dict(zip(sig.parameters, args))) + # add kwargs elements to sig_args + sig_args.update(kwargs) + return sig_args + + +def sdk_remove_key_and_log_state(func_module: str, function_args: dict) -> dict: + """ + Remove API key from the function args and log state of keys. + + Parameters + ---------- + func_module : str + Module of the function + function_args : dict + Function args + + Returns + ------- + dict + Function args with API key removed + """ + + if func_module == "openbb_terminal.keys_model": + # remove key if defined + function_args.pop("key", None) + log_keys() + return function_args diff --git a/openbb_terminal/sdk.py b/openbb_terminal/sdk.py index 0bb1f11e90a..8b654d37dfa 100644 --- a/openbb_terminal/sdk.py +++ b/openbb_terminal/sdk.py @@ -9,8 +9,11 @@ import functools import importlib from typing import Optional, Callable, List import logging +from traceback import format_stack from openbb_terminal.rich_config import console +from openbb_terminal.reports.reports_controller import ReportController +from openbb_terminal.dashboards.dashboards_controller import DashboardsController try: import darts # pyright: reportMissingImports=false @@ -37,6 +40,13 @@ from .cryptocurrency.due_diligence.pycoingecko_model import Coin logger = logging.getLogger(__name__) + +SUPPRESS_LOGGING_CLASSES = { + ReportController: "ReportController", + DashboardsController: "DashboardsController", +} + + functions = { "alt.covid.slopes": { "model": "openbb_terminal.alternative.covid.covid_model.get_case_slopes", @@ -2039,7 +2049,10 @@ if forecasting: def copy_func( - f: Callable, logging_decorator: bool = False, virtual_path: str = "" + f: Callable, + logging_decorator: bool = False, + virtual_path: str = "", + chart: bool = False, ) -> Callable: """Copy the contents and attributes of the entered function. @@ -2051,6 +2064,10 @@ def copy_func( Function to be copied logging_decorator: bool If True, the copied function will be decorated with the logging decorator + virtual_path: str + If not empty, virtual path will be added to the logging + chart: bool + If True, the copied function will log info on whether it is a view (chart) Returns ------- @@ -2073,7 +2090,7 @@ def copy_func( if logging_decorator: log_name = logging.getLogger(g.__module__) - g = sdk_arg_logger(func=g, log=log_name, virtual_path=virtual_path) + g = sdk_arg_logger(func=g, log=log_name, virtual_path=virtual_path, chart=chart) g = log_start_end(func=g, log=log_name) return g @@ -2131,6 +2148,29 @@ def change_docstring(api_callable, model: Callable, view=None): return api_callable +def check_suppress_logging(suppress_dict: dict) -> bool: + """ + Check if logging should be suppressed. + If the dict contains a value that is found in the stack trace, + the logging should be suppressed. + + Parameters + ---------- + supress_dict: dict + Dictionary with values that trigger log suppression + + Returns + ------- + bool + True if logging shall be suppressed, False otherwise + """ + for _, value in suppress_dict.items(): + for ele in format_stack(): + if value in ele: + return True + return False + + class FunctionFactory: """The SDK Function Factory, which creates the callable instance.""" @@ -2155,7 +2195,7 @@ class FunctionFactory: self.view = None if view is not None: self.view = copy_func( - f=view, logging_decorator=True, virtual_path=virtual_path + f=view, logging_decorator=True, virtual_path=virtual_path, chart=True ) def api_callable(self, *args, **kwargs): @@ -2215,13 +2255,15 @@ class MainMenu: class Loader: """The Loader class.""" - def __init__(self, funcs: dict): + def __init__(self, funcs: dict, suppress_logging: bool = False): print( "WARNING! Breaking changes incoming! Especially avoid using kwargs, since some of them will change.\n" "For more information see the official documentation at: https://openbb-finance.github.io/OpenBBTerminal/sdk/" ) + + self.__suppress_logging = suppress_logging self.__function_map = self.build_function_map(funcs=funcs) - self.__initialize_logging() + self.__check_initialize_logging() self.load_menus() def __call__(self): @@ -2298,6 +2340,10 @@ class Loader: self.openbb = main_menu + def __check_initialize_logging(self): + if not self.__suppress_logging: + self.__initialize_logging() + @staticmethod def __load_module(module_path: str) -> Optional[types.ModuleType]: """Load a module from a path. @@ -2409,4 +2455,7 @@ class Loader: # TO USE THE SDK DIRECTLY JUST IMPORT IT: # from openbb_terminal.sdk import openbb (or: from openbb_terminal.sdk import openbb as sdk) -openbb = Loader(funcs=functions).openbb +openbb = Loader( + funcs=functions, + suppress_logging=check_suppress_logging(suppress_dict=SUPPRESS_LOGGING_CLASSES), +).openbb