Keep track of 'parent' operation in operation loggers

This commit is contained in:
Alexandre Aubin 2020-04-24 01:11:56 +02:00
parent d8be90165c
commit 63ff02be50

View file

@ -28,6 +28,7 @@ import os
import re import re
import yaml import yaml
import collections import collections
import glob
from datetime import datetime from datetime import datetime
from logging import FileHandler, getLogger, Formatter from logging import FileHandler, getLogger, Formatter
@ -350,6 +351,8 @@ class OperationLogger(object):
This class record logs and metadata like context or start time/end time. This class record logs and metadata like context or start time/end time.
""" """
_instances = []
def __init__(self, operation, related_to=None, **kwargs): def __init__(self, operation, related_to=None, **kwargs):
# TODO add a way to not save password on app installation # TODO add a way to not save password on app installation
self.operation = operation self.operation = operation
@ -360,6 +363,8 @@ class OperationLogger(object):
self.logger = None self.logger = None
self._name = None self._name = None
self.data_to_redact = [] self.data_to_redact = []
self.parent = self.parent_logger()
self._instances.append(self)
for filename in ["/etc/yunohost/mysql", "/etc/yunohost/psql"]: for filename in ["/etc/yunohost/mysql", "/etc/yunohost/psql"]:
if os.path.exists(filename): if os.path.exists(filename):
@ -370,6 +375,50 @@ class OperationLogger(object):
if not os.path.exists(self.path): if not os.path.exists(self.path):
os.makedirs(self.path) os.makedirs(self.path)
def parent_logger(self):
# If there are other operation logger instances
for instance in reversed(self._instances):
# Is one of these operation logger started but not yet done ?
if instance.started_at is not None and instance.ended_at is None:
# We are a child of the first one we found
return instance.name
locks = read_file("/var/run/moulinette_yunohost.lock").strip().split("\n")
# If we're the process with the lock, we're the root logger
if locks == [] or str(os.getpid()) in locks:
return None
# If we get here, we are in a yunohost command called by a yunohost
# (maybe indirectly from an app script for example...)
#
# The strategy is :
# 1. list 20 most recent log files
# 2. iterate over the PID of parent processes
# 3. see if parent process has some log file open (being actively
# written in)
# 4. if among those file, there's an operation log file, we use the id
# of the most recent file
recent_operation_logs = sorted(glob.iglob("/var/log/yunohost/categories/operation/*.log"), key=os.path.getctime, reverse=True)[:20]
import psutil
proc = psutil.Process().parent()
while proc is not None:
# We use proc.open_files() to list files opened / actively used by this proc
# We only keep files matching a recent yunohost operation log
active_logs = sorted([f.path for f in proc.open_files() if f.path in recent_operation_logs], key=os.path.getctime, reverse=True)
if active_logs != []:
# extra the log if from the full path
return os.path.basename(active_logs[0])[:-4]
else:
proc = proc.parent()
continue
# If nothing found, assume we're the root operation logger
return None
def start(self): def start(self):
""" """
Start to record logs that change the system Start to record logs that change the system
@ -456,6 +505,7 @@ class OperationLogger(object):
data = { data = {
'started_at': self.started_at, 'started_at': self.started_at,
'operation': self.operation, 'operation': self.operation,
'parent': self.parent,
} }
if self.related_to is not None: if self.related_to is not None:
data['related_to'] = self.related_to data['related_to'] = self.related_to
@ -491,8 +541,10 @@ class OperationLogger(object):
self.ended_at = datetime.utcnow() self.ended_at = datetime.utcnow()
self._error = error self._error = error
self._success = error is None self._success = error is None
if self.logger is not None: if self.logger is not None:
self.logger.removeHandler(self.file_handler) self.logger.removeHandler(self.file_handler)
self.file_handler.close()
is_api = msettings.get('interface') == 'api' is_api = msettings.get('interface') == 'api'
desc = _get_description_from_name(self.name) desc = _get_description_from_name(self.name)