Coverage for python/lsst/log/utils.py: 26%

49 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-02-02 06:15 -0800

1#!/usr/bin/env python 

2# 

3# LSST Data Management System 

4# 

5# Copyright 2016 AURA/LSST. 

6# 

7# This product includes software developed by the 

8# LSST Project (http://www.lsst.org/). 

9# 

10# This program is free software: you can redistribute it and/or modify 

11# it under the terms of the GNU General Public License as published by 

12# the Free Software Foundation, either version 3 of the License, or 

13# (at your option) any later version. 

14# 

15# This program is distributed in the hope that it will be useful, 

16# but WITHOUT ANY WARRANTY; without even the implied warranty of 

17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

18# GNU General Public License for more details. 

19# 

20# You should have received a copy of the LSST License Statement and 

21# the GNU General Public License along with this program. If not, 

22# see <https://www.lsstcorp.org/LegalNotices/>. 

23# 

24 

25__all__ = [ 

26 "traceSetAt", 

27 "temporaryLogLevel", 

28 "LogRedirect", 

29 "enable_notebook_logging", 

30 "disable_notebook_logging", 

31] 

32 

33from contextlib import contextmanager 

34import os 

35import sys 

36import threading 

37 

38from lsst.log import Log 

39 

40 

41def traceSetAt(name, number): 

42 """Adjusts logging level to display messages with the trace number being 

43 less than or equal to the provided value. 

44 

45 Parameters 

46 ---------- 

47 name : `str` 

48 Name of the logger. 

49 number : `int` 

50 The trace number threshold for display. 

51 """ 

52 for i in range(6): 

53 level = Log.INFO if i > number else Log.DEBUG 

54 Log.getLogger('TRACE%d.%s' % (i, name)).setLevel(level) 

55 

56 

57@contextmanager 

58def temporaryLogLevel(name, level): 

59 """A context manager that temporarily sets the level of a `Log`. 

60 

61 Parameters 

62 ---------- 

63 name : `str` 

64 Name of the log to modify. 

65 level : `int` 

66 Integer enumeration constant indicating the temporary log level. 

67 """ 

68 log = Log.getLogger(name) 

69 old = log.getLevel() 

70 log.setLevel(level) 

71 try: 

72 yield 

73 finally: 

74 log.setLevel(old) 

75 

76 

77class LogRedirect: 

78 """Redirect a logging file descriptor to a Python stream. 

79 

80 Parameters 

81 ---------- 

82 fd : `int` 

83 File descriptor number, usually 1 for standard out, the default log 

84 output location. 

85 dest : `io.TextIOBase` 

86 Destination text stream, often `sys.stderr` for ipython or Jupyter 

87 notebooks. 

88 encoding : `str` 

89 Text encoding of the data written to fd. 

90 errors : `str` 

91 Encoding error handling. 

92 

93 Notes 

94 ----- 

95 Inspired by `this Stack Overflow answer 

96 <https://stackoverflow.com/questions/41216215>`_ 

97 """ 

98 

99 def __init__(self, fd=1, dest=sys.stderr, encoding="utf-8", errors="strict"): 

100 self._fd = fd 

101 self._dest = dest 

102 # Save original filehandle so we can restore it later. 

103 self._filehandle = os.dup(fd) 

104 

105 # Redirect `fd` to the write end of the pipe. 

106 pipe_read, pipe_write = os.pipe() 

107 os.dup2(pipe_write, fd) 

108 os.close(pipe_write) 

109 

110 # This thread reads from the read end of the pipe. 

111 def consumer_thread(f, data): 

112 while True: 

113 buf = os.read(f, 1024) 

114 if not buf: 

115 break 

116 data.write(buf.decode(encoding, errors)) 

117 os.close(f) 

118 return 

119 

120 # Spawn consumer thread with the desired destination stream. 

121 self._thread = threading.Thread(target=consumer_thread, args=(pipe_read, dest)) 

122 self._thread.start() 

123 

124 def finish(self): 

125 """Stop redirecting output. 

126 """ 

127 

128 # Cleanup: flush streams, restore `fd` 

129 self._dest.flush() 

130 # This dup2 closes the saved file descriptor, which is now the write 

131 # end of the pipe, causing the thread's read to terminate 

132 os.dup2(self._filehandle, self._fd) 

133 os.close(self._filehandle) 

134 self._thread.join() 

135 

136 

137_redirect = None 

138 

139 

140def enable_notebook_logging(dest=sys.stderr): 

141 """Enable notebook output for log4cxx messages.""" 

142 global _redirect 

143 if _redirect is None: 

144 _redirect = LogRedirect(dest=dest) 

145 

146 

147def disable_notebook_logging(): 

148 """Stop notebook output for log4cxx messages.""" 

149 global _redirect 

150 if _redirect is not None: 

151 _redirect.finish() 

152 _redirect = None