Coverage for src/ensembl/utils/logging.py: 96%

25 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-02-18 11:50 +0000

1# See the NOTICE file distributed with this work for additional information 

2# regarding copyright ownership. 

3# 

4# Licensed under the Apache License, Version 2.0 (the "License"); 

5# you may not use this file except in compliance with the License. 

6# You may obtain a copy of the License at 

7# 

8# http://www.apache.org/licenses/LICENSE-2.0 

9# 

10# Unless required by applicable law or agreed to in writing, software 

11# distributed under the License is distributed on an "AS IS" BASIS, 

12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

13# See the License for the specific language governing permissions and 

14# limitations under the License. 

15"""Easy initialisation functionality to set an event logging system. 

16 

17Examples: 

18 

19 >>> import logging, pathlib 

20 >>> from ensembl.utils.logging import init_logging 

21 >>> logfile = pathlib.Path("test.log") 

22 >>> init_logging("INFO", logfile, "DEBUG") 

23 >>> logging.info("This message is written in both stderr and the log file") 

24 >>> logging.debug("This message is only written in the log file") 

25 

26""" 

27 

28from __future__ import annotations 

29 

30__all__ = [ 

31 "LogLevel", 

32 "init_logging", 

33 "init_logging_with_args", 

34] 

35 

36import argparse 

37from datetime import datetime 

38import logging 

39from typing import Optional, Union 

40 

41from ensembl.utils import StrPath 

42 

43 

44LogLevel = Union[int, str] 

45 

46 

47def formatTime( 

48 record: logging.LogRecord, 

49 datefmt: str | None = None, # pylint: disable=unused-argument 

50) -> str: 

51 """Returns the creation time of the log record in ISO8601 format. 

52 

53 Args: 

54 record: Log record to format. 

55 datefmt: Date format to use. Ignored in this implementation. 

56 """ 

57 return datetime.fromtimestamp(record.created).astimezone().isoformat(timespec="milliseconds") 

58 

59 

60def init_logging( 

61 log_level: LogLevel = "WARNING", 

62 log_file: Optional[StrPath] = None, 

63 log_file_level: LogLevel = "DEBUG", 

64 msg_format: str = "%(asctime)s [%(process)s] %(levelname)-9s %(name)-13s: %(message)s", 

65) -> None: 

66 """Initialises the logging system. 

67 

68 By default, all the log messages corresponding to `log_level` (and above) will be printed in the 

69 standard error. If `log_file` is provided, all messages of `log_file_level` level (and above) will 

70 be written into the provided file. 

71 

72 Args: 

73 log_level: Minimum logging level for the standard error. 

74 log_file: Logging file where to write logging messages besides the standard error. 

75 log_file_level: Minimum logging level for the logging file. 

76 msg_format: A format string for the logged output as a whole. More information: 

77 https://docs.python.org/3/library/logging.html#logrecord-attributes 

78 

79 """ 

80 # Define new formatter used for handlers 

81 formatter = logging.Formatter(msg_format) 

82 formatter.formatTime = formatTime # type: ignore[method-assign] 

83 # Configure the basic logging system, setting the root logger to the minimum log level available 

84 # to avoid filtering messages in any handler due to "parent delegation". Also close and remove any 

85 # existing handlers before setting this configuration. 

86 logging.basicConfig(level="DEBUG", force=True) 

87 # Set the correct log level and format of the new StreamHandler (by default the latter is set to NOTSET) 

88 logging.root.handlers[0].setLevel(log_level) 

89 logging.root.handlers[0].setFormatter(formatter) 

90 if log_file: 

91 # Create the log file handler and add it to the root logger 

92 file_handler = logging.FileHandler(log_file) 

93 file_handler.setLevel(log_file_level) 

94 file_handler.setFormatter(formatter) 

95 logging.root.addHandler(file_handler) 

96 

97 

98def init_logging_with_args(args: argparse.Namespace) -> None: 

99 """Processes the Namespace object provided to call `init_logging()` with the correct arguments. 

100 

101 Args: 

102 args: Namespace populated by an argument parser. 

103 

104 """ 

105 args_dict = vars(args) 

106 log_args = {x: args_dict[x] for x in ["log_level", "log_file", "log_file_level"] if x in args_dict} 

107 init_logging(**log_args)