wadebug/wa_actions/log_utils.py (156 lines of code) (raw):
# Copyright (c) Facebook, Inc. and its affiliates.
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
from __future__ import absolute_import, division, print_function, unicode_literals
import errno
import json
import os
import shutil
from datetime import datetime, timedelta, timezone
import docker
from wadebug import exceptions
from wadebug.config import Config
from wadebug.wa_actions import docker_utils
from wadebug.wa_actions.wabiz_api import WABizAPI
CONFIG_FILE = "wadebug.conf.yml"
OUTPUT_FOLDER = "wadebug_logs"
SUPPORT_INFO_LOG_FILE = "support-info.log"
WEB_LOG_PATH = "/var/log/whatsapp"
WEB_LOG_FILE = "web.log"
WEB_ERROR_LOG_PATH = "/var/log/lighttpd"
WEB_ERROR_LOG_FILE = "error.log"
CONTAINER_LOG_DURATION_HOURS = 3
# WA container logs use UTC timezone
CONTAINER_LOG_TIMEZONE = timezone.utc
def prepare_logs(logs_since, logs_since_format):
check_access()
logs_start_dt, logs_end_dt = get_container_logs_start_end_datetimes(
logs_since,
logs_since_format,
CONTAINER_LOG_TIMEZONE,
CONTAINER_LOG_DURATION_HOURS,
)
log_files = get_logs(logs_start_dt, logs_end_dt)
support_info_file = get_support_info()
if support_info_file:
log_files.append(support_info_file)
path = os.path.join(os.getcwd(), "wadebug_logs/")
shutil.make_archive("wadebug_logs", "zip", path)
return open(os.path.join(os.getcwd(), "wadebug_logs.zip"), "rb"), log_files
def check_access():
try:
if os.access(os.getcwd(), os.R_OK):
os.makedirs(os.path.join(os.getcwd(), OUTPUT_FOLDER))
else:
raise exceptions.FileAccessError(
"Access error: Cannot read from current directory"
)
except OSError as e:
if e.errno != errno.EEXIST:
raise exceptions.FileAccessError(
"Access error: Cannot write logs to current directory"
)
def get_container_logs_start_end_datetimes(
start_dt_str, dt_format, dt_timezone, duration_hours
):
logs_duration = timedelta(hours=duration_hours)
if start_dt_str:
start_dt = datetime.strptime(start_dt_str, dt_format).replace(
tzinfo=dt_timezone
)
return start_dt, start_dt + logs_duration
else:
end_dt = datetime.now(dt_timezone)
return end_dt - logs_duration, end_dt
def get_logs(logs_start_dt, logs_end_dt):
wa_containers = docker_utils.get_wa_containers()
log_files = []
errors = []
for wa_container in wa_containers:
try:
container_log_filename = get_container_logs(
wa_container, logs_start_dt, logs_end_dt
)
log_files.append(container_log_filename)
inspect_log_filename = get_container_inspect_logs(wa_container)
log_files.append(inspect_log_filename)
core_dump_filename = get_corecontainer_coredumps_logs(wa_container)
if core_dump_filename is not None:
log_files.append(core_dump_filename)
webapp_log, webapp_error_log = get_webcontainer_logs(wa_container)
if webapp_log is not None and webapp_error_log is not None:
log_files.append(webapp_log)
log_files.append(webapp_error_log)
except Exception as e:
print(e)
errors.append((wa_container.container, e))
if errors:
err_str = "Container: {}\nException: {}"
exception_msg = "Some logs could not be obtained:\n{}".format(
"\n".join([err_str.format(err[0].name, err) for err in errors])
)
raise exceptions.LogsNotCompleteError(exception_msg)
return [lf for lf in log_files if lf is not None]
def get_container_logs(wa_container, logs_start_dt, logs_end_dt):
container = wa_container.container
container_logs = docker_utils.get_container_logs(
container,
# docker python SDK only accepts int
int(logs_start_dt.timestamp()),
int(logs_end_dt.timestamp()),
)
log_filename = os.path.join(
OUTPUT_FOLDER, "{}-container.log".format(container.name)
)
docker_utils.write_to_file_in_binary(log_filename, container_logs)
return log_filename
def get_container_inspect_logs(wa_container):
container = wa_container.container
inspect_log_filename = os.path.join(
OUTPUT_FOLDER, "{}-inspect.log".format(container.name)
)
inspect_result = docker_utils.get_inspect_result(container)
docker_utils.write_to_file(
inspect_log_filename, json.dumps(inspect_result, indent=1)
)
return inspect_log_filename
def get_corecontainer_coredumps_logs(wa_container):
container = wa_container.container
core_dump_filename = None
if wa_container.is_coreapp():
core_dump_filename = os.path.join(
OUTPUT_FOLDER, "{}-coredump.log".format(container.name)
)
core_dump_results = docker_utils.get_core_dump_logs(container)
docker_utils.write_to_file(core_dump_filename, core_dump_results)
return core_dump_filename
def get_webcontainer_logs(wa_container):
container = wa_container.container
webapp_log_filename = None
webapp_error_log_filename = None
if wa_container.is_webapp():
webapp_log_filename = copy_additional_logs_for_webcontainer(
container, WEB_LOG_PATH, WEB_LOG_FILE
)
webapp_error_log_filename = copy_additional_logs_for_webcontainer(
container, WEB_ERROR_LOG_PATH, WEB_ERROR_LOG_PATH
)
return webapp_log_filename, webapp_error_log_filename
def copy_additional_logs_for_webcontainer(container, path, file_name):
try:
logs = docker_utils.get_archive_from_container(container, path, file_name)
path = os.path.join(OUTPUT_FOLDER, "{}-{}".format(container.name, file_name))
docker_utils.write_to_file(path, logs)
return path
except (KeyError, docker.errors.NotFound):
pass
def get_support_info():
support_info_filename = os.path.join(OUTPUT_FOLDER, SUPPORT_INFO_LOG_FILE)
try:
config = Config().values
if config:
api = WABizAPI(**config.get("webapp"))
support_info_content = api.get_support_info()
else:
return
except Exception:
return
docker_utils.write_to_file(
support_info_filename, json.dumps(support_info_content, indent=2)
)
return support_info_filename