# This file is part of TALER # (C) 2014, 2015, 2016, 2019 Taler Systems SA # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 3 of the License, or (at your option) any later # version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free # Software Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301 USA # # @author Marcello Stanisci # @brief Implementation of the GNUnet logging logic. # #!/usr/bin/env python3 # GNUNET_FORCE_LOG format [component];[file];[function];[from line [to line]];loglevel import os import re import logging import datetime import inspect ## # Represent a definition for one logging action. class LogDefinition: ## # Init constructor. # # @param self the object itself. # @param component which component this definition is going to affect. # @param filename which filename this definition is going to affect. # @param function which function this definition is going to affect. # @param line_interval which line_interval this definition is going to affect. # @param loglevel which loglevel is accepted. # @param forced does this definition come from GNUNET_FORCE_LOG? def __init__(self, component, filename, function, line_interval, loglevel, forced): self.forced = forced self.component = ".*" if "" == component else component self.filename = ".*" if "" == filename else filename self.function = ".*" if "" == function else function self.line_interval = self.__parse_line_interval(line_interval) # string here self.loglevel = loglevel ## # Parse the @a line_interval from a logging definition. # # @param self the object itself. # @param line_interval the line interval value as it comes # from the user definition. The format is X[-Y]. # @return a dict with min/max fields; if max is not given, # then min == max. If the input is wrong, then just # match every line. def __parse_line_interval(self, line_interval): interval_re = "^([0-9]+)(-([0-9]+))?$" match = re.match(interval_re, line_interval) if match: return dict( min=int(match.group(1)), max=int(match.group(3) if match.group(3) else match.group(1)), ) # just match every line if bad interval was provided. return dict(min=0, max=float("inf")) ## # Represent a loglevel. # # @param self the object itself. # @param string the loglevel given as a string (DEBUG/ERROR/WARNING/...) # @param level the loglevel given as for the 'logging' module API. # @param function the native function from 'logging' module that # _actually_ outputs the log line. class GnunetLoglevel: def __init__(self, string, level, function): self.string = string self.level = level self.function = function def __str__(self): return self.string def getLevel(self): return self.level def getFunction(self): return self.function ## # The "mother" class of this module. This class is in charge of # parsing the definitions given by the user, from all the streams: # being it programmatical, or the environment. It is also in charge # of giving the right precedence to the streams: e.g. GNUNET_FORCE_LOG # takes precedence over the "setup()" method. class GnunetLogger: COMPONENT_IDX = 0 FILENAME_IDX = 1 FUNCTION_IDX = 2 LINE_INTERVAL = 3 LEVEL_IDX = 4 ## # Init contructor. # # @param self the object itself. # @param component the component name, that will be fed # to the native 'logging' API. def __init__(self, component): self.logger = logging.getLogger(component) self.ERROR = GnunetLoglevel("ERROR", logging.ERROR, self.logger.error) self.WARNING = GnunetLoglevel("WARNING", logging.WARNING, self.logger.warning) self.INFO = GnunetLoglevel("INFO", logging.INFO, self.logger.info) self.DEBUG = GnunetLoglevel("DEBUG", logging.DEBUG, self.logger.debug) self.component = component self.loglevel = None # Setting the *logging* loglevel in order to have the # chance of changing the *logger* (object) loglevel along the # execution. So this particular loglevel has no relevance # (might have been any other loglevel). logging.basicConfig(level=logging.INFO) self.no_forced_definitions = True self.definitions = list() # Give priority to the forced env. if os.environ.get("GNUNET_FORCE_LOG"): self.no_forced_definitions = False self.__parse_definitions(os.environ.get("GNUNET_FORCE_LOG"), True) if os.environ.get("GNUNET_LOG"): self.__parse_definitions(os.environ.get("GNUNET_LOG"), False) if os.environ.get("GNUNET_FORCE_LOGFILE"): filename = self.parse_filename(os.environ.get("GNUNET_FORCE_LOGFILE")) fh = logging.FileHandler(filename) self.logger.addHandler(fh) ## # Parse the filename where to write log lines. # # @param self the object itself. # @param filename the filename to parse (usually given # to the '-l' option). # @return the parse filename string (with all the dates # placeholders interpreted.) def parse_filename(self, filename): # implement {} and [] substitution. f = filename.replace("{}", self.component) f = f.replace("[]", str(os.getpid())) now = datetime.datetime.now() f = f.replace("%Y", now.strftime("%Y")) f = f.replace("%m", now.strftime("%m")) f = f.replace("%d", now.strftime("%d")) return f ## # Maps loglevels as strings, to loglevels as defined # in _this_ object. # # @param self the object itself. # @param level the string to map. # @return the loglevel native of _this_ object; defaults # to INFO, if not found in the map. def string_to_loglevel(self, level): level_map = { "ERROR": self.ERROR, "WARNING": self.WARNING, "INFO": self.INFO, "DEBUG": self.DEBUG, } # Defaults to INFO. return level_map.get(level, self.INFO) ## # Set the loglevel for this module. def setup(self, loglevel): self.loglevel = loglevel ## # Log API for users to produce logs. # # @param self the object itself. # @param message the message to log. # @param message_loglevel the loglevel associated with the message. def log(self, message, message_loglevel): caller_frame = inspect.stack()[1] filename = os.path.basename(inspect.getfile(caller_frame[0])) lineno = caller_frame.lineno function = caller_frame.function # Ordinary case (level setup + nothing forced). if self.loglevel and self.no_forced_definitions: self.logger.setLevel(level=self.loglevel.getLevel()) message_loglevel.getFunction()(message) return # We crawl through GNUNET_FORCE_LOG definitions, # or GNUNET_LOG (in case of non-forced definition # and non-given loglevel at object creation time) for defi in self.definitions: if defi.forced or not self.loglevel: if ( re.match(defi.component, self.component) and re.match(defi.filename, filename) and re.match(defi.function, function) and defi.line_interval["min"] <= lineno <= defi.line_interval["max"] ): self.logger.setLevel( level=self.string_to_loglevel(defi.loglevel).getLevel() ) message_loglevel.getFunction()(message) return # If the flow got here, then one of the following # may hold. # # (1) GNUNET_FORCE_LOG was given and no definition was # found for this component (neither forced nor unforced). # Possibly, there also exists a default loglevel. if self.loglevel: self.logger.setLevel(level=self.loglevel.getLevel()) # (2) GNUNET_FORCE_LOG was NOT given and neither was # a default loglevel, and also a unforced definition # wasn't found. Must assign a made-up loglevel. else: self.logger.setLevel(level=logging.INFO) message_loglevel.getFunction()(message) ## # Helper function that parses definitions coming from the environment. # # @param self the object itself. # @param line the definition coming from the environment. # @param forced whether the definition comes from GNUNET_FORCE_LOG or not. def __parse_definitions(self, line, forced): gfl_split = line.split("/") for component in gfl_split: gfl_split_split = component.split(";") if 5 != len(gfl_split_split): print("warning: GNUNET_(FORCE_)LOG is malformed") return definition = LogDefinition( gfl_split_split[GnunetLogger.COMPONENT_IDX], gfl_split_split[GnunetLogger.FILENAME_IDX], gfl_split_split[GnunetLogger.FUNCTION_IDX], gfl_split_split[GnunetLogger.LINE_INTERVAL], gfl_split_split[GnunetLogger.LEVEL_IDX], forced, ) self.definitions.append(definition)