xref: /aosp_15_r20/external/cronet/build/fuchsia/test/log_manager.py (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1#!/usr/bin/env vpython3
2# Copyright 2022 The Chromium Authors
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5"""Reads log data from a device."""
6
7import argparse
8import os
9import subprocess
10import sys
11
12from contextlib import AbstractContextManager
13from typing import Iterable, Optional, TextIO
14
15from common import catch_sigterm, read_package_paths, register_common_args, \
16                   register_device_args, run_continuous_ffx_command, \
17                   stop_ffx_daemon, wait_for_sigterm
18from compatible_utils import running_unattended
19from ffx_integration import ScopedFfxConfig, run_symbolizer
20
21
22class LogManager(AbstractContextManager):
23    """Handles opening and closing file streams for logging purposes."""
24
25    def __init__(self, logs_dir: Optional[str]) -> None:
26        self._logs_dir = logs_dir
27
28        # A dictionary with the log file path as the key and a file stream as
29        # value.
30        self._log_files = {}
31        self._log_procs = []
32        self._scoped_ffx_log = None
33
34        if self._logs_dir:
35            self._scoped_ffx_log = ScopedFfxConfig('log.dir', self._logs_dir)
36
37    def __enter__(self):
38        if self._scoped_ffx_log:
39            self._scoped_ffx_log.__enter__()
40            # log.dir change always requires the restarting of the daemon.
41            # In the test fleet with running_unattended being true, we
42            # explicitly disallow the daemon from automatically starting, and
43            # do all the configuration before starting the daemon.
44            # But in local development workflow, we help the developers to
45            # restart the daemon to ensure the change of log.dir taking effect.
46            if not running_unattended():
47                stop_ffx_daemon()
48
49        return self
50
51    def is_logging_enabled(self) -> bool:
52        """Check whether logging is turned on."""
53
54        return self._logs_dir is not None
55
56    def add_log_process(self, process: subprocess.Popen) -> None:
57        """Register a logging process to LogManager to be killed at LogManager
58        teardown."""
59
60        self._log_procs.append(process)
61
62    def open_log_file(self, log_file_name: str) -> TextIO:
63        """Open a file stream with log_file_name in the logs directory."""
64
65        if not self._logs_dir:
66            raise Exception('Logging directory is not specified.')
67        log_file_path = os.path.join(self._logs_dir, log_file_name)
68        log_file = open(log_file_path, 'w', buffering=1)
69        self._log_files[log_file_path] = log_file
70        return log_file
71
72    def stop(self):
73        """Stop all active logging instances."""
74
75        for proc in self._log_procs:
76            proc.kill()
77        for log in self._log_files.values():
78            log.close()
79
80    def __exit__(self, exc_type, exc_value, traceback):
81        self.stop()
82        if self._scoped_ffx_log:
83            self._scoped_ffx_log.__exit__(exc_type, exc_value, traceback)
84            if not running_unattended():
85                stop_ffx_daemon()
86
87
88def start_system_log(log_manager: LogManager,
89                     log_to_stdout: bool,
90                     pkg_paths: Optional[Iterable[str]] = None,
91                     log_args: Optional[Iterable[str]] = None,
92                     target_id: Optional[str] = None) -> None:
93    """
94    Start system logging.
95
96    Args:
97        log_manager: A LogManager class that manages the log file and process.
98        log_to_stdout: If set to True, print logs directly to stdout.
99        pkg_paths: Path to the packages
100        log_args: Arguments forwarded to `ffx log` command.
101        target_id: Specify a target to use.
102    """
103
104    if not log_manager.is_logging_enabled() and not log_to_stdout:
105        return
106    symbol_paths = None
107    if pkg_paths:
108        symbol_paths = []
109
110        # Locate debug symbols for each package.
111        for pkg_path in pkg_paths:
112            assert os.path.isfile(pkg_path), '%s does not exist' % pkg_path
113            symbol_paths.append(
114                os.path.join(os.path.dirname(pkg_path), 'ids.txt'))
115
116    if log_to_stdout:
117        system_log = sys.stdout
118    else:
119        system_log = log_manager.open_log_file('system_log')
120    log_cmd = ['log', '--symbolize', 'off', '--no-color']
121    if log_args:
122        log_cmd.extend(log_args)
123    if symbol_paths:
124        log_proc = run_continuous_ffx_command(log_cmd,
125                                              target_id,
126                                              stdout=subprocess.PIPE)
127        log_manager.add_log_process(log_proc)
128        log_manager.add_log_process(
129            run_symbolizer(symbol_paths, log_proc.stdout, system_log))
130    else:
131        log_manager.add_log_process(
132            run_continuous_ffx_command(log_cmd, target_id, stdout=system_log))
133
134
135def main():
136    """Stand-alone function for fetching system logs and print to terminal.
137    Runs until the process is killed or interrupted (i.e. user presses CTRL-C).
138    """
139
140    catch_sigterm()
141    parser = argparse.ArgumentParser()
142    register_common_args(parser)
143    register_device_args(parser)
144    parser.add_argument('--packages',
145                        action='append',
146                        help='Name of the packages to symbolize.')
147    manager_args, system_log_args = parser.parse_known_args()
148    if manager_args.packages and not manager_args.out_dir:
149        raise ValueError('--out-dir must be specified to symbolize packages.')
150    package_paths = []
151    if manager_args.packages:
152        for package in manager_args.packages:
153            package_paths.extend(
154                read_package_paths(manager_args.out_dir, package))
155    with LogManager(None) as log_manager:
156        start_system_log(log_manager, True, package_paths, system_log_args,
157                         manager_args.target_id)
158        wait_for_sigterm()
159
160
161if __name__ == '__main__':
162    sys.exit(main())
163