xref: /aosp_15_r20/external/toolchain-utils/cros_utils/logger.py (revision 760c253c1ed00ce9abd48f8546f08516e57485fe)
1*760c253cSXin Li# Copyright 2019 The ChromiumOS Authors
2*760c253cSXin Li# Use of this source code is governed by a BSD-style license that can be
3*760c253cSXin Li# found in the LICENSE file.
4*760c253cSXin Li
5*760c253cSXin Li"""Logging helper module."""
6*760c253cSXin Li
7*760c253cSXin Li
8*760c253cSXin Li# System modules
9*760c253cSXin Liimport os.path
10*760c253cSXin Liimport sys
11*760c253cSXin Liimport traceback
12*760c253cSXin Lifrom typing import Union
13*760c253cSXin Li
14*760c253cSXin Li
15*760c253cSXin Li# TODO([email protected]): Use GetRoot from misc
16*760c253cSXin Lidef GetRoot(scr_name):
17*760c253cSXin Li    """Break up pathname into (dir+name)."""
18*760c253cSXin Li    abs_path = os.path.abspath(scr_name)
19*760c253cSXin Li    return (os.path.dirname(abs_path), os.path.basename(abs_path))
20*760c253cSXin Li
21*760c253cSXin Li
22*760c253cSXin Liclass Logger:
23*760c253cSXin Li    """Logging helper class."""
24*760c253cSXin Li
25*760c253cSXin Li    MAX_LOG_FILES = 10
26*760c253cSXin Li
27*760c253cSXin Li    def __init__(self, rootdir, basefilename, print_console, subdir="logs"):
28*760c253cSXin Li        logdir = os.path.join(rootdir, subdir)
29*760c253cSXin Li        basename = os.path.join(logdir, basefilename)
30*760c253cSXin Li
31*760c253cSXin Li        try:
32*760c253cSXin Li            os.makedirs(logdir)
33*760c253cSXin Li        except OSError:
34*760c253cSXin Li            pass
35*760c253cSXin Li            # print("Warning: Logs directory '%s' already exists." % logdir)
36*760c253cSXin Li
37*760c253cSXin Li        self.print_console = print_console
38*760c253cSXin Li
39*760c253cSXin Li        self._CreateLogFileHandles(basename)
40*760c253cSXin Li
41*760c253cSXin Li        self._WriteTo(self.cmdfd, " ".join(sys.argv), True)
42*760c253cSXin Li
43*760c253cSXin Li    def _AddSuffix(self, basename, suffix):
44*760c253cSXin Li        return "%s%s" % (basename, suffix)
45*760c253cSXin Li
46*760c253cSXin Li    def _FindSuffix(self, basename):
47*760c253cSXin Li        timestamps = []
48*760c253cSXin Li        found_suffix = None
49*760c253cSXin Li        for i in range(self.MAX_LOG_FILES):
50*760c253cSXin Li            suffix = str(i)
51*760c253cSXin Li            suffixed_basename = self._AddSuffix(basename, suffix)
52*760c253cSXin Li            cmd_file = "%s.cmd" % suffixed_basename
53*760c253cSXin Li            if not os.path.exists(cmd_file):
54*760c253cSXin Li                found_suffix = suffix
55*760c253cSXin Li                break
56*760c253cSXin Li            timestamps.append(os.stat(cmd_file).st_mtime)
57*760c253cSXin Li
58*760c253cSXin Li        if found_suffix:
59*760c253cSXin Li            return found_suffix
60*760c253cSXin Li
61*760c253cSXin Li        # Try to pick the oldest file with the suffix and return that one.
62*760c253cSXin Li        suffix = str(timestamps.index(min(timestamps)))
63*760c253cSXin Li        # print ("Warning: Overwriting log file: %s" %
64*760c253cSXin Li        #       self._AddSuffix(basename, suffix))
65*760c253cSXin Li        return suffix
66*760c253cSXin Li
67*760c253cSXin Li    def _CreateLogFileHandle(self, name):
68*760c253cSXin Li        fd = None
69*760c253cSXin Li        try:
70*760c253cSXin Li            fd = open(name, "w", encoding="utf-8")
71*760c253cSXin Li        except IOError:
72*760c253cSXin Li            print("Warning: could not open %s for writing." % name)
73*760c253cSXin Li        return fd
74*760c253cSXin Li
75*760c253cSXin Li    def _CreateLogFileHandles(self, basename):
76*760c253cSXin Li        suffix = self._FindSuffix(basename)
77*760c253cSXin Li        suffixed_basename = self._AddSuffix(basename, suffix)
78*760c253cSXin Li
79*760c253cSXin Li        self.cmdfd = self._CreateLogFileHandle("%s.cmd" % suffixed_basename)
80*760c253cSXin Li        self.stdout = self._CreateLogFileHandle("%s.out" % suffixed_basename)
81*760c253cSXin Li        self.stderr = self._CreateLogFileHandle("%s.err" % suffixed_basename)
82*760c253cSXin Li
83*760c253cSXin Li        self._CreateLogFileSymlinks(basename, suffixed_basename)
84*760c253cSXin Li
85*760c253cSXin Li    # Symlink unsuffixed basename to currently suffixed one.
86*760c253cSXin Li    def _CreateLogFileSymlinks(self, basename, suffixed_basename):
87*760c253cSXin Li        try:
88*760c253cSXin Li            for extension in ["cmd", "out", "err"]:
89*760c253cSXin Li                src_file = "%s.%s" % (
90*760c253cSXin Li                    os.path.basename(suffixed_basename),
91*760c253cSXin Li                    extension,
92*760c253cSXin Li                )
93*760c253cSXin Li                dest_file = "%s.%s" % (basename, extension)
94*760c253cSXin Li                if os.path.exists(dest_file):
95*760c253cSXin Li                    os.remove(dest_file)
96*760c253cSXin Li                os.symlink(src_file, dest_file)
97*760c253cSXin Li        except Exception as ex:
98*760c253cSXin Li            print("Exception while creating symlinks: %s" % str(ex))
99*760c253cSXin Li
100*760c253cSXin Li    def _WriteTo(self, fd, msg, flush):
101*760c253cSXin Li        if fd:
102*760c253cSXin Li            fd.write(msg)
103*760c253cSXin Li            if flush:
104*760c253cSXin Li                fd.flush()
105*760c253cSXin Li
106*760c253cSXin Li    def LogStartDots(self, print_to_console=True):
107*760c253cSXin Li        term_fd = self._GetStdout(print_to_console)
108*760c253cSXin Li        if term_fd:
109*760c253cSXin Li            term_fd.flush()
110*760c253cSXin Li            term_fd.write(". ")
111*760c253cSXin Li            term_fd.flush()
112*760c253cSXin Li
113*760c253cSXin Li    def LogAppendDot(self, print_to_console=True):
114*760c253cSXin Li        term_fd = self._GetStdout(print_to_console)
115*760c253cSXin Li        if term_fd:
116*760c253cSXin Li            term_fd.write(". ")
117*760c253cSXin Li            term_fd.flush()
118*760c253cSXin Li
119*760c253cSXin Li    def LogEndDots(self, print_to_console=True):
120*760c253cSXin Li        term_fd = self._GetStdout(print_to_console)
121*760c253cSXin Li        if term_fd:
122*760c253cSXin Li            term_fd.write("\n")
123*760c253cSXin Li            term_fd.flush()
124*760c253cSXin Li
125*760c253cSXin Li    def LogMsg(self, file_fd, term_fd, msg, flush=True):
126*760c253cSXin Li        if file_fd:
127*760c253cSXin Li            self._WriteTo(file_fd, msg, flush)
128*760c253cSXin Li        if self.print_console:
129*760c253cSXin Li            self._WriteTo(term_fd, msg, flush)
130*760c253cSXin Li
131*760c253cSXin Li    def _GetStdout(self, print_to_console):
132*760c253cSXin Li        if print_to_console:
133*760c253cSXin Li            return sys.stdout
134*760c253cSXin Li        return None
135*760c253cSXin Li
136*760c253cSXin Li    def _GetStderr(self, print_to_console):
137*760c253cSXin Li        if print_to_console:
138*760c253cSXin Li            return sys.stderr
139*760c253cSXin Li        return None
140*760c253cSXin Li
141*760c253cSXin Li    def LogCmdToFileOnly(self, cmd, machine="", user=None):
142*760c253cSXin Li        if not self.cmdfd:
143*760c253cSXin Li            return
144*760c253cSXin Li
145*760c253cSXin Li        host = ("%s@%s" % (user, machine)) if user else machine
146*760c253cSXin Li        flush = True
147*760c253cSXin Li        cmd_string = "CMD (%s): %s\n" % (host, cmd)
148*760c253cSXin Li        self._WriteTo(self.cmdfd, cmd_string, flush)
149*760c253cSXin Li
150*760c253cSXin Li    def LogCmd(self, cmd, machine="", user=None, print_to_console=True):
151*760c253cSXin Li        if user:
152*760c253cSXin Li            host = "%s@%s" % (user, machine)
153*760c253cSXin Li        else:
154*760c253cSXin Li            host = machine
155*760c253cSXin Li
156*760c253cSXin Li        self.LogMsg(
157*760c253cSXin Li            self.cmdfd,
158*760c253cSXin Li            self._GetStdout(print_to_console),
159*760c253cSXin Li            "CMD (%s): %s\n" % (host, cmd),
160*760c253cSXin Li        )
161*760c253cSXin Li
162*760c253cSXin Li    def LogFatal(self, msg, print_to_console=True):
163*760c253cSXin Li        self.LogMsg(
164*760c253cSXin Li            self.stderr, self._GetStderr(print_to_console), "FATAL: %s\n" % msg
165*760c253cSXin Li        )
166*760c253cSXin Li        self.LogMsg(
167*760c253cSXin Li            self.stderr,
168*760c253cSXin Li            self._GetStderr(print_to_console),
169*760c253cSXin Li            "\n".join(traceback.format_stack()),
170*760c253cSXin Li        )
171*760c253cSXin Li        sys.exit(1)
172*760c253cSXin Li
173*760c253cSXin Li    def LogError(self, msg, print_to_console=True):
174*760c253cSXin Li        self.LogMsg(
175*760c253cSXin Li            self.stderr, self._GetStderr(print_to_console), "ERROR: %s\n" % msg
176*760c253cSXin Li        )
177*760c253cSXin Li
178*760c253cSXin Li    def LogWarning(self, msg, print_to_console=True):
179*760c253cSXin Li        self.LogMsg(
180*760c253cSXin Li            self.stderr,
181*760c253cSXin Li            self._GetStderr(print_to_console),
182*760c253cSXin Li            "WARNING: %s\n" % msg,
183*760c253cSXin Li        )
184*760c253cSXin Li
185*760c253cSXin Li    def LogOutput(self, msg, print_to_console=True):
186*760c253cSXin Li        self.LogMsg(
187*760c253cSXin Li            self.stdout, self._GetStdout(print_to_console), "OUTPUT: %s\n" % msg
188*760c253cSXin Li        )
189*760c253cSXin Li
190*760c253cSXin Li    def LogFatalIf(self, condition, msg):
191*760c253cSXin Li        if condition:
192*760c253cSXin Li            self.LogFatal(msg)
193*760c253cSXin Li
194*760c253cSXin Li    def LogErrorIf(self, condition, msg):
195*760c253cSXin Li        if condition:
196*760c253cSXin Li            self.LogError(msg)
197*760c253cSXin Li
198*760c253cSXin Li    def LogWarningIf(self, condition, msg):
199*760c253cSXin Li        if condition:
200*760c253cSXin Li            self.LogWarning(msg)
201*760c253cSXin Li
202*760c253cSXin Li    def LogCommandOutput(self, msg, print_to_console=True):
203*760c253cSXin Li        self.LogMsg(
204*760c253cSXin Li            self.stdout, self._GetStdout(print_to_console), msg, flush=False
205*760c253cSXin Li        )
206*760c253cSXin Li
207*760c253cSXin Li    def LogCommandError(self, msg, print_to_console=True):
208*760c253cSXin Li        self.LogMsg(
209*760c253cSXin Li            self.stderr, self._GetStderr(print_to_console), msg, flush=False
210*760c253cSXin Li        )
211*760c253cSXin Li
212*760c253cSXin Li    def Flush(self):
213*760c253cSXin Li        self.cmdfd.flush()
214*760c253cSXin Li        self.stdout.flush()
215*760c253cSXin Li        self.stderr.flush()
216*760c253cSXin Li
217*760c253cSXin Li
218*760c253cSXin Liclass MockLogger:
219*760c253cSXin Li    """Logging helper class."""
220*760c253cSXin Li
221*760c253cSXin Li    MAX_LOG_FILES = 10
222*760c253cSXin Li
223*760c253cSXin Li    def __init__(self, *_args, **_kwargs):
224*760c253cSXin Li        self.stdout = sys.stdout
225*760c253cSXin Li        self.stderr = sys.stderr
226*760c253cSXin Li
227*760c253cSXin Li    def _AddSuffix(self, basename, suffix):
228*760c253cSXin Li        return "%s%s" % (basename, suffix)
229*760c253cSXin Li
230*760c253cSXin Li    def _FindSuffix(self, basename):
231*760c253cSXin Li        timestamps = []
232*760c253cSXin Li        found_suffix = None
233*760c253cSXin Li        for i in range(self.MAX_LOG_FILES):
234*760c253cSXin Li            suffix = str(i)
235*760c253cSXin Li            suffixed_basename = self._AddSuffix(basename, suffix)
236*760c253cSXin Li            cmd_file = "%s.cmd" % suffixed_basename
237*760c253cSXin Li            if not os.path.exists(cmd_file):
238*760c253cSXin Li                found_suffix = suffix
239*760c253cSXin Li                break
240*760c253cSXin Li            timestamps.append(os.stat(cmd_file).st_mtime)
241*760c253cSXin Li
242*760c253cSXin Li        if found_suffix:
243*760c253cSXin Li            return found_suffix
244*760c253cSXin Li
245*760c253cSXin Li        # Try to pick the oldest file with the suffix and return that one.
246*760c253cSXin Li        suffix = str(timestamps.index(min(timestamps)))
247*760c253cSXin Li        # print ("Warning: Overwriting log file: %s" %
248*760c253cSXin Li        #       self._AddSuffix(basename, suffix))
249*760c253cSXin Li        return suffix
250*760c253cSXin Li
251*760c253cSXin Li    def _CreateLogFileHandle(self, name):
252*760c253cSXin Li        print("MockLogger: creating open file handle for %s (writing)" % name)
253*760c253cSXin Li
254*760c253cSXin Li    def _CreateLogFileHandles(self, basename):
255*760c253cSXin Li        suffix = self._FindSuffix(basename)
256*760c253cSXin Li        suffixed_basename = self._AddSuffix(basename, suffix)
257*760c253cSXin Li
258*760c253cSXin Li        print("MockLogger: opening file %s.cmd" % suffixed_basename)
259*760c253cSXin Li        print("MockLogger: opening file %s.out" % suffixed_basename)
260*760c253cSXin Li        print("MockLogger: opening file %s.err" % suffixed_basename)
261*760c253cSXin Li
262*760c253cSXin Li        self._CreateLogFileSymlinks(basename, suffixed_basename)
263*760c253cSXin Li
264*760c253cSXin Li    # Symlink unsuffixed basename to currently suffixed one.
265*760c253cSXin Li    def _CreateLogFileSymlinks(self, basename, suffixed_basename):
266*760c253cSXin Li        for extension in ["cmd", "out", "err"]:
267*760c253cSXin Li            src_file = "%s.%s" % (
268*760c253cSXin Li                os.path.basename(suffixed_basename),
269*760c253cSXin Li                extension,
270*760c253cSXin Li            )
271*760c253cSXin Li            dest_file = "%s.%s" % (basename, extension)
272*760c253cSXin Li            print(
273*760c253cSXin Li                "MockLogger: Calling os.symlink(%s, %s)" % (src_file, dest_file)
274*760c253cSXin Li            )
275*760c253cSXin Li
276*760c253cSXin Li    def _WriteTo(self, _fd, msg, _flush):
277*760c253cSXin Li        print("MockLogger: %s" % msg)
278*760c253cSXin Li
279*760c253cSXin Li    def LogStartDots(self, _print_to_console=True):
280*760c253cSXin Li        print(". ")
281*760c253cSXin Li
282*760c253cSXin Li    def LogAppendDot(self, _print_to_console=True):
283*760c253cSXin Li        print(". ")
284*760c253cSXin Li
285*760c253cSXin Li    def LogEndDots(self, _print_to_console=True):
286*760c253cSXin Li        print("\n")
287*760c253cSXin Li
288*760c253cSXin Li    def LogMsg(self, _file_fd, _term_fd, msg, **_kwargs):
289*760c253cSXin Li        print("MockLogger: %s" % msg)
290*760c253cSXin Li
291*760c253cSXin Li    def _GetStdout(self, _print_to_console):
292*760c253cSXin Li        return None
293*760c253cSXin Li
294*760c253cSXin Li    def _GetStderr(self, _print_to_console):
295*760c253cSXin Li        return None
296*760c253cSXin Li
297*760c253cSXin Li    def LogCmdToFileOnly(self, *_args, **_kwargs):
298*760c253cSXin Li        return
299*760c253cSXin Li
300*760c253cSXin Li    # def LogCmdToFileOnly(self, cmd, machine='', user=None):
301*760c253cSXin Li    #   host = ('%s@%s' % (user, machine)) if user else machine
302*760c253cSXin Li    #   cmd_string = 'CMD (%s): %s\n' % (host, cmd)
303*760c253cSXin Li    #   print('MockLogger: Writing to file ONLY: %s' % cmd_string)
304*760c253cSXin Li
305*760c253cSXin Li    def LogCmd(self, cmd, machine="", user=None, print_to_console=True):
306*760c253cSXin Li        if user:
307*760c253cSXin Li            host = "%s@%s" % (user, machine)
308*760c253cSXin Li        else:
309*760c253cSXin Li            host = machine
310*760c253cSXin Li
311*760c253cSXin Li        self.LogMsg(
312*760c253cSXin Li            0, self._GetStdout(print_to_console), "CMD (%s): %s\n" % (host, cmd)
313*760c253cSXin Li        )
314*760c253cSXin Li
315*760c253cSXin Li    def LogFatal(self, msg, print_to_console=True):
316*760c253cSXin Li        self.LogMsg(0, self._GetStderr(print_to_console), "FATAL: %s\n" % msg)
317*760c253cSXin Li        self.LogMsg(
318*760c253cSXin Li            0,
319*760c253cSXin Li            self._GetStderr(print_to_console),
320*760c253cSXin Li            "\n".join(traceback.format_stack()),
321*760c253cSXin Li        )
322*760c253cSXin Li        print("MockLogger: Calling sysexit(1)")
323*760c253cSXin Li
324*760c253cSXin Li    def LogError(self, msg, print_to_console=True):
325*760c253cSXin Li        self.LogMsg(0, self._GetStderr(print_to_console), "ERROR: %s\n" % msg)
326*760c253cSXin Li
327*760c253cSXin Li    def LogWarning(self, msg, print_to_console=True):
328*760c253cSXin Li        self.LogMsg(0, self._GetStderr(print_to_console), "WARNING: %s\n" % msg)
329*760c253cSXin Li
330*760c253cSXin Li    def LogOutput(self, msg, print_to_console=True):
331*760c253cSXin Li        self.LogMsg(0, self._GetStdout(print_to_console), "OUTPUT: %s\n" % msg)
332*760c253cSXin Li
333*760c253cSXin Li    def LogFatalIf(self, condition, msg):
334*760c253cSXin Li        if condition:
335*760c253cSXin Li            self.LogFatal(msg)
336*760c253cSXin Li
337*760c253cSXin Li    def LogErrorIf(self, condition, msg):
338*760c253cSXin Li        if condition:
339*760c253cSXin Li            self.LogError(msg)
340*760c253cSXin Li
341*760c253cSXin Li    def LogWarningIf(self, condition, msg):
342*760c253cSXin Li        if condition:
343*760c253cSXin Li            self.LogWarning(msg)
344*760c253cSXin Li
345*760c253cSXin Li    def LogCommandOutput(self, msg, print_to_console=True):
346*760c253cSXin Li        self.LogMsg(
347*760c253cSXin Li            self.stdout, self._GetStdout(print_to_console), msg, flush=False
348*760c253cSXin Li        )
349*760c253cSXin Li
350*760c253cSXin Li    def LogCommandError(self, msg, print_to_console=True):
351*760c253cSXin Li        self.LogMsg(
352*760c253cSXin Li            self.stderr, self._GetStderr(print_to_console), msg, flush=False
353*760c253cSXin Li        )
354*760c253cSXin Li
355*760c253cSXin Li    def Flush(self):
356*760c253cSXin Li        print("MockLogger: Flushing cmdfd, stdout, stderr")
357*760c253cSXin Li
358*760c253cSXin Li
359*760c253cSXin Limain_logger = None
360*760c253cSXin Li
361*760c253cSXin Li
362*760c253cSXin Lidef InitLogger(script_name, log_dir, print_console=True, mock=False):
363*760c253cSXin Li    """Initialize a global logger. To be called only once."""
364*760c253cSXin Li    # pylint: disable=global-statement
365*760c253cSXin Li    global main_logger
366*760c253cSXin Li    if main_logger:
367*760c253cSXin Li        return main_logger
368*760c253cSXin Li    rootdir, basefilename = GetRoot(script_name)
369*760c253cSXin Li    if not log_dir:
370*760c253cSXin Li        log_dir = rootdir
371*760c253cSXin Li    if not mock:
372*760c253cSXin Li        main_logger = Logger(log_dir, basefilename, print_console)
373*760c253cSXin Li    else:
374*760c253cSXin Li        main_logger = MockLogger(log_dir, basefilename, print_console)
375*760c253cSXin Li    return main_logger
376*760c253cSXin Li
377*760c253cSXin Li
378*760c253cSXin Lidef GetLogger(log_dir="", mock=False) -> Union[Logger, MockLogger]:
379*760c253cSXin Li    return InitLogger(sys.argv[0], log_dir, mock=mock)
380*760c253cSXin Li
381*760c253cSXin Li
382*760c253cSXin Lidef HandleUncaughtExceptions(fun):
383*760c253cSXin Li    """Catches all exceptions that would go outside decorated fun scope."""
384*760c253cSXin Li
385*760c253cSXin Li    def _Interceptor(*args, **kwargs):
386*760c253cSXin Li        try:
387*760c253cSXin Li            return fun(*args, **kwargs)
388*760c253cSXin Li        except Exception:
389*760c253cSXin Li            GetLogger().LogFatal(
390*760c253cSXin Li                "Uncaught exception:\n%s" % traceback.format_exc()
391*760c253cSXin Li            )
392*760c253cSXin Li
393*760c253cSXin Li    return _Interceptor
394