Python logging.html() Examples
The following are 30
code examples of logging.html().
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example.
You may also want to check out all available functions/classes of the module
logging
, or try the search function
.
Example #1
Source File: local_cloudwatch_handler.py From greengo with MIT License | 6 votes |
def _send_to_local_cw(self): # construct a putLogEvents request and send it # http://boto3.readthedocs.io/en/latest/reference/services/logs.html#CloudWatchLogs.Client.put_log_events request_data = { 'logGroupName': self.log_group_name, 'logStreamName': 'fromPythonAppender', 'logEvents': self.events_buffer } request = Request(LOCAL_CLOUDWATCH_ENDPOINT, json.dumps(request_data).encode('utf-8')) request.add_header(HEADER_AUTH_TOKEN, self.auth_token) try: urlopen(request) finally: # This will run whether urlopen throws an exception or not. It will # not prevent an exception from being raised however, so if any # exception occurs during the request to Localwatch (i.e. we get a # 503), the logs buffered here will be dropped. self._clear_buffer()
Example #2
Source File: loghandler.py From quay with Apache License 2.0 | 6 votes |
def _json_default(obj): """ Coerce everything to strings. All objects representing time get output as ISO8601. """ if isinstance(obj, (datetime.date, datetime.time, datetime.datetime)): return obj.isoformat() elif isinstance(obj, Exception): return "Exception: %s" % str(obj) return str(obj) # skip natural LogRecord attributes # http://docs.python.org/library/logging.html#logrecord-attributes
Example #3
Source File: _listener.py From pywbem with GNU Lesser General Public License v2.1 | 6 votes |
def send_success_response(self, msgid, methodname): """Send a CIM-XML response message back to the WBEM server that indicates success.""" resp_xml = _cim_xml.CIM( _cim_xml.MESSAGE( _cim_xml.SIMPLEEXPRSP( _cim_xml.EXPMETHODRESPONSE( methodname), ), # noqa: E123 msgid, IMPLEMENTED_PROTOCOL_VERSION), IMPLEMENTED_CIM_VERSION, IMPLEMENTED_DTD_VERSION) resp_body = '<?xml version="1.0" encoding="utf-8" ?>\n' + \ resp_xml.toxml() if isinstance(resp_body, six.text_type): resp_body = resp_body.encode("utf-8") http_code = 200 self.send_response(http_code, http_client.responses.get(http_code, '')) self.send_header("Content-Type", "text/html") self.send_header("Content-Length", str(len(resp_body))) self.send_header("CIMExport", "MethodResponse") self.end_headers() self.wfile.write(resp_body)
Example #4
Source File: loghandler.py From quay with Apache License 2.0 | 6 votes |
def __init__(self, *args, **kwargs): """ :param json_default: a function for encoding non-standard objects as outlined in http://docs.python.org/2/library/json.html :param json_encoder: optional custom encoder :param json_serializer: a :meth:`json.dumps`-compatible callable that will be used to serialize the log record. :param prefix: an optional key prefix to nest logs """ self.json_default = kwargs.pop("json_default", _json_default) self.json_encoder = kwargs.pop("json_encoder", None) self.json_serializer = kwargs.pop("json_serializer", json.dumps) self.default_values = kwargs.pop("default_extra", {}) self.prefix_key = kwargs.pop("prefix_key", "data") logging.Formatter.__init__(self, *args, **kwargs) self._fmt_parameters = self._parse_format_string() self._skip_fields = set(self._fmt_parameters) self._skip_fields.update(RESERVED_ATTRS)
Example #5
Source File: __init__.py From ver-observer with GNU General Public License v3.0 | 6 votes |
def setup_default_logger(logfile=None, level=logging.DEBUG, formatter=None, maxBytes=0, backupCount=0): """ Deprecated. Use `logzero.loglevel(..)`, `logzero.logfile(..)`, etc. Globally reconfigures the default `logzero.logger` instance. Usage: .. code-block:: python from logzero import logger, setup_default_logger setup_default_logger(level=logging.WARN) logger.info("hello") # this will not be displayed anymore because minimum loglevel was set to WARN :arg string logfile: If set, also write logs to the specified filename. :arg int level: Minimum `logging-level <https://docs.python.org/2/library/logging.html#logging-levels>`_ to display (default: `logging.DEBUG`). :arg Formatter formatter: `Python logging Formatter object <https://docs.python.org/2/library/logging.html#formatter-objects>`_ (by default uses the internal LogFormatter). :arg int maxBytes: Size of the logfile when rollover should occur. Defaults to 0, rollover never occurs. :arg int backupCount: Number of backups to keep. Defaults to 0, rollover never occurs. """ global logger logger = setup_logger(name=LOGZERO_DEFAULT_LOGGER, logfile=logfile, level=level, formatter=formatter) return logger
Example #6
Source File: __init__.py From logzero with MIT License | 6 votes |
def formatter(formatter, update_custom_handlers=False): """ Set the formatter for all handlers of the default logger (``logzero.logger``). This reconfigures only the logzero internal handlers by default, but you can also reconfigure custom handlers by using ``update_custom_handlers=True``. Beware that setting a formatter which uses colors also may write the color codes to logfiles. :arg Formatter formatter: `Python logging Formatter object <https://docs.python.org/2/library/logging.html#formatter-objects>`_ (by default uses the internal LogFormatter). :arg bool update_custom_handlers: If you added custom handlers to this logger and want this to update them too, you need to set ``update_custom_handlers`` to `True` """ for handler in list(logger.handlers): if hasattr(handler, LOGZERO_INTERNAL_LOGGER_ATTR) or update_custom_handlers: handler.setFormatter(formatter) global _formatter _formatter = formatter
Example #7
Source File: __init__.py From logzero with MIT License | 6 votes |
def loglevel(level=logging.DEBUG, update_custom_handlers=False): """ Set the minimum loglevel for the default logger (`logzero.logger`). This reconfigures only the internal handlers of the default logger (eg. stream and logfile). You can also update the loglevel for custom handlers by using `update_custom_handlers=True`. :arg int level: Minimum `logging-level <https://docs.python.org/2/library/logging.html#logging-levels>`_ to display (default: `logging.DEBUG`). :arg bool update_custom_handlers: If you added custom handlers to this logger and want this to update them too, you need to set `update_custom_handlers` to `True` """ logger.setLevel(level) # Reconfigure existing internal handlers for handler in list(logger.handlers): if hasattr(handler, LOGZERO_INTERNAL_LOGGER_ATTR) or update_custom_handlers: # Don't update the loglevel if this handler uses a custom one if hasattr(handler, LOGZERO_INTERNAL_HANDLER_IS_CUSTOM_LOGLEVEL): continue # Update the loglevel for all default handlers handler.setLevel(level) global _loglevel _loglevel = level
Example #8
Source File: __init__.py From logzero with MIT License | 6 votes |
def setup_default_logger(logfile=None, level=logging.DEBUG, formatter=None, maxBytes=0, backupCount=0, disableStderrLogger=False): """ Deprecated. Use `logzero.loglevel(..)`, `logzero.logfile(..)`, etc. Globally reconfigures the default `logzero.logger` instance. Usage: .. code-block:: python from logzero import logger, setup_default_logger setup_default_logger(level=logging.WARN) logger.info("hello") # this will not be displayed anymore because minimum loglevel was set to WARN :arg string logfile: If set, also write logs to the specified filename. :arg int level: Minimum `logging-level <https://docs.python.org/2/library/logging.html#logging-levels>`_ to display (default: `logging.DEBUG`). :arg Formatter formatter: `Python logging Formatter object <https://docs.python.org/2/library/logging.html#formatter-objects>`_ (by default uses the internal LogFormatter). :arg int maxBytes: Size of the logfile when rollover should occur. Defaults to 0, rollover never occurs. :arg int backupCount: Number of backups to keep. Defaults to 0, rollover never occurs. :arg bool disableStderrLogger: Should the default stderr logger be disabled. Defaults to False. """ global logger logger = setup_logger(name=LOGZERO_DEFAULT_LOGGER, logfile=logfile, level=level, formatter=formatter, disableStderrLogger=disableStderrLogger) return logger
Example #9
Source File: run.py From mutatest with MIT License | 6 votes |
def capture_output(log_level: int) -> bool: """Utility function used in subprocess for captured output. Available log levels are: https://docs.python.org/3/library/logging.html#levels 10 is the value for Debug, so if it's not "DEBUG", return true and capture output. Args: log_level: the logging level Returns: Bool indicator on capturing output """ return log_level != 10 #################################################################################################### # CLEAN TRIAL RUNNING FUNCTIONS ####################################################################################################
Example #10
Source File: log.py From naz with MIT License | 6 votes |
def _heartbeat(self) -> None: if not self.heartbeatInterval: return # check if `heartbeatInterval` seconds have passed. # if they have, emit a heartbeat log record to the target handler _now = time.monotonic() _diff = _now - self._s_time if _diff >= self.heartbeatInterval: self._s_time = _now # see: https://docs.python.org/3/library/logging.html#logging.LogRecord record = logging.makeLogRecord( { "level": logging.INFO, "name": "BreachHandler", "pathname": ".../naz/naz/log.py", "func": "BreachHandler._heartbeat", "msg": { "event": "naz.BreachHandler.heartbeat", "heartbeatInterval": self.heartbeatInterval, }, } ) self.target.emit(record=record) # type: ignore # pytype: disable=attribute-error
Example #11
Source File: log.py From naz with MIT License | 6 votes |
def _formatTime(self) -> str: """ Return the creation time of the specified log event as formatted text. This code is borrowed from: https://docs.python.org/3/library/logging.html#logging.Formatter.formatTime The basic behaviour is as follows: an ISO8601-like (or RFC 3339-like) format is used. This function uses `time.localtime()` to convert the creation time to a tuple. """ _converter = time.localtime _formatter = logging.Formatter() now = time.time() msecs = (now - int(now)) * 1000 ct = _converter(now) # type: ignore t = time.strftime(_formatter.default_time_format, ct) s = _formatter.default_msec_format % (t, msecs) return s
Example #12
Source File: local_cloudwatch_handler.py From aws-builders-fair-projects with Apache License 2.0 | 6 votes |
def write(self, data): data = str(data) if data == '\n': # when print(data) is invoked, it invokes write() twice. First, # writes the data, then writes a new line. This is to avoid # emitting log record with just a new-line character. return # creates https://docs.python.org/2/library/logging.html#logrecord-objects file_name, line_number = inspect.getouterframes(inspect.currentframe())[1][1:3] record = logging.makeLogRecord({"created": time.time(), "msg": data, "filename": os.path.basename(file_name), "lineno": line_number, "levelname": "DEBUG", "levelno": logging.DEBUG}) self.emit(record)
Example #13
Source File: log.py From ARK with MIT License | 6 votes |
def tlog(self, level, text, *args): """ 记录文本日志信息,较高级别的日志(FATAL)会被收集到总控统一存储以备后续追查。 实际的文本日志组装和输出是由标准库\ `logging — Logging facility for Python <https://docs.python.org/2.7/library/logging.html>`_\ 提供的 :param int level: 日志级别,级别包括:DEBUG < INFO < WARNING < ERROR < CRITICAL :param obj text: 要输出的文本信息,通过python字符串的%语法可以获得类似c语言printf的效果 :param args: 格式化字符串中占位符对应的变量值,如果变量是对象则打印成json :returns: 无返回 :rtype: None """ logger = logging.getLogger(self._log_name) texts, json_args = self._format_request(logger.getEffectiveLevel(), text, args) if len(json_args) > 0: logger.log(level, texts, *json_args) else: logger.log(level, texts)
Example #14
Source File: local_cloudwatch_handler.py From greengo with MIT License | 6 votes |
def write(self, data): data = str(data) if data == '\n': # when print(data) is invoked, it invokes write() twice. First, # writes the data, then writes a new line. This is to avoid # emitting log record with just a new-line character. return # creates https://docs.python.org/2/library/logging.html#logrecord-objects file_name, line_number = inspect.getouterframes(inspect.currentframe())[1][1:3] record = logging.makeLogRecord({"created": time.time(), "msg": data, "filename": os.path.basename(file_name), "lineno": line_number, "levelname": "DEBUG", "levelno": logging.DEBUG}) self.emit(record)
Example #15
Source File: __init__.py From ver-observer with GNU General Public License v3.0 | 6 votes |
def loglevel(level=logging.DEBUG, update_custom_handlers=False): """ Set the minimum loglevel for the default logger (`logzero.logger`). This reconfigures only the internal handlers of the default logger (eg. stream and logfile). You can also update the loglevel for custom handlers by using `update_custom_handlers=True`. :arg int level: Minimum `logging-level <https://docs.python.org/2/library/logging.html#logging-levels>`_ to display (default: `logging.DEBUG`). :arg bool update_custom_handlers: If you added custom handlers to this logger and want this to update them too, you need to set `update_custom_handlers` to `True` """ logger.setLevel(level) # Reconfigure existing internal handlers for handler in list(logger.handlers): if hasattr(handler, LOGZERO_INTERNAL_LOGGER_ATTR) or update_custom_handlers: # Don't update the loglevel if this handler uses a custom one if hasattr(handler, LOGZERO_INTERNAL_HANDLER_IS_CUSTOM_LOGLEVEL): continue # Update the loglevel for all default handlers handler.setLevel(level) global _loglevel _loglevel = level
Example #16
Source File: logger.py From geocube with BSD 3-Clause "New" or "Revised" License | 6 votes |
def log_to_console(status=True, level=None): """Log events to the console. Parameters ---------- status: bool, optional, default=True Whether logging to console should be turned on(True) or off(False) level: string, optional, default=None Level of logging; whichever level is chosen all higher levels will be logged. See: https://docs.python.org/2/library/logging.html#levels """ set_log_level(level) if status: console_handler = logging.StreamHandler() formatter = logging.Formatter(_LOGGER_FORMAT_STR) console_handler.setFormatter(formatter) _LOGGER.addHandler(console_handler) else: _remove_log_handler(logging.StreamHandler)
Example #17
Source File: __init__.py From ver-observer with GNU General Public License v3.0 | 6 votes |
def formatter(formatter, update_custom_handlers=False): """ Set the formatter for all handlers of the default logger (``logzero.logger``). This reconfigures only the logzero internal handlers by default, but you can also reconfigure custom handlers by using ``update_custom_handlers=True``. Beware that setting a formatter which uses colors also may write the color codes to logfiles. :arg Formatter formatter: `Python logging Formatter object <https://docs.python.org/2/library/logging.html#formatter-objects>`_ (by default uses the internal LogFormatter). :arg bool update_custom_handlers: If you added custom handlers to this logger and want this to update them too, you need to set ``update_custom_handlers`` to `True` """ for handler in list(logger.handlers): if hasattr(handler, LOGZERO_INTERNAL_LOGGER_ATTR) or update_custom_handlers: handler.setFormatter(formatter) global _formatter _formatter = formatter
Example #18
Source File: instarecon.py From instarecon with MIT License | 6 votes |
def __init__(self, nameserver=None, timeout=None, shodan_key=None, verbose=0): self.targets = set() self.bad_targets = set() if nameserver: lookup.dns_resolver.nameservers = [nameserver] if timeout: lookup.dns_resolver.timeout = timeout lookup.dns_resolver.lifetime = timeout if shodan_key: lookup.shodan_key = shodan_key # https://docs.python.org/2/library/logging.html#logging-levels logging_level = 40 # ERROR log_format = "[-] %(levelname)s: %(message)s" if verbose == 1: logging_level = 30 # WARNING elif verbose == 2: logging_level = 20 # INFO elif verbose > 2: logging_level = 10 # DEBUG log_format = "[-] %(levelname)s:%(module)s:%(funcName)s:%(lineno)d: %(message)s" logging.basicConfig(format=log_format, level=logging_level)
Example #19
Source File: log.py From st2 with Apache License 2.0 | 6 votes |
def decorate_log_method(func): @wraps(func) def func_wrapper(*args, **kwargs): # Prefix extra keys with underscore if 'extra' in kwargs: kwargs['extra'] = prefix_dict_keys(dictionary=kwargs['extra'], prefix='_') try: return func(*args, **kwargs) except TypeError as e: # In some version of Python 2.7, logger.exception doesn't take any kwargs so we need # this hack :/ # See: # - https://docs.python.org/release/2.7.3/library/logging.html#logging.Logger.exception # - https://docs.python.org/release/2.7.7/library/logging.html#logging.Logger.exception if 'got an unexpected keyword argument \'extra\'' in six.text_type(e): kwargs.pop('extra', None) return func(*args, **kwargs) raise e return func_wrapper
Example #20
Source File: reporter.py From pyswarms with MIT License | 6 votes |
def log(self, msg, lvl=logging.INFO, *args, **kwargs): """Log a message within a set level This method abstracts the logging.Logger.log() method. We use this method during major state changes, errors, or critical events during the optimization run. You can check logging levels on this `link`_. In essence, DEBUG is 10, INFO is 20, WARNING is 30, ERROR is 40, and CRITICAL is 50. .. _link: https://docs.python.org/3/library/logging.html#logging-levels Parameters ---------- msg : str Message to be logged lvl : int, optional Logging level. Default is `logging.INFO` """ self.logger.log(lvl, msg, *args, **kwargs)
Example #21
Source File: local_cloudwatch_handler.py From aws-builders-fair-projects with Apache License 2.0 | 6 votes |
def write(self, data): data = str(data) if data == '\n': # when print(data) is invoked, it invokes write() twice. First, # writes the data, then writes a new line. This is to avoid # emitting log record with just a new-line character. return # creates https://docs.python.org/2/library/logging.html#logrecord-objects file_name, line_number = inspect.getouterframes(inspect.currentframe())[1][1:3] record = logging.makeLogRecord({"created": time.time(), "msg": data, "filename": os.path.basename(file_name), "lineno": line_number, "levelname": "DEBUG", "levelno": logging.DEBUG}) self.emit(record)
Example #22
Source File: __init__.py From python-zenlog with GNU General Public License v3.0 | 6 votes |
def __init__(self, lvl=logging.DEBUG, format=None): self._lvl = lvl if not format: format = " %(log_color)s%(styledname)-8s%(reset)s | %(log_color)s%(message)s%(reset)s" self.format = format logging.root.setLevel(self._lvl) self.formatter = colorlog.ColoredFormatter(self.format) self.stream = logging.StreamHandler() self.stream.setLevel(self._lvl) self.stream.setFormatter(self.formatter) self.logger = logging.getLogger('pythonConfig') self.logger.setLevel(self._lvl) self.logger.addHandler(self.stream) self.theme = THEME self.extra = {"styledname": self.theme[self._lvl]} # the magic happens here: we use the "extra" argument documented in # https://docs.python.org/2/library/logging.html#logging.Logger.debug # to inject new items into the logging.LogRecord objects # we also create our convenience methods here
Example #23
Source File: server.py From schedule-system with MIT License | 6 votes |
def modify_logger(logger, log_file): # refer: https://docs.python.org/3.5/library/logging.html#logrecord-attributes formatter = logging.Formatter( fmt='\n'.join([ '[%(name)s] %(asctime)s.%(msecs)d', '\t%(pathname)s [line: %(lineno)d]', '\t%(processName)s[%(process)d] => %(threadName)s[%(thread)d] => %(module)s.%(filename)s:%(funcName)s()', '\t%(levelname)s: %(message)s\n' ]), datefmt='%Y-%m-%d %H:%M:%S' ) # stream_handler = logging.StreamHandler() # stream_handler.setFormatter(formatter) # logger.addHandler(stream_handler) file_handler = logging.FileHandler(log_file, mode='a', encoding='utf-8') file_handler.setFormatter(formatter) logger.addHandler(file_handler) logger.setLevel(logging.DEBUG) return logger
Example #24
Source File: local_cloudwatch_handler.py From aws-builders-fair-projects with Apache License 2.0 | 6 votes |
def _send_to_local_cw(self): # construct a putLogEvents request and send it # http://boto3.readthedocs.io/en/latest/reference/services/logs.html#CloudWatchLogs.Client.put_log_events request_data = { 'logGroupName': self.log_group_name, 'logStreamName': 'fromPythonAppender', 'logEvents': self.events_buffer } request = Request(LOCAL_CLOUDWATCH_ENDPOINT, json.dumps(request_data).encode('utf-8')) request.add_header(HEADER_AUTH_TOKEN, self.auth_token) try: urlopen(request) finally: # This will run whether urlopen throws an exception or not. It will # not prevent an exception from being raised however, so if any # exception occurs during the request to Localwatch (i.e. we get a # 503), the logs buffered here will be dropped. self._clear_buffer()
Example #25
Source File: helpers.py From pyrdp with GNU General Public License v3.0 | 6 votes |
def getLoggerPassFilters(loggerName: str) -> Logger: """ Returns a logger instance where the filters of all the parent chain are applied to it. This is needed since Filters do NOT get inherited from parent logger to child logger. See: https://docs.python.org/3/library/logging.html#filter-objects """ logger = logging.getLogger(loggerName) subLoggerNames = loggerName.split(".") filterList = [] parentLoggerName = "" for subLoggerName in subLoggerNames: parentLoggerName += subLoggerName parentLogger = logging.getLogger(parentLoggerName) filterList += parentLogger.filters parentLoggerName += "." [logger.addFilter(parentFilter) for parentFilter in filterList] return logger
Example #26
Source File: local_cloudwatch_handler.py From aws-builders-fair-projects with Apache License 2.0 | 6 votes |
def _send_to_local_cw(self): # construct a putLogEvents request and send it # http://boto3.readthedocs.io/en/latest/reference/services/logs.html#CloudWatchLogs.Client.put_log_events request_data = { 'logGroupName': self.log_group_name, 'logStreamName': 'fromPythonAppender', 'logEvents': self.events_buffer } request = Request(LOCAL_CLOUDWATCH_ENDPOINT, json.dumps(request_data).encode('utf-8')) request.add_header(HEADER_AUTH_TOKEN, self.auth_token) try: urlopen(request) finally: # This will run whether urlopen throws an exception or not. It will # not prevent an exception from being raised however, so if any # exception occurs during the request to Localwatch (i.e. we get a # 503), the logs buffered here will be dropped. self._clear_buffer()
Example #27
Source File: local_cloudwatch_handler.py From aws-builders-fair-projects with Apache License 2.0 | 6 votes |
def write(self, data): data = str(data) if data == '\n': # when print(data) is invoked, it invokes write() twice. First, # writes the data, then writes a new line. This is to avoid # emitting log record with just a new-line character. return # creates https://docs.python.org/2/library/logging.html#logrecord-objects file_name, line_number = inspect.getouterframes(inspect.currentframe())[1][1:3] record = logging.makeLogRecord({"created": time.time(), "msg": data, "filename": os.path.basename(file_name), "lineno": line_number, "levelname": "DEBUG", "levelno": logging.DEBUG}) self.emit(record)
Example #28
Source File: aws_common_utils_layer.py From aws-support-tickets-aggregator with MIT License | 5 votes |
def set_logging_level( manually_set_level=None, environment_variable_key="LOGGING_LEVEL" ): """ Set logging level according to whatever value is stored in the environment variable. Order of "if" checks is prioritized by anticipated frequency of use. See actual levels here: https://docs.python.org/3/library/logging.html#levels Provide value for either environment_variable_key or manually_set_level, not both. Defaults to using environment_variable_key 'LOGGING_LEVEL' If providing manually_set_level, please provide the string (e.g. "INFO"), not the class (e.g. logging.INFO) Returns logger object """ logger = logging.getLogger() level = ( os.environ.get(environment_variable_key) if environment_variable_key else manually_set_level ) if level == "INFO": logger.setLevel(logging.INFO) elif level == "ERROR": logger.setLevel(logging.ERROR) elif level == "WARNING": logger.setLevel(logging.WARNING) elif level == "DEBUG": logger.setLevel(logging.DEBUG) elif level == "CRITICAL": logger.setLevel(logging.CRITICAL) else: logging.error("Received level of %s, defaulting to NOTSET", level) logger.setLevel(logging.NOTSET) return logger
Example #29
Source File: Settings.py From neo-python with MIT License | 5 votes |
def set_loglevel(self, level): """ Set the minimum loglevel for all components Args: level (int): eg. logging.DEBUG or logging.ERROR. See also https://docs.python.org/2/library/logging.html#logging-levels """ self.log_level = level log_manager.config_stdio(default_level=level)
Example #30
Source File: __init__.py From aws-step-functions-data-science-sdk-python with Apache License 2.0 | 5 votes |
def set_stream_logger(level=logging.INFO): logger = logging.getLogger('stepfunctions') # setup logger config logger.setLevel(level) logger.propagate = False # avoid attaching multiple identical stream handlers logger.handlers = [] # add stream handler to logger handler = logging.StreamHandler(sys.stdout) handler.setLevel(level) handler.setFormatter(CustomColorFormatter()) logger.addHandler(handler) # http://docs.python.org/3.3/howto/logging.html#configuring-logging-for-a-library