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