From 509ea4d656fa92174f6305274c5103da21a49932 Mon Sep 17 00:00:00 2001 From: Matt Holder Date: Mon, 4 Dec 2023 09:38:05 -0500 Subject: [PATCH 01/32] logs now send to local elasticsearch instance --- bonfire/bonfire.py | 5 +++++ bonfire/config.py | 7 +++++++ bonfire/elastic_logging.py | 29 +++++++++++++++++++++++++++++ bonfire/namespaces.py | 5 +++++ bonfire/processor.py | 5 ++++- 5 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 bonfire/elastic_logging.py diff --git a/bonfire/bonfire.py b/bonfire/bonfire.py index 746c9237..fe12fc69 100755 --- a/bonfire/bonfire.py +++ b/bonfire/bonfire.py @@ -50,8 +50,13 @@ validate_time_string, merge_app_configs, ) +from bonfire.elastic_logging import AsyncElasticsearchHandler + log = logging.getLogger(__name__) +if not any(isinstance(h, AsyncElasticsearchHandler) for h in log.handlers): + es_handler = AsyncElasticsearchHandler(conf.ELASTICSEARCH_HOST) + log.addHandler(es_handler) APP_SRE_SRC = "appsre" FILE_SRC = "file" diff --git a/bonfire/config.py b/bonfire/config.py index f756ec30..c73054e3 100644 --- a/bonfire/config.py +++ b/bonfire/config.py @@ -8,6 +8,7 @@ from pkg_resources import resource_filename from bonfire.utils import FatalError, get_config_path, load_file +from bonfire.elastic_logging import AsyncElasticsearchHandler log = logging.getLogger(__name__) @@ -30,6 +31,8 @@ DEFAULT_GRAPHQL_URL = "https://app-interface.apps.appsrep05ue1.zqxk.p1.openshiftapps.com/graphql" +DEFAULT_ELASTICSEARCH_HOST = "https://localhost:9200/search-bonfire/_doc" + ENV_FILE = str(DEFAULT_ENV_PATH.absolute()) if DEFAULT_ENV_PATH.exists() else "" load_dotenv(ENV_FILE) @@ -63,6 +66,8 @@ os.getenv("BONFIRE_DEFAULT_FALLBACK_REF_ENV", "insights-stage") ) +ELASTICSEARCH_HOST = os.getenv("ELASTICSEARCH_HOST", DEFAULT_ELASTICSEARCH_HOST) +ELASTICSEARCH_APIKEY = os.getenv("ELASTICSEARCH_APIKEY") DEFAULT_FRONTEND_DEPENDENCIES = ( "chrome-service", @@ -77,6 +82,8 @@ "unleash-proxy", ) +es_handler = AsyncElasticsearchHandler(ELASTICSEARCH_HOST) +log.addHandler(es_handler) def _get_auto_added_frontend_dependencies(): env_var = os.getenv("BONFIRE_FRONTEND_DEPENDENCIES") diff --git a/bonfire/elastic_logging.py b/bonfire/elastic_logging.py new file mode 100644 index 00000000..f780bfa7 --- /dev/null +++ b/bonfire/elastic_logging.py @@ -0,0 +1,29 @@ +import logging +import json +import requests +import datetime +from concurrent.futures import ThreadPoolExecutor + +import bonfire.config as conf + +class AsyncElasticsearchHandler(logging.Handler): + def __init__(self, es_url): + super().__init__() + self.es_url = es_url + self.executor = ThreadPoolExecutor(max_workers=10) + + def emit(self, record): + log_entry = self.format(record) + self.executor.submit(self.send_to_es, log_entry) + + def send_to_es(self, log_entry): + # Convert log_entry to JSON and send to Elasticsearch + try: + headers = {"Authorization": conf.ELASTICSEARCH_APIKEY, + "Content-Type": "application/json"} + log = {"timestamp": datetime.datetime.now().isoformat(), "message": log_entry} + response = requests.post(self.es_url, headers=headers, data=json.dumps(log), verify=False) + response.raise_for_status() + except Exception as e: + # Handle exceptions (e.g., network issues, Elasticsearch down) + print(f"ERROR: {response.status_code}") \ No newline at end of file diff --git a/bonfire/namespaces.py b/bonfire/namespaces.py index 2a4add34..85d101bf 100644 --- a/bonfire/namespaces.py +++ b/bonfire/namespaces.py @@ -16,9 +16,14 @@ ) from bonfire.processor import process_reservation from bonfire.utils import FatalError, hms_to_seconds +from bonfire.elastic_logging import AsyncElasticsearchHandler log = logging.getLogger(__name__) +if not any(isinstance(h, AsyncElasticsearchHandler) for h in log.handlers): + es_handler = AsyncElasticsearchHandler(conf.ELASTICSEARCH_HOST) + log.addHandler(es_handler) + TIME_FMT = "%Y-%m-%dT%H:%M:%SZ" diff --git a/bonfire/processor.py b/bonfire/processor.py index 6285a822..5c57527a 100644 --- a/bonfire/processor.py +++ b/bonfire/processor.py @@ -16,10 +16,13 @@ from bonfire.utils import AppOrComponentSelector, FatalError, RepoFile from bonfire.utils import get_clowdapp_dependencies from bonfire.utils import get_dependencies as utils_get_dependencies +from bonfire.elastic_logging import AsyncElasticsearchHandler log = logging.getLogger(__name__) - +if not any(isinstance(h, AsyncElasticsearchHandler) for h in log.handlers): + es_handler = AsyncElasticsearchHandler(conf.ELASTICSEARCH_HOST) + log.addHandler(es_handler) def _process_template(*args, **kwargs): # run process_template with prettier error handling From cdedad8fa0339d2badde9bf32272c95cc81f4e87 Mon Sep 17 00:00:00 2001 From: Matt Holder Date: Mon, 4 Dec 2023 10:46:22 -0500 Subject: [PATCH 02/32] remove logging handlers --- bonfire/bonfire.py | 6 +++--- bonfire/config.py | 4 ---- bonfire/namespaces.py | 4 ---- bonfire/processor.py | 4 ---- 4 files changed, 3 insertions(+), 15 deletions(-) diff --git a/bonfire/bonfire.py b/bonfire/bonfire.py index fe12fc69..71c2d2f0 100755 --- a/bonfire/bonfire.py +++ b/bonfire/bonfire.py @@ -54,9 +54,9 @@ log = logging.getLogger(__name__) -if not any(isinstance(h, AsyncElasticsearchHandler) for h in log.handlers): - es_handler = AsyncElasticsearchHandler(conf.ELASTICSEARCH_HOST) - log.addHandler(es_handler) +es_telemetry = logging.getLogger("elasticsearch") +es_handler = AsyncElasticsearchHandler(conf.ELASTICSEARCH_HOST) +es_telemetry.addHandler(es_handler) APP_SRE_SRC = "appsre" FILE_SRC = "file" diff --git a/bonfire/config.py b/bonfire/config.py index c73054e3..84037766 100644 --- a/bonfire/config.py +++ b/bonfire/config.py @@ -8,7 +8,6 @@ from pkg_resources import resource_filename from bonfire.utils import FatalError, get_config_path, load_file -from bonfire.elastic_logging import AsyncElasticsearchHandler log = logging.getLogger(__name__) @@ -82,9 +81,6 @@ "unleash-proxy", ) -es_handler = AsyncElasticsearchHandler(ELASTICSEARCH_HOST) -log.addHandler(es_handler) - def _get_auto_added_frontend_dependencies(): env_var = os.getenv("BONFIRE_FRONTEND_DEPENDENCIES") diff --git a/bonfire/namespaces.py b/bonfire/namespaces.py index 85d101bf..0d1194d7 100644 --- a/bonfire/namespaces.py +++ b/bonfire/namespaces.py @@ -16,13 +16,9 @@ ) from bonfire.processor import process_reservation from bonfire.utils import FatalError, hms_to_seconds -from bonfire.elastic_logging import AsyncElasticsearchHandler log = logging.getLogger(__name__) -if not any(isinstance(h, AsyncElasticsearchHandler) for h in log.handlers): - es_handler = AsyncElasticsearchHandler(conf.ELASTICSEARCH_HOST) - log.addHandler(es_handler) TIME_FMT = "%Y-%m-%dT%H:%M:%SZ" diff --git a/bonfire/processor.py b/bonfire/processor.py index 5c57527a..693ae44f 100644 --- a/bonfire/processor.py +++ b/bonfire/processor.py @@ -16,13 +16,9 @@ from bonfire.utils import AppOrComponentSelector, FatalError, RepoFile from bonfire.utils import get_clowdapp_dependencies from bonfire.utils import get_dependencies as utils_get_dependencies -from bonfire.elastic_logging import AsyncElasticsearchHandler log = logging.getLogger(__name__) -if not any(isinstance(h, AsyncElasticsearchHandler) for h in log.handlers): - es_handler = AsyncElasticsearchHandler(conf.ELASTICSEARCH_HOST) - log.addHandler(es_handler) def _process_template(*args, **kwargs): # run process_template with prettier error handling From 39755937a4c210a54c3adb9525484f5b44d39077 Mon Sep 17 00:00:00 2001 From: Matt Holder Date: Mon, 4 Dec 2023 10:59:42 -0500 Subject: [PATCH 03/32] report option usage via decorator --- bonfire/bonfire.py | 24 ++++++++++++++++++++++++ bonfire/elastic_logging.py | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/bonfire/bonfire.py b/bonfire/bonfire.py index 71c2d2f0..70aebaf2 100755 --- a/bonfire/bonfire.py +++ b/bonfire/bonfire.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 +import inspect import json import logging import sys @@ -86,6 +87,28 @@ def current_namespace_or_error(): return namespace +def option_usage_wrapper(func): + @wraps(func) + def wrapper(*args, **kwargs): + # Inspect the function signature + signature = inspect.signature(func) + bound_args = signature.bind(*args, **kwargs) + bound_args.apply_defaults() + + # Check each parameter for non-empty values + non_empty_params = {} + for option_name, option_value in bound_args.arguments.items(): + if option_value: + non_empty_params[option_name] = option_value + # Perform an action here for non-empty parameters + es_telemetry.info(f"option '{option_name}': {option_value}") + + # Call the original function with the provided arguments + return func(*args, **kwargs) + + return wrapper + + def click_exception_wrapper(command): def decorator(f): @wraps(f) @@ -812,6 +835,7 @@ def _list_namespaces(available, mine, output): @options(_ns_reserve_options) @options(_timeout_option) @click_exception_wrapper("namespace reserve") +@option_usage_wrapper def _cmd_namespace_reserve(name, requester, duration, pool, timeout, local, force): """Reserve an ephemeral namespace""" ns = _check_and_reserve_namespace(name, requester, duration, pool, timeout, local, force) diff --git a/bonfire/elastic_logging.py b/bonfire/elastic_logging.py index f780bfa7..918b39a2 100644 --- a/bonfire/elastic_logging.py +++ b/bonfire/elastic_logging.py @@ -26,4 +26,4 @@ def send_to_es(self, log_entry): response.raise_for_status() except Exception as e: # Handle exceptions (e.g., network issues, Elasticsearch down) - print(f"ERROR: {response.status_code}") \ No newline at end of file + print(f"ERROR: {response.status_code}") From b5132a373315a48883c22d3099f903aea101d937 Mon Sep 17 00:00:00 2001 From: Matt Holder Date: Mon, 4 Dec 2023 11:26:04 -0500 Subject: [PATCH 04/32] add decorator to commands that have options --- bonfire/bonfire.py | 57 ++++++++++++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/bonfire/bonfire.py b/bonfire/bonfire.py index 70aebaf2..6d2b4d4f 100755 --- a/bonfire/bonfire.py +++ b/bonfire/bonfire.py @@ -87,26 +87,30 @@ def current_namespace_or_error(): return namespace -def option_usage_wrapper(func): - @wraps(func) - def wrapper(*args, **kwargs): - # Inspect the function signature - signature = inspect.signature(func) - bound_args = signature.bind(*args, **kwargs) - bound_args.apply_defaults() - - # Check each parameter for non-empty values - non_empty_params = {} - for option_name, option_value in bound_args.arguments.items(): - if option_value: - non_empty_params[option_name] = option_value - # Perform an action here for non-empty parameters - es_telemetry.info(f"option '{option_name}': {option_value}") - - # Call the original function with the provided arguments - return func(*args, **kwargs) +def option_usage_wrapper(command): + def decorator(f): + @wraps(f) + def wrapper(*args, **kwargs): + # Inspect the function signature + signature = inspect.signature(f) + bound_args = signature.bind(*args, **kwargs) + bound_args.apply_defaults() + + # Check each parameter for non-empty values + non_empty_params = {} + for option_name, option_value in bound_args.arguments.items(): + if option_value: + non_empty_params[option_name] = option_value + # Perform an action here for non-empty parameters + + + es_telemetry.info(f"{command} called with options '{non_empty_params.keys()}'") + # Call the original function with the provided arguments + return f(*args, **kwargs) - return wrapper + return wrapper + + return decorator def click_exception_wrapper(command): @@ -793,6 +797,7 @@ def inner(func): @namespace.command("list") @options(_ns_list_options) +@option_usage_wrapper("namespace list") def _list_namespaces(available, mine, output): """Get list of ephemeral namespaces""" if not has_ns_operator(): @@ -835,7 +840,7 @@ def _list_namespaces(available, mine, output): @options(_ns_reserve_options) @options(_timeout_option) @click_exception_wrapper("namespace reserve") -@option_usage_wrapper +@option_usage_wrapper("namespace reserve") def _cmd_namespace_reserve(name, requester, duration, pool, timeout, local, force): """Reserve an ephemeral namespace""" ns = _check_and_reserve_namespace(name, requester, duration, pool, timeout, local, force) @@ -853,6 +858,7 @@ def _cmd_namespace_reserve(name, requester, duration, pool, timeout, local, forc ) @options([_local_option]) @click_exception_wrapper("namespace release") +@option_usage_wrapper("namespace release") def _cmd_namespace_release(namespace, force, local): """Remove reservation from an ephemeral namespace""" if not has_ns_operator(): @@ -882,6 +888,7 @@ def _cmd_namespace_release(namespace, force, local): ) @options([_local_option]) @click_exception_wrapper("namespace extend") +@option_usage_wrapper("namespace extend") def _cmd_namespace_extend(namespace, duration, local): """Extend a reservation of an ephemeral namespace""" if not has_ns_operator(): @@ -902,6 +909,7 @@ def _cmd_namespace_extend(namespace, duration, local): help="Only wait for DB resources owned by ClowdApps to be ready", ) @options(_timeout_option) +@option_usage_wrapper("namespace wait-on-resources") def _cmd_namespace_wait_on_resources(namespace, timeout, db_only): """Wait for rolled out resources to be ready in namespace""" if not namespace: @@ -915,6 +923,7 @@ def _cmd_namespace_wait_on_resources(namespace, timeout, db_only): @namespace.command("describe") @click.argument("namespace", required=False, type=str) +@option_usage_wrapper("namespace describe") def _describe_namespace(namespace): """Get current namespace info""" if not namespace: @@ -1057,6 +1066,7 @@ def _cmd_pool_types(): help="Namespace you intend to deploy to (default: none)", type=str, ) +@option_usage_wrapper("process") def _cmd_process( app_names, source, @@ -1242,6 +1252,7 @@ def _check_and_reserve_namespace(name, requester, duration, pool, timeout, local ) @options(_ns_reserve_options) @options(_timeout_option) +@option_usage_wrapper("deploy") def _cmd_config_deploy( app_names, source, @@ -1393,6 +1404,7 @@ def _process_clowdenv(target_namespace, quay_user, env_name, template_file, loca @main.command("process-env") @options(_clowdenv_process_options) +@option_usage_wrapper("process-env") def _cmd_process_clowdenv(namespace, quay_user, clowd_env, template_file, local): """Process ClowdEnv template and print output""" clowd_env_config = _process_clowdenv(namespace, quay_user, clowd_env, template_file, local) @@ -1416,6 +1428,7 @@ def _cmd_process_clowdenv(namespace, quay_user, clowd_env, template_file, local) @options(_ns_reserve_options) @options(_timeout_option) @click_exception_wrapper("deploy-env") +@option_usage_wrapper("deploy-env") def _cmd_deploy_clowdenv( namespace, quay_user, @@ -1461,6 +1474,7 @@ def _cmd_deploy_clowdenv( @main.command("process-iqe-cji") @options(_iqe_cji_process_options) +@option_usage_wrapper("process-iqe-cji") def _cmd_process_iqe_cji( clowd_app_name, debug, @@ -1513,6 +1527,7 @@ def _cmd_process_iqe_cji( @options(_ns_reserve_options) @options(_timeout_option) @click_exception_wrapper("deploy-iqe-cji") +@option_usage_wrapper("deploy-iqe-cji") def _cmd_deploy_iqe_cji( namespace, clowd_app_name, @@ -1612,6 +1627,7 @@ def _cmd_edit_default_config(path): help="List components contained within each app group", ) @apps.command("list") +@option_usage_wrapper("apps list") def _cmd_apps_list( source, local_config_path, @@ -1642,6 +1658,7 @@ def _cmd_apps_list( type=str, ) @apps.command("what-depends-on") +@option_usage_wrapper("apps what-depends-on") def _cmd_apps_what_depends_on( source, local_config_path, From 768c6f07394d481f27ed6443f9694713467a3c8b Mon Sep 17 00:00:00 2001 From: Matt Holder Date: Mon, 4 Dec 2023 13:08:25 -0500 Subject: [PATCH 05/32] reformatted messaged to be more readable --- bonfire/bonfire.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/bonfire/bonfire.py b/bonfire/bonfire.py index 6d2b4d4f..28906ab5 100755 --- a/bonfire/bonfire.py +++ b/bonfire/bonfire.py @@ -91,21 +91,19 @@ def option_usage_wrapper(command): def decorator(f): @wraps(f) def wrapper(*args, **kwargs): - # Inspect the function signature signature = inspect.signature(f) bound_args = signature.bind(*args, **kwargs) bound_args.apply_defaults() # Check each parameter for non-empty values - non_empty_params = {} + options_used = [] for option_name, option_value in bound_args.arguments.items(): if option_value: - non_empty_params[option_name] = option_value - # Perform an action here for non-empty parameters + options_used.append(option_name) - es_telemetry.info(f"{command} called with options '{non_empty_params.keys()}'") - # Call the original function with the provided arguments + es_telemetry.info(f"{command} called with options '{', '.join(options_used)}'") + return f(*args, **kwargs) return wrapper From a7a7b174648305dca08519c4c622556c90384b60 Mon Sep 17 00:00:00 2001 From: Matt Holder Date: Mon, 4 Dec 2023 14:40:57 -0500 Subject: [PATCH 06/32] only look for parameters --- bonfire/bonfire.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/bonfire/bonfire.py b/bonfire/bonfire.py index 28906ab5..3750f83b 100755 --- a/bonfire/bonfire.py +++ b/bonfire/bonfire.py @@ -91,15 +91,27 @@ def option_usage_wrapper(command): def decorator(f): @wraps(f) def wrapper(*args, **kwargs): - signature = inspect.signature(f) - bound_args = signature.bind(*args, **kwargs) - bound_args.apply_defaults() - - # Check each parameter for non-empty values + command = sys.argv[1] + options_used = [] - for option_name, option_value in bound_args.arguments.items(): - if option_value: - options_used.append(option_name) + is_parameter = False + for arg in sys.argv[2:]: + if is_parameter: + options_used.append(arg.split('=')[0]) + is_parameter = False + elif arg == '-p' or arg == '--set-parameter': + is_parameter = True + + # signature = inspect.signature(f) + # bound_args = signature.bind(*args, **kwargs) + # bound_args.apply_defaults() + + # # Check each parameter for non-empty values + # options_used = [] + # for option_name, option_value in bound_args.arguments.items(): + # if option_value: + # print(f"{option_value} | {option_name}") + # options_used.append(option_name) es_telemetry.info(f"{command} called with options '{', '.join(options_used)}'") From d181ffbbe7c542a240e7e4b3c521fcf1d467e062 Mon Sep 17 00:00:00 2001 From: Matt Holder Date: Tue, 5 Dec 2023 08:10:11 -0500 Subject: [PATCH 07/32] report transaction uuid, run status, and run time --- bonfire/bonfire.py | 19 +++++++++++++++---- bonfire/elastic_logging.py | 5 +++-- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/bonfire/bonfire.py b/bonfire/bonfire.py index 3750f83b..141e7fd2 100755 --- a/bonfire/bonfire.py +++ b/bonfire/bonfire.py @@ -6,6 +6,8 @@ import sys import warnings from functools import wraps +import uuid +import time import click from ocviapy import apply_config, get_current_namespace, StatusError @@ -56,7 +58,7 @@ log = logging.getLogger(__name__) es_telemetry = logging.getLogger("elasticsearch") -es_handler = AsyncElasticsearchHandler(conf.ELASTICSEARCH_HOST) +es_handler = AsyncElasticsearchHandler(conf.ELASTICSEARCH_HOST, str(uuid.uuid4())) es_telemetry.addHandler(es_handler) APP_SRE_SRC = "appsre" @@ -72,6 +74,7 @@ def _error(msg): + es_telemetry.info(f"ERROR: {msg}") click.echo(f"ERROR: {msg}", err=True) sys.exit(1) @@ -92,7 +95,7 @@ def decorator(f): @wraps(f) def wrapper(*args, **kwargs): command = sys.argv[1] - + options_used = [] is_parameter = False for arg in sys.argv[2:]: @@ -113,10 +116,18 @@ def wrapper(*args, **kwargs): # print(f"{option_value} | {option_name}") # options_used.append(option_name) + if options_used: + es_telemetry.info(f"{command} called with options '{', '.join(options_used)}'") + else: + es_telemetry.info(f"{command} called with no options'") + + start = time.time() + result = f(*args, **kwargs) + end = time.time() - es_telemetry.info(f"{command} called with options '{', '.join(options_used)}'") + es_telemetry.info(f"{command} completed in {end - start} seconds") - return f(*args, **kwargs) + return result return wrapper diff --git a/bonfire/elastic_logging.py b/bonfire/elastic_logging.py index 918b39a2..9f2bfd12 100644 --- a/bonfire/elastic_logging.py +++ b/bonfire/elastic_logging.py @@ -7,9 +7,10 @@ import bonfire.config as conf class AsyncElasticsearchHandler(logging.Handler): - def __init__(self, es_url): + def __init__(self, es_url, transaction_id): super().__init__() self.es_url = es_url + self.transaction_id = transaction_id self.executor = ThreadPoolExecutor(max_workers=10) def emit(self, record): @@ -21,7 +22,7 @@ def send_to_es(self, log_entry): try: headers = {"Authorization": conf.ELASTICSEARCH_APIKEY, "Content-Type": "application/json"} - log = {"timestamp": datetime.datetime.now().isoformat(), "message": log_entry} + log = {"uuid": self.transaction_id, "timestamp": datetime.datetime.now().isoformat(), "message": log_entry} response = requests.post(self.es_url, headers=headers, data=json.dumps(log), verify=False) response.raise_for_status() except Exception as e: From 21b2fa74cbbbcd2638e6eb758e6ee3c779e9f884 Mon Sep 17 00:00:00 2001 From: Matt Holder Date: Wed, 6 Dec 2023 08:22:35 -0500 Subject: [PATCH 08/32] updated log message --- bonfire/bonfire.py | 49 ++++++++++++++++++++++---------------- bonfire/elastic_logging.py | 22 ++++++++++++----- 2 files changed, 44 insertions(+), 27 deletions(-) diff --git a/bonfire/bonfire.py b/bonfire/bonfire.py index 141e7fd2..501d3faf 100755 --- a/bonfire/bonfire.py +++ b/bonfire/bonfire.py @@ -8,6 +8,7 @@ from functools import wraps import uuid import time +import datetime import click from ocviapy import apply_config, get_current_namespace, StatusError @@ -58,7 +59,7 @@ log = logging.getLogger(__name__) es_telemetry = logging.getLogger("elasticsearch") -es_handler = AsyncElasticsearchHandler(conf.ELASTICSEARCH_HOST, str(uuid.uuid4())) +es_handler = AsyncElasticsearchHandler(conf.ELASTICSEARCH_HOST) es_telemetry.addHandler(es_handler) APP_SRE_SRC = "appsre" @@ -74,7 +75,20 @@ def _error(msg): - es_telemetry.info(f"ERROR: {msg}") + if es_handler.start_time: + # failure path + log = { + "timestamp": datetime.datetime.now().isoformat(), + "duration_seconds": time.time() - es_handler.start_time, + "bot": conf.BONFIRE_BOT, + "error": msg, + "succeeded": False, + "command": es_handler.command, + "options": es_handler.options_used, + } + + es_telemetry.info(json.dumps(log)) + click.echo(f"ERROR: {msg}", err=True) sys.exit(1) @@ -94,7 +108,6 @@ def option_usage_wrapper(command): def decorator(f): @wraps(f) def wrapper(*args, **kwargs): - command = sys.argv[1] options_used = [] is_parameter = False @@ -105,27 +118,21 @@ def wrapper(*args, **kwargs): elif arg == '-p' or arg == '--set-parameter': is_parameter = True - # signature = inspect.signature(f) - # bound_args = signature.bind(*args, **kwargs) - # bound_args.apply_defaults() - - # # Check each parameter for non-empty values - # options_used = [] - # for option_name, option_value in bound_args.arguments.items(): - # if option_value: - # print(f"{option_value} | {option_name}") - # options_used.append(option_name) + es_handler.start_command_log(time.time(), command, options_used) - if options_used: - es_telemetry.info(f"{command} called with options '{', '.join(options_used)}'") - else: - es_telemetry.info(f"{command} called with no options'") - - start = time.time() result = f(*args, **kwargs) - end = time.time() - es_telemetry.info(f"{command} completed in {end - start} seconds") + # successful path + log = { + "timestamp": datetime.datetime.now().isoformat(), + "duration_seconds": time.time() - es_handler.start_time, + "bot": conf.BONFIRE_BOT, + "error": "", + "succeeded": True, + "command": es_handler.command, + "options": es_handler.options_used, + } + es_telemetry.info(json.dumps(log)) return result diff --git a/bonfire/elastic_logging.py b/bonfire/elastic_logging.py index 9f2bfd12..813cc7a0 100644 --- a/bonfire/elastic_logging.py +++ b/bonfire/elastic_logging.py @@ -1,5 +1,6 @@ import logging import json +import time import requests import datetime from concurrent.futures import ThreadPoolExecutor @@ -7,24 +8,33 @@ import bonfire.config as conf class AsyncElasticsearchHandler(logging.Handler): - def __init__(self, es_url, transaction_id): + def __init__(self, es_url): super().__init__() self.es_url = es_url - self.transaction_id = transaction_id self.executor = ThreadPoolExecutor(max_workers=10) def emit(self, record): log_entry = self.format(record) self.executor.submit(self.send_to_es, log_entry) + def start_command_log(self, start_time, command, options_used): + self.start_time = start_time + self.command = command + self.options_used = options_used + def send_to_es(self, log_entry): + # don't + if not self.start_time: + return + # Convert log_entry to JSON and send to Elasticsearch try: - headers = {"Authorization": conf.ELASTICSEARCH_APIKEY, + headers = {"Authorization": conf.ELASTICSEARCH_APIKEY, "Content-Type": "application/json"} - log = {"uuid": self.transaction_id, "timestamp": datetime.datetime.now().isoformat(), "message": log_entry} - response = requests.post(self.es_url, headers=headers, data=json.dumps(log), verify=False) + + response = requests.post(self.es_url, headers=headers, data=log_entry, timeout=0.1) response.raise_for_status() except Exception as e: # Handle exceptions (e.g., network issues, Elasticsearch down) - print(f"ERROR: {response.status_code}") + pass + From c26e0661b07779688803bd965717414f900bfc0d Mon Sep 17 00:00:00 2001 From: Matt Holder Date: Wed, 6 Dec 2023 08:26:01 -0500 Subject: [PATCH 09/32] enable users to opt out --- bonfire/bonfire.py | 3 +++ bonfire/config.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/bonfire/bonfire.py b/bonfire/bonfire.py index 501d3faf..1967b6a6 100755 --- a/bonfire/bonfire.py +++ b/bonfire/bonfire.py @@ -108,6 +108,9 @@ def option_usage_wrapper(command): def decorator(f): @wraps(f) def wrapper(*args, **kwargs): + # only gather telemetry if enabled and a bot run + if not conf.ENABLE_TELEMETRY and not conf.BONFIRE_BOT: + return f(*args, **kwargs) options_used = [] is_parameter = False diff --git a/bonfire/config.py b/bonfire/config.py index 84037766..87c64b74 100644 --- a/bonfire/config.py +++ b/bonfire/config.py @@ -31,6 +31,7 @@ DEFAULT_GRAPHQL_URL = "https://app-interface.apps.appsrep05ue1.zqxk.p1.openshiftapps.com/graphql" DEFAULT_ELASTICSEARCH_HOST = "https://localhost:9200/search-bonfire/_doc" +DEFAULT_ENABLE_TELEMETRY = True ENV_FILE = str(DEFAULT_ENV_PATH.absolute()) if DEFAULT_ENV_PATH.exists() else "" load_dotenv(ENV_FILE) @@ -67,6 +68,7 @@ ELASTICSEARCH_HOST = os.getenv("ELASTICSEARCH_HOST", DEFAULT_ELASTICSEARCH_HOST) ELASTICSEARCH_APIKEY = os.getenv("ELASTICSEARCH_APIKEY") +ENABLE_TELEMETRY = os.getenv("ENABLE_TELEMETRY", DEFAULT_ENABLE_TELEMETRY) DEFAULT_FRONTEND_DEPENDENCIES = ( "chrome-service", From 66af2a4c4959c7f0b2bd91ee10992ebc2a7887af Mon Sep 17 00:00:00 2001 From: Matt Holder Date: Wed, 6 Dec 2023 08:43:57 -0500 Subject: [PATCH 10/32] correct opt out logic --- bonfire/bonfire.py | 3 --- bonfire/config.py | 2 +- bonfire/elastic_logging.py | 7 ++----- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/bonfire/bonfire.py b/bonfire/bonfire.py index 1967b6a6..c244bc91 100755 --- a/bonfire/bonfire.py +++ b/bonfire/bonfire.py @@ -109,9 +109,6 @@ def decorator(f): @wraps(f) def wrapper(*args, **kwargs): # only gather telemetry if enabled and a bot run - if not conf.ENABLE_TELEMETRY and not conf.BONFIRE_BOT: - return f(*args, **kwargs) - options_used = [] is_parameter = False for arg in sys.argv[2:]: diff --git a/bonfire/config.py b/bonfire/config.py index 87c64b74..a1304875 100644 --- a/bonfire/config.py +++ b/bonfire/config.py @@ -31,7 +31,7 @@ DEFAULT_GRAPHQL_URL = "https://app-interface.apps.appsrep05ue1.zqxk.p1.openshiftapps.com/graphql" DEFAULT_ELASTICSEARCH_HOST = "https://localhost:9200/search-bonfire/_doc" -DEFAULT_ENABLE_TELEMETRY = True +DEFAULT_ENABLE_TELEMETRY = "true" ENV_FILE = str(DEFAULT_ENV_PATH.absolute()) if DEFAULT_ENV_PATH.exists() else "" load_dotenv(ENV_FILE) diff --git a/bonfire/elastic_logging.py b/bonfire/elastic_logging.py index 813cc7a0..ff6434d2 100644 --- a/bonfire/elastic_logging.py +++ b/bonfire/elastic_logging.py @@ -15,7 +15,8 @@ def __init__(self, es_url): def emit(self, record): log_entry = self.format(record) - self.executor.submit(self.send_to_es, log_entry) + if conf.ENABLE_TELEMETRY == 'true' and conf.BONFIRE_BOT: + self.executor.submit(self.send_to_es, log_entry) def start_command_log(self, start_time, command, options_used): self.start_time = start_time @@ -23,10 +24,6 @@ def start_command_log(self, start_time, command, options_used): self.options_used = options_used def send_to_es(self, log_entry): - # don't - if not self.start_time: - return - # Convert log_entry to JSON and send to Elasticsearch try: headers = {"Authorization": conf.ELASTICSEARCH_APIKEY, From 1755c7f8f906cb2fc63d52c60e3652357ee1b3f5 Mon Sep 17 00:00:00 2001 From: Matt Holder Date: Wed, 6 Dec 2023 09:07:09 -0500 Subject: [PATCH 11/32] linting --- bonfire/bonfire.py | 8 +++----- bonfire/elastic_logging.py | 14 +++++++------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/bonfire/bonfire.py b/bonfire/bonfire.py index c244bc91..1298354d 100755 --- a/bonfire/bonfire.py +++ b/bonfire/bonfire.py @@ -1,14 +1,12 @@ #!/usr/bin/env python3 -import inspect +import datetime import json import logging import sys +import time import warnings from functools import wraps -import uuid -import time -import datetime import click from ocviapy import apply_config, get_current_namespace, StatusError @@ -16,6 +14,7 @@ from wait_for import TimedOutError import bonfire.config as conf +from bonfire.elastic_logging import AsyncElasticsearchHandler from bonfire.local import get_local_apps, get_appsfile_apps from bonfire.utils import AppOrComponentSelector, RepoFile, SYNTAX_ERR from bonfire.namespaces import ( @@ -54,7 +53,6 @@ validate_time_string, merge_app_configs, ) -from bonfire.elastic_logging import AsyncElasticsearchHandler log = logging.getLogger(__name__) diff --git a/bonfire/elastic_logging.py b/bonfire/elastic_logging.py index ff6434d2..0f14ae40 100644 --- a/bonfire/elastic_logging.py +++ b/bonfire/elastic_logging.py @@ -1,8 +1,5 @@ import logging -import json -import time import requests -import datetime from concurrent.futures import ThreadPoolExecutor import bonfire.config as conf @@ -13,25 +10,28 @@ def __init__(self, es_url): self.es_url = es_url self.executor = ThreadPoolExecutor(max_workers=10) + def emit(self, record): log_entry = self.format(record) if conf.ENABLE_TELEMETRY == 'true' and conf.BONFIRE_BOT: self.executor.submit(self.send_to_es, log_entry) + def start_command_log(self, start_time, command, options_used): self.start_time = start_time self.command = command self.options_used = options_used - + + def send_to_es(self, log_entry): # Convert log_entry to JSON and send to Elasticsearch try: - headers = {"Authorization": conf.ELASTICSEARCH_APIKEY, + headers = {"Authorization": conf.ELASTICSEARCH_APIKEY, "Content-Type": "application/json"} - + response = requests.post(self.es_url, headers=headers, data=log_entry, timeout=0.1) response.raise_for_status() except Exception as e: # Handle exceptions (e.g., network issues, Elasticsearch down) + print(f"Error sending data to elasticsearch: {e}") pass - From 8379f13d78012dfa895fc45b151c4de2e6b32209 Mon Sep 17 00:00:00 2001 From: Matt Holder Date: Wed, 6 Dec 2023 09:09:40 -0500 Subject: [PATCH 12/32] more linting --- bonfire/bonfire.py | 4 ++-- bonfire/config.py | 1 + bonfire/elastic_logging.py | 3 --- bonfire/processor.py | 1 + 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/bonfire/bonfire.py b/bonfire/bonfire.py index 1298354d..cfab48bc 100755 --- a/bonfire/bonfire.py +++ b/bonfire/bonfire.py @@ -119,7 +119,7 @@ def wrapper(*args, **kwargs): es_handler.start_command_log(time.time(), command, options_used) result = f(*args, **kwargs) - + # successful path log = { "timestamp": datetime.datetime.now().isoformat(), @@ -135,7 +135,7 @@ def wrapper(*args, **kwargs): return result return wrapper - + return decorator diff --git a/bonfire/config.py b/bonfire/config.py index a1304875..a19ed567 100644 --- a/bonfire/config.py +++ b/bonfire/config.py @@ -83,6 +83,7 @@ "unleash-proxy", ) + def _get_auto_added_frontend_dependencies(): env_var = os.getenv("BONFIRE_FRONTEND_DEPENDENCIES") diff --git a/bonfire/elastic_logging.py b/bonfire/elastic_logging.py index 0f14ae40..8fdf5957 100644 --- a/bonfire/elastic_logging.py +++ b/bonfire/elastic_logging.py @@ -10,19 +10,16 @@ def __init__(self, es_url): self.es_url = es_url self.executor = ThreadPoolExecutor(max_workers=10) - def emit(self, record): log_entry = self.format(record) if conf.ENABLE_TELEMETRY == 'true' and conf.BONFIRE_BOT: self.executor.submit(self.send_to_es, log_entry) - def start_command_log(self, start_time, command, options_used): self.start_time = start_time self.command = command self.options_used = options_used - def send_to_es(self, log_entry): # Convert log_entry to JSON and send to Elasticsearch try: diff --git a/bonfire/processor.py b/bonfire/processor.py index 693ae44f..6285a822 100644 --- a/bonfire/processor.py +++ b/bonfire/processor.py @@ -20,6 +20,7 @@ log = logging.getLogger(__name__) + def _process_template(*args, **kwargs): # run process_template with prettier error handling try: From 6510f85c99d2139221d37f42008d08be04a68415 Mon Sep 17 00:00:00 2001 From: Matt Holder Date: Wed, 6 Dec 2023 09:10:53 -0500 Subject: [PATCH 13/32] last of the linting --- bonfire/elastic_logging.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bonfire/elastic_logging.py b/bonfire/elastic_logging.py index 8fdf5957..758a5b37 100644 --- a/bonfire/elastic_logging.py +++ b/bonfire/elastic_logging.py @@ -4,6 +4,7 @@ import bonfire.config as conf + class AsyncElasticsearchHandler(logging.Handler): def __init__(self, es_url): super().__init__() From ba412388c8e65eb6bf318a5a3f4901405640d0ee Mon Sep 17 00:00:00 2001 From: Matt Holder Date: Wed, 6 Dec 2023 09:27:51 -0500 Subject: [PATCH 14/32] dedicated field for logging flag --- bonfire/bonfire.py | 2 +- bonfire/elastic_logging.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/bonfire/bonfire.py b/bonfire/bonfire.py index cfab48bc..41260069 100755 --- a/bonfire/bonfire.py +++ b/bonfire/bonfire.py @@ -73,7 +73,7 @@ def _error(msg): - if es_handler.start_time: + if es_handler.log_started: # failure path log = { "timestamp": datetime.datetime.now().isoformat(), diff --git a/bonfire/elastic_logging.py b/bonfire/elastic_logging.py index 758a5b37..b4746c47 100644 --- a/bonfire/elastic_logging.py +++ b/bonfire/elastic_logging.py @@ -10,6 +10,7 @@ def __init__(self, es_url): super().__init__() self.es_url = es_url self.executor = ThreadPoolExecutor(max_workers=10) + self.log_started = False def emit(self, record): log_entry = self.format(record) @@ -17,6 +18,7 @@ def emit(self, record): self.executor.submit(self.send_to_es, log_entry) def start_command_log(self, start_time, command, options_used): + self.log_started = True self.start_time = start_time self.command = command self.options_used = options_used From 058d096a5e83129b64dd3df72be39482921f501a Mon Sep 17 00:00:00 2001 From: Matt Holder Date: Wed, 6 Dec 2023 13:37:36 -0500 Subject: [PATCH 15/32] consolodate paths to just my wrapper --- bonfire/bonfire.py | 44 ++++++++++++++++---------------------- bonfire/elastic_logging.py | 9 +------- 2 files changed, 20 insertions(+), 33 deletions(-) diff --git a/bonfire/bonfire.py b/bonfire/bonfire.py index 41260069..1a2c0207 100755 --- a/bonfire/bonfire.py +++ b/bonfire/bonfire.py @@ -73,20 +73,6 @@ def _error(msg): - if es_handler.log_started: - # failure path - log = { - "timestamp": datetime.datetime.now().isoformat(), - "duration_seconds": time.time() - es_handler.start_time, - "bot": conf.BONFIRE_BOT, - "error": msg, - "succeeded": False, - "command": es_handler.command, - "options": es_handler.options_used, - } - - es_telemetry.info(json.dumps(log)) - click.echo(f"ERROR: {msg}", err=True) sys.exit(1) @@ -116,21 +102,29 @@ def wrapper(*args, **kwargs): elif arg == '-p' or arg == '--set-parameter': is_parameter = True - es_handler.start_command_log(time.time(), command, options_used) - - result = f(*args, **kwargs) + start_time = time.time() - # successful path - log = { + log_entry = { "timestamp": datetime.datetime.now().isoformat(), - "duration_seconds": time.time() - es_handler.start_time, + "duration_seconds": time.time() - start_time, "bot": conf.BONFIRE_BOT, - "error": "", - "succeeded": True, - "command": es_handler.command, - "options": es_handler.options_used, + "command": command, + "options": options_used, } - es_telemetry.info(json.dumps(log)) + + try: + result = f(*args, **kwargs) + # successful path + log_entry["succeeded"] = True + log_entry["error"] = "" + es_telemetry.info(json.dumps(log_entry)) + except Exception as e: + # fail path + log_entry["succeeded"] = False + log_entry["error"] = str(e) + es_telemetry.info(json.dumps(log_entry)) + # propagate exception for click_exception_wrapper + raise e return result diff --git a/bonfire/elastic_logging.py b/bonfire/elastic_logging.py index b4746c47..ca0aa85c 100644 --- a/bonfire/elastic_logging.py +++ b/bonfire/elastic_logging.py @@ -10,26 +10,19 @@ def __init__(self, es_url): super().__init__() self.es_url = es_url self.executor = ThreadPoolExecutor(max_workers=10) - self.log_started = False def emit(self, record): log_entry = self.format(record) if conf.ENABLE_TELEMETRY == 'true' and conf.BONFIRE_BOT: self.executor.submit(self.send_to_es, log_entry) - def start_command_log(self, start_time, command, options_used): - self.log_started = True - self.start_time = start_time - self.command = command - self.options_used = options_used - def send_to_es(self, log_entry): # Convert log_entry to JSON and send to Elasticsearch try: headers = {"Authorization": conf.ELASTICSEARCH_APIKEY, "Content-Type": "application/json"} - response = requests.post(self.es_url, headers=headers, data=log_entry, timeout=0.1) + response = requests.post(self.es_url, headers=headers, data=log_entry, timeout=0.1, verify=False) response.raise_for_status() except Exception as e: # Handle exceptions (e.g., network issues, Elasticsearch down) From ace45fbbf609780e2cefc671ba0422d298d3af45 Mon Sep 17 00:00:00 2001 From: Matt Holder Date: Wed, 6 Dec 2023 14:08:32 -0500 Subject: [PATCH 16/32] verify with ssl --- bonfire/elastic_logging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bonfire/elastic_logging.py b/bonfire/elastic_logging.py index ca0aa85c..0a9531b2 100644 --- a/bonfire/elastic_logging.py +++ b/bonfire/elastic_logging.py @@ -22,7 +22,7 @@ def send_to_es(self, log_entry): headers = {"Authorization": conf.ELASTICSEARCH_APIKEY, "Content-Type": "application/json"} - response = requests.post(self.es_url, headers=headers, data=log_entry, timeout=0.1, verify=False) + response = requests.post(self.es_url, headers=headers, data=log_entry, timeout=0.1) response.raise_for_status() except Exception as e: # Handle exceptions (e.g., network issues, Elasticsearch down) From 9f0ec2716f6ec95c84896164e50dd3779cbfd561 Mon Sep 17 00:00:00 2001 From: Matt Holder Date: Thu, 7 Dec 2023 10:01:01 -0500 Subject: [PATCH 17/32] don't send es logs to console --- bonfire/bonfire.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bonfire/bonfire.py b/bonfire/bonfire.py index 1a2c0207..ea4e5d12 100755 --- a/bonfire/bonfire.py +++ b/bonfire/bonfire.py @@ -57,6 +57,7 @@ log = logging.getLogger(__name__) es_telemetry = logging.getLogger("elasticsearch") +es_telemetry.propagate = False es_handler = AsyncElasticsearchHandler(conf.ELASTICSEARCH_HOST) es_telemetry.addHandler(es_handler) From 05507153bfb74d137f8934b2bfb07edb096e8a68 Mon Sep 17 00:00:00 2001 From: Matt Holder Date: Fri, 8 Dec 2023 09:09:23 -0500 Subject: [PATCH 18/32] cleaning up variable usage --- bonfire/config.py | 4 ++-- bonfire/elastic_logging.py | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/bonfire/config.py b/bonfire/config.py index a19ed567..6e82da85 100644 --- a/bonfire/config.py +++ b/bonfire/config.py @@ -31,7 +31,7 @@ DEFAULT_GRAPHQL_URL = "https://app-interface.apps.appsrep05ue1.zqxk.p1.openshiftapps.com/graphql" DEFAULT_ELASTICSEARCH_HOST = "https://localhost:9200/search-bonfire/_doc" -DEFAULT_ENABLE_TELEMETRY = "true" +DEFAULT_ENABLE_TELEMETRY = "false" ENV_FILE = str(DEFAULT_ENV_PATH.absolute()) if DEFAULT_ENV_PATH.exists() else "" load_dotenv(ENV_FILE) @@ -68,7 +68,7 @@ ELASTICSEARCH_HOST = os.getenv("ELASTICSEARCH_HOST", DEFAULT_ELASTICSEARCH_HOST) ELASTICSEARCH_APIKEY = os.getenv("ELASTICSEARCH_APIKEY") -ENABLE_TELEMETRY = os.getenv("ENABLE_TELEMETRY", DEFAULT_ENABLE_TELEMETRY) +ENABLE_TELEMETRY = os.getenv("ENABLE_TELEMETRY", DEFAULT_ENABLE_TELEMETRY).lower() == "true" DEFAULT_FRONTEND_DEPENDENCIES = ( "chrome-service", diff --git a/bonfire/elastic_logging.py b/bonfire/elastic_logging.py index 0a9531b2..b0a4f427 100644 --- a/bonfire/elastic_logging.py +++ b/bonfire/elastic_logging.py @@ -5,6 +5,9 @@ import bonfire.config as conf +log = logging.getLogger(__name__) + + class AsyncElasticsearchHandler(logging.Handler): def __init__(self, es_url): super().__init__() @@ -13,7 +16,7 @@ def __init__(self, es_url): def emit(self, record): log_entry = self.format(record) - if conf.ENABLE_TELEMETRY == 'true' and conf.BONFIRE_BOT: + if conf.ENABLE_TELEMETRY: self.executor.submit(self.send_to_es, log_entry) def send_to_es(self, log_entry): @@ -26,5 +29,4 @@ def send_to_es(self, log_entry): response.raise_for_status() except Exception as e: # Handle exceptions (e.g., network issues, Elasticsearch down) - print(f"Error sending data to elasticsearch: {e}") - pass + log.error("Error sending data to elasticsearch: %s", e) From 6ba80bcf804783c794662ce99915e74793b6c3b8 Mon Sep 17 00:00:00 2001 From: Matt Holder Date: Mon, 11 Dec 2023 08:26:32 -0500 Subject: [PATCH 19/32] refactor es telemetry to not be a decorator --- bonfire/bonfire.py | 106 +++++++++++++++---------------------- bonfire/elastic_logging.py | 17 ++++-- 2 files changed, 55 insertions(+), 68 deletions(-) diff --git a/bonfire/bonfire.py b/bonfire/bonfire.py index ea4e5d12..e4619f1e 100755 --- a/bonfire/bonfire.py +++ b/bonfire/bonfire.py @@ -4,7 +4,7 @@ import json import logging import sys -import time +import uuid import warnings from functools import wraps @@ -57,9 +57,6 @@ log = logging.getLogger(__name__) es_telemetry = logging.getLogger("elasticsearch") -es_telemetry.propagate = False -es_handler = AsyncElasticsearchHandler(conf.ELASTICSEARCH_HOST) -es_telemetry.addHandler(es_handler) APP_SRE_SRC = "appsre" FILE_SRC = "file" @@ -74,6 +71,7 @@ def _error(msg): + send_telemetry(msg, success=False) click.echo(f"ERROR: {msg}", err=True) sys.exit(1) @@ -89,57 +87,13 @@ def current_namespace_or_error(): return namespace -def option_usage_wrapper(command): - def decorator(f): - @wraps(f) - def wrapper(*args, **kwargs): - # only gather telemetry if enabled and a bot run - options_used = [] - is_parameter = False - for arg in sys.argv[2:]: - if is_parameter: - options_used.append(arg.split('=')[0]) - is_parameter = False - elif arg == '-p' or arg == '--set-parameter': - is_parameter = True - - start_time = time.time() - - log_entry = { - "timestamp": datetime.datetime.now().isoformat(), - "duration_seconds": time.time() - start_time, - "bot": conf.BONFIRE_BOT, - "command": command, - "options": options_used, - } - - try: - result = f(*args, **kwargs) - # successful path - log_entry["succeeded"] = True - log_entry["error"] = "" - es_telemetry.info(json.dumps(log_entry)) - except Exception as e: - # fail path - log_entry["succeeded"] = False - log_entry["error"] = str(e) - es_telemetry.info(json.dumps(log_entry)) - # propagate exception for click_exception_wrapper - raise e - - return result - - return wrapper - - return decorator - - def click_exception_wrapper(command): def decorator(f): @wraps(f) def wrapper(*args, **kwargs): try: - return f(*args, **kwargs) + result = f(*args, **kwargs) + return result except KeyboardInterrupt: _error(f"{command}: aborted by keyboard interrupt") except TimedOutError as err: @@ -818,7 +772,6 @@ def inner(func): @namespace.command("list") @options(_ns_list_options) -@option_usage_wrapper("namespace list") def _list_namespaces(available, mine, output): """Get list of ephemeral namespaces""" if not has_ns_operator(): @@ -861,7 +814,6 @@ def _list_namespaces(available, mine, output): @options(_ns_reserve_options) @options(_timeout_option) @click_exception_wrapper("namespace reserve") -@option_usage_wrapper("namespace reserve") def _cmd_namespace_reserve(name, requester, duration, pool, timeout, local, force): """Reserve an ephemeral namespace""" ns = _check_and_reserve_namespace(name, requester, duration, pool, timeout, local, force) @@ -879,7 +831,6 @@ def _cmd_namespace_reserve(name, requester, duration, pool, timeout, local, forc ) @options([_local_option]) @click_exception_wrapper("namespace release") -@option_usage_wrapper("namespace release") def _cmd_namespace_release(namespace, force, local): """Remove reservation from an ephemeral namespace""" if not has_ns_operator(): @@ -909,7 +860,6 @@ def _cmd_namespace_release(namespace, force, local): ) @options([_local_option]) @click_exception_wrapper("namespace extend") -@option_usage_wrapper("namespace extend") def _cmd_namespace_extend(namespace, duration, local): """Extend a reservation of an ephemeral namespace""" if not has_ns_operator(): @@ -930,7 +880,6 @@ def _cmd_namespace_extend(namespace, duration, local): help="Only wait for DB resources owned by ClowdApps to be ready", ) @options(_timeout_option) -@option_usage_wrapper("namespace wait-on-resources") def _cmd_namespace_wait_on_resources(namespace, timeout, db_only): """Wait for rolled out resources to be ready in namespace""" if not namespace: @@ -944,7 +893,6 @@ def _cmd_namespace_wait_on_resources(namespace, timeout, db_only): @namespace.command("describe") @click.argument("namespace", required=False, type=str) -@option_usage_wrapper("namespace describe") def _describe_namespace(namespace): """Get current namespace info""" if not namespace: @@ -1087,7 +1035,6 @@ def _cmd_pool_types(): help="Namespace you intend to deploy to (default: none)", type=str, ) -@option_usage_wrapper("process") def _cmd_process( app_names, source, @@ -1273,7 +1220,6 @@ def _check_and_reserve_namespace(name, requester, duration, pool, timeout, local ) @options(_ns_reserve_options) @options(_timeout_option) -@option_usage_wrapper("deploy") def _cmd_config_deploy( app_names, source, @@ -1411,6 +1357,7 @@ def _err_handler(err): _err_handler(err) else: log.info("successfully deployed to namespace %s", ns) + send_telemetry("successful deployment") url = get_console_url() if url: ns_url = f"{url}/k8s/cluster/projects/{ns}" @@ -1425,7 +1372,6 @@ def _process_clowdenv(target_namespace, quay_user, env_name, template_file, loca @main.command("process-env") @options(_clowdenv_process_options) -@option_usage_wrapper("process-env") def _cmd_process_clowdenv(namespace, quay_user, clowd_env, template_file, local): """Process ClowdEnv template and print output""" clowd_env_config = _process_clowdenv(namespace, quay_user, clowd_env, template_file, local) @@ -1449,7 +1395,6 @@ def _cmd_process_clowdenv(namespace, quay_user, clowd_env, template_file, local) @options(_ns_reserve_options) @options(_timeout_option) @click_exception_wrapper("deploy-env") -@option_usage_wrapper("deploy-env") def _cmd_deploy_clowdenv( namespace, quay_user, @@ -1495,7 +1440,6 @@ def _cmd_deploy_clowdenv( @main.command("process-iqe-cji") @options(_iqe_cji_process_options) -@option_usage_wrapper("process-iqe-cji") def _cmd_process_iqe_cji( clowd_app_name, debug, @@ -1548,7 +1492,6 @@ def _cmd_process_iqe_cji( @options(_ns_reserve_options) @options(_timeout_option) @click_exception_wrapper("deploy-iqe-cji") -@option_usage_wrapper("deploy-iqe-cji") def _cmd_deploy_iqe_cji( namespace, clowd_app_name, @@ -1648,7 +1591,6 @@ def _cmd_edit_default_config(path): help="List components contained within each app group", ) @apps.command("list") -@option_usage_wrapper("apps list") def _cmd_apps_list( source, local_config_path, @@ -1679,7 +1621,6 @@ def _cmd_apps_list( type=str, ) @apps.command("what-depends-on") -@option_usage_wrapper("apps what-depends-on") def _cmd_apps_what_depends_on( source, local_config_path, @@ -1696,7 +1637,44 @@ def _cmd_apps_what_depends_on( print("\n".join(found) or f"no apps depending on {component} found") +def _mask_parameter_values(cli_args): + masked_list = [] + + is_parameter = False + for arg in cli_args: + if is_parameter: + masked_arg = f"{arg.split('=')[0]}=*******" + masked_list.append(masked_arg) + is_parameter = False + else: + masked_list.append(arg) + is_parameter = arg == '-p' or arg == '--set-parameter' + + return masked_list + + +def send_telemetry(log_message, success=True): + es_handler = next((h for h in es_telemetry.handlers if type(h) == AsyncElasticsearchHandler), None) + + if not es_handler: + es_telemetry.warning("AsyncElasticsearchHandler not configured on telemetry logger") + + es_handler.set_success_status(success) + + es_telemetry.info(log_message) + + def main_with_handler(): + metadata = { + "uuid": str(uuid.uuid4()), + "start_time": datetime.datetime.now().isoformat(), + "bot": conf.BONFIRE_BOT, + "command": _mask_parameter_values(sys.argv[1:]) + } + + es_handler = AsyncElasticsearchHandler(conf.ELASTICSEARCH_HOST, metadata) + es_telemetry.addHandler(es_handler) + try: main() except FatalError as err: diff --git a/bonfire/elastic_logging.py b/bonfire/elastic_logging.py index b0a4f427..f7bde616 100644 --- a/bonfire/elastic_logging.py +++ b/bonfire/elastic_logging.py @@ -1,4 +1,6 @@ +from datetime import datetime import logging +import json import requests from concurrent.futures import ThreadPoolExecutor @@ -9,15 +11,22 @@ class AsyncElasticsearchHandler(logging.Handler): - def __init__(self, es_url): + def __init__(self, es_url, metadata): super().__init__() self.es_url = es_url + self.metadata = metadata self.executor = ThreadPoolExecutor(max_workers=10) def emit(self, record): - log_entry = self.format(record) + self.metadata["time_of_logging"] = datetime.now().isoformat() + self.metadata["duration"] = str(datetime.now() - datetime.fromisoformat(self.metadata["start_time"])) + + log_entry = {"log": self.format(record), "metadata": self.metadata} if conf.ENABLE_TELEMETRY: - self.executor.submit(self.send_to_es, log_entry) + self.executor.submit(self.send_to_es, json.dumps(log_entry)) + + def set_success_status(self, run_status): + self.metadata["succeeded"] = run_status def send_to_es(self, log_entry): # Convert log_entry to JSON and send to Elasticsearch @@ -25,7 +34,7 @@ def send_to_es(self, log_entry): headers = {"Authorization": conf.ELASTICSEARCH_APIKEY, "Content-Type": "application/json"} - response = requests.post(self.es_url, headers=headers, data=log_entry, timeout=0.1) + response = requests.post(self.es_url, headers=headers, data=log_entry, timeout=0.1, verify=False) response.raise_for_status() except Exception as e: # Handle exceptions (e.g., network issues, Elasticsearch down) From 9135dbf7bade91e1f7c1ae3468414e614c9f1adb Mon Sep 17 00:00:00 2001 From: Matt Holder Date: Mon, 11 Dec 2023 08:27:35 -0500 Subject: [PATCH 20/32] verify ssl certs --- bonfire/elastic_logging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bonfire/elastic_logging.py b/bonfire/elastic_logging.py index f7bde616..4748f4da 100644 --- a/bonfire/elastic_logging.py +++ b/bonfire/elastic_logging.py @@ -34,7 +34,7 @@ def send_to_es(self, log_entry): headers = {"Authorization": conf.ELASTICSEARCH_APIKEY, "Content-Type": "application/json"} - response = requests.post(self.es_url, headers=headers, data=log_entry, timeout=0.1, verify=False) + response = requests.post(self.es_url, headers=headers, data=log_entry, timeout=0.1) response.raise_for_status() except Exception as e: # Handle exceptions (e.g., network issues, Elasticsearch down) From 4e94229217d0b25be949cfeb41a2694b77bd1ba0 Mon Sep 17 00:00:00 2001 From: Matt Holder Date: Mon, 11 Dec 2023 08:58:08 -0500 Subject: [PATCH 21/32] refactor so elastic related logging logic in one place --- bonfire/bonfire.py | 47 ++++---------------------------------- bonfire/elastic_logging.py | 42 +++++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 44 deletions(-) diff --git a/bonfire/bonfire.py b/bonfire/bonfire.py index e4619f1e..f85cc2b4 100755 --- a/bonfire/bonfire.py +++ b/bonfire/bonfire.py @@ -1,10 +1,8 @@ #!/usr/bin/env python3 -import datetime import json import logging import sys -import uuid import warnings from functools import wraps @@ -14,7 +12,7 @@ from wait_for import TimedOutError import bonfire.config as conf -from bonfire.elastic_logging import AsyncElasticsearchHandler +from bonfire.elastic_logging import ElasticLogger from bonfire.local import get_local_apps, get_appsfile_apps from bonfire.utils import AppOrComponentSelector, RepoFile, SYNTAX_ERR from bonfire.namespaces import ( @@ -56,7 +54,7 @@ log = logging.getLogger(__name__) -es_telemetry = logging.getLogger("elasticsearch") +es_telemetry = ElasticLogger() APP_SRE_SRC = "appsre" FILE_SRC = "file" @@ -71,7 +69,7 @@ def _error(msg): - send_telemetry(msg, success=False) + es_telemetry.send_telemetry(msg, success=False) click.echo(f"ERROR: {msg}", err=True) sys.exit(1) @@ -1357,7 +1355,7 @@ def _err_handler(err): _err_handler(err) else: log.info("successfully deployed to namespace %s", ns) - send_telemetry("successful deployment") + es_telemetry.send_telemetry("successful deployment") url = get_console_url() if url: ns_url = f"{url}/k8s/cluster/projects/{ns}" @@ -1637,44 +1635,7 @@ def _cmd_apps_what_depends_on( print("\n".join(found) or f"no apps depending on {component} found") -def _mask_parameter_values(cli_args): - masked_list = [] - - is_parameter = False - for arg in cli_args: - if is_parameter: - masked_arg = f"{arg.split('=')[0]}=*******" - masked_list.append(masked_arg) - is_parameter = False - else: - masked_list.append(arg) - is_parameter = arg == '-p' or arg == '--set-parameter' - - return masked_list - - -def send_telemetry(log_message, success=True): - es_handler = next((h for h in es_telemetry.handlers if type(h) == AsyncElasticsearchHandler), None) - - if not es_handler: - es_telemetry.warning("AsyncElasticsearchHandler not configured on telemetry logger") - - es_handler.set_success_status(success) - - es_telemetry.info(log_message) - - def main_with_handler(): - metadata = { - "uuid": str(uuid.uuid4()), - "start_time": datetime.datetime.now().isoformat(), - "bot": conf.BONFIRE_BOT, - "command": _mask_parameter_values(sys.argv[1:]) - } - - es_handler = AsyncElasticsearchHandler(conf.ELASTICSEARCH_HOST, metadata) - es_telemetry.addHandler(es_handler) - try: main() except FatalError as err: diff --git a/bonfire/elastic_logging.py b/bonfire/elastic_logging.py index 4748f4da..8ea97990 100644 --- a/bonfire/elastic_logging.py +++ b/bonfire/elastic_logging.py @@ -2,6 +2,8 @@ import logging import json import requests +import sys +import uuid from concurrent.futures import ThreadPoolExecutor import bonfire.config as conf @@ -10,6 +12,44 @@ log = logging.getLogger(__name__) +class ElasticLogger(): + def __init__(self): + self.es_telemetry = logging.getLogger("elasicsearch") + metadata = { + "uuid": str(uuid.uuid4()), + "start_time": datetime.now().isoformat(), + "bot": conf.BONFIRE_BOT, + "command": self._mask_parameter_values(sys.argv[1:]) + } + + es_handler = next((h for h in self.es_telemetry.handlers if type(h) == AsyncElasticsearchHandler), None) + if es_handler: + log.warning("AsyncElasticsearchHandler already configured for current logger") + + self.es_handler = AsyncElasticsearchHandler(conf.ELASTICSEARCH_HOST, metadata) + self.es_telemetry.addHandler(self.es_handler) + + def _mask_parameter_values(self, cli_args): + masked_list = [] + + is_parameter = False + for arg in cli_args: + if is_parameter: + masked_arg = f"{arg.split('=')[0]}=*******" + masked_list.append(masked_arg) + is_parameter = False + else: + masked_list.append(arg) + is_parameter = arg == '-p' or arg == '--set-parameter' + + return masked_list + + def send_telemetry(self, log_message, success=True): + self.es_handler.set_success_status(success) + + self.es_telemetry.info(log_message) + + class AsyncElasticsearchHandler(logging.Handler): def __init__(self, es_url, metadata): super().__init__() @@ -34,7 +74,7 @@ def send_to_es(self, log_entry): headers = {"Authorization": conf.ELASTICSEARCH_APIKEY, "Content-Type": "application/json"} - response = requests.post(self.es_url, headers=headers, data=log_entry, timeout=0.1) + response = requests.post(self.es_url, headers=headers, data=log_entry, timeout=0.1, verify=False) response.raise_for_status() except Exception as e: # Handle exceptions (e.g., network issues, Elasticsearch down) From 78f11ef3a7fdaab7ec4a60a2d31023545fd36ce5 Mon Sep 17 00:00:00 2001 From: Matt Holder Date: Mon, 11 Dec 2023 09:18:23 -0500 Subject: [PATCH 22/32] linting + verify ssl --- bonfire/elastic_logging.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bonfire/elastic_logging.py b/bonfire/elastic_logging.py index 8ea97990..ca3b7dc7 100644 --- a/bonfire/elastic_logging.py +++ b/bonfire/elastic_logging.py @@ -22,13 +22,13 @@ def __init__(self): "command": self._mask_parameter_values(sys.argv[1:]) } - es_handler = next((h for h in self.es_telemetry.handlers if type(h) == AsyncElasticsearchHandler), None) + es_handler = next((h for h in self.es_telemetry.handlers if type(h) is AsyncElasticsearchHandler), None) if es_handler: log.warning("AsyncElasticsearchHandler already configured for current logger") self.es_handler = AsyncElasticsearchHandler(conf.ELASTICSEARCH_HOST, metadata) self.es_telemetry.addHandler(self.es_handler) - + def _mask_parameter_values(self, cli_args): masked_list = [] @@ -59,7 +59,7 @@ def __init__(self, es_url, metadata): def emit(self, record): self.metadata["time_of_logging"] = datetime.now().isoformat() - self.metadata["duration"] = str(datetime.now() - datetime.fromisoformat(self.metadata["start_time"])) + self.metadata["duration"] = str(datetime.now()- datetime.fromisoformat(self.metadata["start_time"])) log_entry = {"log": self.format(record), "metadata": self.metadata} if conf.ENABLE_TELEMETRY: @@ -74,7 +74,7 @@ def send_to_es(self, log_entry): headers = {"Authorization": conf.ELASTICSEARCH_APIKEY, "Content-Type": "application/json"} - response = requests.post(self.es_url, headers=headers, data=log_entry, timeout=0.1, verify=False) + response = requests.post(self.es_url, headers=headers, data=log_entry, timeout=0.1) response.raise_for_status() except Exception as e: # Handle exceptions (e.g., network issues, Elasticsearch down) From 75707fb6e35ce65b2075f759510a1df5c0b0be4a Mon Sep 17 00:00:00 2001 From: Matt Holder Date: Mon, 11 Dec 2023 09:19:23 -0500 Subject: [PATCH 23/32] linting --- bonfire/elastic_logging.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bonfire/elastic_logging.py b/bonfire/elastic_logging.py index ca3b7dc7..c9b00c70 100644 --- a/bonfire/elastic_logging.py +++ b/bonfire/elastic_logging.py @@ -22,7 +22,8 @@ def __init__(self): "command": self._mask_parameter_values(sys.argv[1:]) } - es_handler = next((h for h in self.es_telemetry.handlers if type(h) is AsyncElasticsearchHandler), None) + es_handler = next((h for h in self.es_telemetry.handlers + if type(h) is AsyncElasticsearchHandler), None) if es_handler: log.warning("AsyncElasticsearchHandler already configured for current logger") From 51303e7491867e98eb94995ac7f68079f7436719 Mon Sep 17 00:00:00 2001 From: Matt Holder Date: Mon, 11 Dec 2023 09:23:42 -0500 Subject: [PATCH 24/32] shortening lines for lint --- bonfire/elastic_logging.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bonfire/elastic_logging.py b/bonfire/elastic_logging.py index c9b00c70..8b62fbed 100644 --- a/bonfire/elastic_logging.py +++ b/bonfire/elastic_logging.py @@ -1,4 +1,4 @@ -from datetime import datetime +from datetime import datetime as dt import logging import json import requests @@ -59,8 +59,8 @@ def __init__(self, es_url, metadata): self.executor = ThreadPoolExecutor(max_workers=10) def emit(self, record): - self.metadata["time_of_logging"] = datetime.now().isoformat() - self.metadata["duration"] = str(datetime.now()- datetime.fromisoformat(self.metadata["start_time"])) + self.metadata["time_of_logging"] = dt.now().isoformat() + self.metadata["duration"] = str(dt.now() - dt.fromisoformat(self.metadata["start_time"])) log_entry = {"log": self.format(record), "metadata": self.metadata} if conf.ENABLE_TELEMETRY: From 969fa9ec13575422ca4a2db539ba449c84688ab4 Mon Sep 17 00:00:00 2001 From: Matt Holder Date: Mon, 11 Dec 2023 09:37:38 -0500 Subject: [PATCH 25/32] missed a datetime --- bonfire/elastic_logging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bonfire/elastic_logging.py b/bonfire/elastic_logging.py index 8b62fbed..6bb333c4 100644 --- a/bonfire/elastic_logging.py +++ b/bonfire/elastic_logging.py @@ -17,7 +17,7 @@ def __init__(self): self.es_telemetry = logging.getLogger("elasicsearch") metadata = { "uuid": str(uuid.uuid4()), - "start_time": datetime.now().isoformat(), + "start_time": dt.now().isoformat(), "bot": conf.BONFIRE_BOT, "command": self._mask_parameter_values(sys.argv[1:]) } From 2a070fb3bd4d63a9e7ba689c7bf2d09122b0ae68 Mon Sep 17 00:00:00 2001 From: Matt Holder Date: Mon, 11 Dec 2023 09:38:32 -0500 Subject: [PATCH 26/32] removed trailing whitespace --- bonfire/elastic_logging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bonfire/elastic_logging.py b/bonfire/elastic_logging.py index 6bb333c4..72efe89d 100644 --- a/bonfire/elastic_logging.py +++ b/bonfire/elastic_logging.py @@ -22,7 +22,7 @@ def __init__(self): "command": self._mask_parameter_values(sys.argv[1:]) } - es_handler = next((h for h in self.es_telemetry.handlers + es_handler = next((h for h in self.es_telemetry.handlers if type(h) is AsyncElasticsearchHandler), None) if es_handler: log.warning("AsyncElasticsearchHandler already configured for current logger") From 53eb018ba7bc9357e471e3cf064c03ceabfdb4ae Mon Sep 17 00:00:00 2001 From: Matt Holder Date: Mon, 11 Dec 2023 13:28:09 -0500 Subject: [PATCH 27/32] minor tweaks --- bonfire/elastic_logging.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/bonfire/elastic_logging.py b/bonfire/elastic_logging.py index 72efe89d..eb9c1ae9 100644 --- a/bonfire/elastic_logging.py +++ b/bonfire/elastic_logging.py @@ -17,7 +17,7 @@ def __init__(self): self.es_telemetry = logging.getLogger("elasicsearch") metadata = { "uuid": str(uuid.uuid4()), - "start_time": dt.now().isoformat(), + "start_time": dt.now(), "bot": conf.BONFIRE_BOT, "command": self._mask_parameter_values(sys.argv[1:]) } @@ -30,7 +30,8 @@ def __init__(self): self.es_handler = AsyncElasticsearchHandler(conf.ELASTICSEARCH_HOST, metadata) self.es_telemetry.addHandler(self.es_handler) - def _mask_parameter_values(self, cli_args): + @staticmethod + def _mask_parameter_values(cli_args): masked_list = [] is_parameter = False @@ -59,10 +60,14 @@ def __init__(self, es_url, metadata): self.executor = ThreadPoolExecutor(max_workers=10) def emit(self, record): - self.metadata["time_of_logging"] = dt.now().isoformat() - self.metadata["duration"] = str(dt.now() - dt.fromisoformat(self.metadata["start_time"])) + self.metadata["@timestamp"] = dt.now().isoformat() + self.metadata["elapsed_sec"] = (dt.now() - self.metadata["start_time"]).total_seconds() - log_entry = {"log": self.format(record), "metadata": self.metadata} + # copy and modify metadata for json formatting + metadata = self.metadata + metadata["start_time"] = metadata["start_time"].isoformat() + + log_entry = {"log": self.format(record), "metadata": metadata} if conf.ENABLE_TELEMETRY: self.executor.submit(self.send_to_es, json.dumps(log_entry)) From e8b39f03b09756285b361b3e3658ec5ddc0dc6ef Mon Sep 17 00:00:00 2001 From: Matt Holder Date: Mon, 11 Dec 2023 14:05:59 -0500 Subject: [PATCH 28/32] bonfire_bot flag is now bool instead of string --- bonfire/elastic_logging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bonfire/elastic_logging.py b/bonfire/elastic_logging.py index eb9c1ae9..a5403f51 100644 --- a/bonfire/elastic_logging.py +++ b/bonfire/elastic_logging.py @@ -18,7 +18,7 @@ def __init__(self): metadata = { "uuid": str(uuid.uuid4()), "start_time": dt.now(), - "bot": conf.BONFIRE_BOT, + "bot": (conf.BONFIRE_BOT.lower() == 'true'), "command": self._mask_parameter_values(sys.argv[1:]) } From 7ccee613ec0169120359db52c79b41a38a01ed81 Mon Sep 17 00:00:00 2001 From: Matt Holder Date: Mon, 11 Dec 2023 14:20:09 -0500 Subject: [PATCH 29/32] moving things around --- bonfire/elastic_logging.py | 59 ++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/bonfire/elastic_logging.py b/bonfire/elastic_logging.py index a5403f51..ff4e3b0f 100644 --- a/bonfire/elastic_logging.py +++ b/bonfire/elastic_logging.py @@ -15,37 +15,15 @@ class ElasticLogger(): def __init__(self): self.es_telemetry = logging.getLogger("elasicsearch") - metadata = { - "uuid": str(uuid.uuid4()), - "start_time": dt.now(), - "bot": (conf.BONFIRE_BOT.lower() == 'true'), - "command": self._mask_parameter_values(sys.argv[1:]) - } es_handler = next((h for h in self.es_telemetry.handlers if type(h) is AsyncElasticsearchHandler), None) if es_handler: log.warning("AsyncElasticsearchHandler already configured for current logger") - self.es_handler = AsyncElasticsearchHandler(conf.ELASTICSEARCH_HOST, metadata) + self.es_handler = AsyncElasticsearchHandler(conf.ELASTICSEARCH_HOST) self.es_telemetry.addHandler(self.es_handler) - @staticmethod - def _mask_parameter_values(cli_args): - masked_list = [] - - is_parameter = False - for arg in cli_args: - if is_parameter: - masked_arg = f"{arg.split('=')[0]}=*******" - masked_list.append(masked_arg) - is_parameter = False - else: - masked_list.append(arg) - is_parameter = arg == '-p' or arg == '--set-parameter' - - return masked_list - def send_telemetry(self, log_message, success=True): self.es_handler.set_success_status(success) @@ -53,21 +31,23 @@ def send_telemetry(self, log_message, success=True): class AsyncElasticsearchHandler(logging.Handler): - def __init__(self, es_url, metadata): + def __init__(self, es_url): super().__init__() self.es_url = es_url - self.metadata = metadata self.executor = ThreadPoolExecutor(max_workers=10) + self.start_time = dt.now() + self.metadata = { + "uuid": str(uuid.uuid4()), + "start_time": self.start_time.isoformat(), + "bot": (conf.BONFIRE_BOT.lower() == 'true'), + "command": self._mask_parameter_values(sys.argv[1:]) + } def emit(self, record): self.metadata["@timestamp"] = dt.now().isoformat() - self.metadata["elapsed_sec"] = (dt.now() - self.metadata["start_time"]).total_seconds() + self.metadata["elapsed_sec"] = (dt.now() - self.start_time).total_seconds() - # copy and modify metadata for json formatting - metadata = self.metadata - metadata["start_time"] = metadata["start_time"].isoformat() - - log_entry = {"log": self.format(record), "metadata": metadata} + log_entry = {"log": self.format(record), "metadata": self.metadata} if conf.ENABLE_TELEMETRY: self.executor.submit(self.send_to_es, json.dumps(log_entry)) @@ -85,3 +65,20 @@ def send_to_es(self, log_entry): except Exception as e: # Handle exceptions (e.g., network issues, Elasticsearch down) log.error("Error sending data to elasticsearch: %s", e) + + + @staticmethod + def _mask_parameter_values(cli_args): + masked_list = [] + + is_parameter = False + for arg in cli_args: + if is_parameter: + masked_arg = f"{arg.split('=')[0]}=*******" + masked_list.append(masked_arg) + is_parameter = False + else: + masked_list.append(arg) + is_parameter = arg == '-p' or arg == '--set-parameter' + + return masked_list From dca80c5e7b2c372381e8c45c153020ec27373095 Mon Sep 17 00:00:00 2001 From: Matt Holder Date: Mon, 11 Dec 2023 14:21:34 -0500 Subject: [PATCH 30/32] linting --- bonfire/elastic_logging.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bonfire/elastic_logging.py b/bonfire/elastic_logging.py index ff4e3b0f..352ff19d 100644 --- a/bonfire/elastic_logging.py +++ b/bonfire/elastic_logging.py @@ -66,7 +66,6 @@ def send_to_es(self, log_entry): # Handle exceptions (e.g., network issues, Elasticsearch down) log.error("Error sending data to elasticsearch: %s", e) - @staticmethod def _mask_parameter_values(cli_args): masked_list = [] From 257dc0973722daac5c08c6801033398b67a10636 Mon Sep 17 00:00:00 2001 From: Matt Holder Date: Mon, 11 Dec 2023 14:54:32 -0500 Subject: [PATCH 31/32] bonfire_bot now bool in config + refactoring --- bonfire/config.py | 2 +- bonfire/elastic_logging.py | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/bonfire/config.py b/bonfire/config.py index 6e82da85..907ae5fc 100644 --- a/bonfire/config.py +++ b/bonfire/config.py @@ -58,7 +58,7 @@ # can be used to set name of 'requester' on namespace reservations BONFIRE_NS_REQUESTER = os.getenv("BONFIRE_NS_REQUESTER") # set to true when bonfire is running via automation using a bot acct (not an end user) -BONFIRE_BOT = os.getenv("BONFIRE_BOT") +BONFIRE_BOT = os.getenv("BONFIRE_BOT", "false").lower() == "true" BONFIRE_DEFAULT_PREFER = str(os.getenv("BONFIRE_DEFAULT_PREFER", "ENV_NAME=frontends")).split(",") BONFIRE_DEFAULT_REF_ENV = str(os.getenv("BONFIRE_DEFAULT_REF_ENV", "insights-stage")) diff --git a/bonfire/elastic_logging.py b/bonfire/elastic_logging.py index 352ff19d..9aa175e1 100644 --- a/bonfire/elastic_logging.py +++ b/bonfire/elastic_logging.py @@ -16,13 +16,14 @@ class ElasticLogger(): def __init__(self): self.es_telemetry = logging.getLogger("elasicsearch") - es_handler = next((h for h in self.es_telemetry.handlers - if type(h) is AsyncElasticsearchHandler), None) - if es_handler: - log.warning("AsyncElasticsearchHandler already configured for current logger") - - self.es_handler = AsyncElasticsearchHandler(conf.ELASTICSEARCH_HOST) - self.es_telemetry.addHandler(self.es_handler) + # prevent duplicate handlers + self.es_handler = next( + (h for h in self.es_telemetry.handlers if type(h) is AsyncElasticsearchHandler), + None + ) + if not self.es_handler: + self.es_handler = AsyncElasticsearchHandler(conf.ELASTICSEARCH_HOST) + self.es_telemetry.addHandler(self.es_handler) def send_telemetry(self, log_message, success=True): self.es_handler.set_success_status(success) @@ -39,7 +40,7 @@ def __init__(self, es_url): self.metadata = { "uuid": str(uuid.uuid4()), "start_time": self.start_time.isoformat(), - "bot": (conf.BONFIRE_BOT.lower() == 'true'), + "bot": conf.BONFIRE_BOT, "command": self._mask_parameter_values(sys.argv[1:]) } From d979883d1a42adadbf1dd07306de2982ec462bdd Mon Sep 17 00:00:00 2001 From: Brandon Squizzato Date: Mon, 18 Dec 2023 16:32:43 -0500 Subject: [PATCH 32/32] Run 'black' for formatting --- bonfire/elastic_logging.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/bonfire/elastic_logging.py b/bonfire/elastic_logging.py index 9aa175e1..5835e771 100644 --- a/bonfire/elastic_logging.py +++ b/bonfire/elastic_logging.py @@ -12,14 +12,13 @@ log = logging.getLogger(__name__) -class ElasticLogger(): +class ElasticLogger: def __init__(self): self.es_telemetry = logging.getLogger("elasicsearch") # prevent duplicate handlers self.es_handler = next( - (h for h in self.es_telemetry.handlers if type(h) is AsyncElasticsearchHandler), - None + (h for h in self.es_telemetry.handlers if type(h) is AsyncElasticsearchHandler), None ) if not self.es_handler: self.es_handler = AsyncElasticsearchHandler(conf.ELASTICSEARCH_HOST) @@ -41,7 +40,7 @@ def __init__(self, es_url): "uuid": str(uuid.uuid4()), "start_time": self.start_time.isoformat(), "bot": conf.BONFIRE_BOT, - "command": self._mask_parameter_values(sys.argv[1:]) + "command": self._mask_parameter_values(sys.argv[1:]), } def emit(self, record): @@ -58,8 +57,10 @@ def set_success_status(self, run_status): def send_to_es(self, log_entry): # Convert log_entry to JSON and send to Elasticsearch try: - headers = {"Authorization": conf.ELASTICSEARCH_APIKEY, - "Content-Type": "application/json"} + headers = { + "Authorization": conf.ELASTICSEARCH_APIKEY, + "Content-Type": "application/json", + } response = requests.post(self.es_url, headers=headers, data=log_entry, timeout=0.1) response.raise_for_status() @@ -79,6 +80,6 @@ def _mask_parameter_values(cli_args): is_parameter = False else: masked_list.append(arg) - is_parameter = arg == '-p' or arg == '--set-parameter' + is_parameter = arg == "-p" or arg == "--set-parameter" return masked_list