Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Telemetry support #339

Merged
merged 32 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
509ea4d
logs now send to local elasticsearch instance
mjholder Dec 4, 2023
cdedad8
remove logging handlers
mjholder Dec 4, 2023
3975593
report option usage via decorator
mjholder Dec 4, 2023
b5132a3
add decorator to commands that have options
mjholder Dec 4, 2023
768c6f0
reformatted messaged to be more readable
mjholder Dec 4, 2023
a7a7b17
only look for parameters
mjholder Dec 4, 2023
d181ffb
report transaction uuid, run status, and run time
mjholder Dec 5, 2023
21b2fa7
updated log message
mjholder Dec 6, 2023
c26e066
enable users to opt out
mjholder Dec 6, 2023
66af2a4
correct opt out logic
mjholder Dec 6, 2023
1755c7f
linting
mjholder Dec 6, 2023
8379f13
more linting
mjholder Dec 6, 2023
6510f85
last of the linting
mjholder Dec 6, 2023
ba41238
dedicated field for logging flag
mjholder Dec 6, 2023
058d096
consolodate paths to just my wrapper
mjholder Dec 6, 2023
ace45fb
verify with ssl
mjholder Dec 6, 2023
9f0ec27
don't send es logs to console
mjholder Dec 7, 2023
0550715
cleaning up variable usage
mjholder Dec 8, 2023
6ba80bc
refactor es telemetry to not be a decorator
mjholder Dec 11, 2023
9135dbf
verify ssl certs
mjholder Dec 11, 2023
4e94229
refactor so elastic related logging logic in one place
mjholder Dec 11, 2023
78f11ef
linting + verify ssl
mjholder Dec 11, 2023
75707fb
linting
mjholder Dec 11, 2023
51303e7
shortening lines for lint
mjholder Dec 11, 2023
969fa9e
missed a datetime
mjholder Dec 11, 2023
2a070fb
removed trailing whitespace
mjholder Dec 11, 2023
53eb018
minor tweaks
mjholder Dec 11, 2023
e8b39f0
bonfire_bot flag is now bool instead of string
mjholder Dec 11, 2023
7ccee61
moving things around
mjholder Dec 11, 2023
dca80c5
linting
mjholder Dec 11, 2023
257dc09
bonfire_bot now bool in config + refactoring
mjholder Dec 11, 2023
d979883
Run 'black' for formatting
bsquizz Dec 18, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion bonfire/bonfire.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from wait_for import TimedOutError

import bonfire.config as conf
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 (
Expand Down Expand Up @@ -51,7 +52,9 @@
merge_app_configs,
)


log = logging.getLogger(__name__)
es_telemetry = ElasticLogger()

APP_SRE_SRC = "appsre"
FILE_SRC = "file"
Expand All @@ -66,6 +69,7 @@


def _error(msg):
es_telemetry.send_telemetry(msg, success=False)
click.echo(f"ERROR: {msg}", err=True)
sys.exit(1)

Expand All @@ -86,7 +90,8 @@ 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:
Expand Down Expand Up @@ -1350,6 +1355,7 @@ def _err_handler(err):
_err_handler(err)
else:
log.info("successfully deployed to namespace %s", ns)
es_telemetry.send_telemetry("successful deployment")
url = get_console_url()
if url:
ns_url = f"{url}/k8s/cluster/projects/{ns}"
Expand Down
8 changes: 7 additions & 1 deletion bonfire/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@

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 = "false"

ENV_FILE = str(DEFAULT_ENV_PATH.absolute()) if DEFAULT_ENV_PATH.exists() else ""
load_dotenv(ENV_FILE)

Expand All @@ -55,14 +58,17 @@
# 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"))
BONFIRE_DEFAULT_FALLBACK_REF_ENV = str(
os.getenv("BONFIRE_DEFAULT_FALLBACK_REF_ENV", "insights-stage")
)

ELASTICSEARCH_HOST = os.getenv("ELASTICSEARCH_HOST", DEFAULT_ELASTICSEARCH_HOST)
ELASTICSEARCH_APIKEY = os.getenv("ELASTICSEARCH_APIKEY")
ENABLE_TELEMETRY = os.getenv("ENABLE_TELEMETRY", DEFAULT_ENABLE_TELEMETRY).lower() == "true"

DEFAULT_FRONTEND_DEPENDENCIES = (
"chrome-service",
Expand Down
85 changes: 85 additions & 0 deletions bonfire/elastic_logging.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
from datetime import datetime as dt
import logging
import json
import requests
import sys
import uuid
from concurrent.futures import ThreadPoolExecutor

import bonfire.config as conf


log = logging.getLogger(__name__)


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
)
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)

self.es_telemetry.info(log_message)


class AsyncElasticsearchHandler(logging.Handler):
def __init__(self, es_url):
super().__init__()
self.es_url = es_url
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,
"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.start_time).total_seconds()

log_entry = {"log": self.format(record), "metadata": self.metadata}
if conf.ENABLE_TELEMETRY:
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
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.raise_for_status()
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
1 change: 1 addition & 0 deletions bonfire/namespaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@


log = logging.getLogger(__name__)

TIME_FMT = "%Y-%m-%dT%H:%M:%SZ"


Expand Down