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