]> git.proxmox.com Git - mirror_ifupdown2.git/blob - ifupdown2/lib/log.py
0768716bfe9836d878f0f27b6ed19d90470b0c99
[mirror_ifupdown2.git] / ifupdown2 / lib / log.py
1 # Copyright (C) 2016, 2017, 2018, 2019 Cumulus Networks, Inc. all rights reserved
2 #
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License as
5 # published by the Free Software Foundation; version 2.
6 #
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 # General Public License for more details.
11 #
12 # You should have received a copy of the GNU General Public License
13 # along with this program; if not, write to the Free Software
14 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15 # 02110-1301, USA.
16 #
17 # https://www.gnu.org/licenses/gpl-2.0-standalone.html
18 #
19 # Author:
20 # Julien Fortin, julien@cumulusnetworks.com
21 #
22
23 import os
24 import sys
25 import traceback
26
27 import logging
28 import logging.handlers
29
30 root_logger = logging.getLogger()
31
32
33 class LogManager:
34 LOGGER_NAME = "ifupdown2"
35 LOGGER_NAME_DAEMON = "ifupdown2d"
36
37 DEFAULT_TCP_LOGGING_PORT = 42422
38 DEFAULT_LOGGING_LEVEL_DAEMON = logging.INFO
39 DEFAULT_LOGGING_LEVEL_NORMAL = logging.WARNING
40
41 __instance = None
42
43 @staticmethod
44 def get_instance():
45 if not LogManager.__instance:
46 try:
47 LogManager.__instance = LogManager()
48 except Exception as e:
49 sys.stderr.write("warning: ifupdown2.Log: %s\n" % str(e))
50 traceback.print_exc()
51 return LogManager.__instance
52
53 def __init__(self):
54 """
55 Setup root logger and console handler (stderr). To enable daemon, client
56 or standalone logging please call the proper function, see:
57 "start_(daemon|client|standlone)_logging"
58 """
59 if LogManager.__instance:
60 raise RuntimeError("Log: invalid access. Please use Log.getInstance()")
61 else:
62 LogManager.__instance = self
63
64 self.__fmt = "%(levelname)s: %(message)s"
65
66 self.__debug_fmt = "%(asctime)s: %(threadName)s: %(name)s: " \
67 "%(filename)s:%(lineno)d:%(funcName)s(): " \
68 "%(levelname)s: %(message)s"
69
70 self.__root_logger = logging.getLogger()
71 self.__root_logger.name = self.LOGGER_NAME
72
73 self.__socket_handler = None
74 self.__syslog_handler = None
75 self.__console_handler = None
76
77 self.daemon = None
78
79 # by default we attach a console handler that logs on stderr
80 # the daemon can manually remove this handler on startup
81 self.__console_handler = logging.StreamHandler(sys.stderr)
82 self.__console_handler.setFormatter(logging.Formatter(self.__fmt))
83 self.__root_logger.addHandler(self.__console_handler)
84
85 if os.path.exists("/dev/log"):
86 try:
87 self.__syslog_handler = logging.handlers.SysLogHandler(
88 address="/dev/log",
89 facility=logging.handlers.SysLogHandler.LOG_DAEMON
90 )
91 self.__syslog_handler.setFormatter(logging.Formatter(self.__fmt))
92 except Exception as e:
93 sys.stderr.write("warning: syslog: %s\n" % str(e))
94 self.__syslog_handler = None
95
96 logging.addLevelName(logging.CRITICAL, "critical")
97 logging.addLevelName(logging.WARNING, "warning")
98 logging.addLevelName(logging.ERROR, "error")
99 logging.addLevelName(logging.DEBUG, "debug")
100 logging.addLevelName(logging.INFO, "info")
101
102 def set_level(self, default, error=False, warning=False, info=False, debug=False):
103 """
104 Set root handler logging level
105 :param default:
106 :param error:
107 :param warning:
108 :param info:
109 :param debug:
110 """
111 if debug:
112 log_level = logging.DEBUG
113 elif info:
114 log_level = logging.INFO
115 elif warning:
116 log_level = logging.WARNING
117 elif error:
118 log_level = logging.ERROR
119 else:
120 log_level = default
121
122 for handler in self.__root_logger.handlers:
123 handler.setLevel(log_level)
124 self.__root_logger.setLevel(log_level)
125
126 def enable_console(self):
127 """ Add console handler to root logger """
128 self.__root_logger.addHandler(self.__console_handler)
129
130 def disable_console(self):
131 """ Remove console handler from root logger """
132 self.__root_logger.removeHandler(self.__console_handler)
133
134 def enable_syslog(self):
135 """ Add syslog handler to root logger """
136 if self.__syslog_handler:
137 self.__root_logger.addHandler(self.__syslog_handler)
138
139 def disable_syslog(self):
140 """ Remove syslog handler from root logger """
141 if self.__syslog_handler:
142 self.__root_logger.removeHandler(self.__syslog_handler)
143
144 def close_log_stream(self):
145 """ Close socket to disconnect client.
146 We first have to perform this little hack: it seems like the socket is
147 not opened until data (LogRecord) are transmitted. In our most basic use
148 case (client sends "ifup -a") the daemon doesn't send back any LogRecord
149 but we can't predict that in the client. The client is already in a
150 blocking-select waiting for data on it's socket handler
151 (StreamRequestHandler). For this special case we need to manually call
152 "createSocket" to open the channel to the client so that we can properly
153 close it. That way the client can exit cleanly.
154 """
155 self.__root_logger.removeHandler(self.__socket_handler)
156 self.__socket_handler.acquire()
157 self.__socket_handler.retryTime = None
158 try:
159 if not self.__socket_handler.sock:
160 self.__socket_handler.createSocket()
161 finally:
162 self.__socket_handler.close()
163 self.__socket_handler.release()
164
165 def start_stream(self):
166 self.__root_logger.addHandler(self.__socket_handler)
167
168 def set_daemon_logging_level(self, args):
169 self.set_level(self.DEFAULT_LOGGING_LEVEL_DAEMON, info=args.verbose, debug=args.debug)
170
171 def set_request_logging_level(self, args):
172 if not hasattr(args, "syslog") or not args.syslog:
173 self.disable_syslog()
174 else:
175 self.__root_logger.removeHandler(self.__socket_handler)
176 self.set_level(self.DEFAULT_LOGGING_LEVEL_NORMAL, info=args.verbose, debug=args.debug)
177
178 def start_client_logging(self, args):
179 """ Setup root logger name and client log level
180 syslog is handled by the daemon directly
181 """
182 self.__root_logger.name = self.LOGGER_NAME
183
184 if hasattr(args, "syslog") and args.syslog:
185 self.enable_syslog()
186 self.disable_console()
187
188 self.set_level(self.DEFAULT_LOGGING_LEVEL_NORMAL, info=args.verbose, debug=args.debug)
189
190 def start_standalone_logging(self, args):
191 self.__root_logger.name = self.LOGGER_NAME
192
193 if hasattr(args, "syslog") and args.syslog:
194 self.enable_syslog()
195 self.disable_console()
196
197 self.__root_logger.removeHandler(self.__console_handler)
198
199 self.set_level(self.DEFAULT_LOGGING_LEVEL_NORMAL, info=args.verbose, debug=args.debug)
200
201 def start_daemon_logging(self, args):
202 """
203 Daemon mode initialize a socket handler to transmit logging to the
204 client, we can also do syslog logging and/or console logging (probably
205 just for debugging purpose)
206 :param args:
207 :return:
208 """
209 self.__root_logger.name = self.LOGGER_NAME_DAEMON
210 self.daemon = True
211
212 self.enable_syslog()
213
214 # Create SocketHandler for daemon-client communication
215 self.__socket_handler = logging.handlers.SocketHandler(
216 "localhost",
217 port=self.DEFAULT_TCP_LOGGING_PORT
218 )
219 self.__root_logger.addHandler(self.__socket_handler)
220
221 if not args.console:
222 self.disable_console()
223
224 self.set_daemon_logging_level(args)
225
226 def write(self, msg):
227 root_logger.info(msg)