1# Copyright 2024, The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15import argparse 16import logging 17import os 18import signal 19import sys 20import tempfile 21 22from edit_monitor import daemon_manager 23from edit_monitor import edit_monitor 24 25 26def create_arg_parser(): 27 """Creates an instance of the default arg parser.""" 28 29 parser = argparse.ArgumentParser( 30 description=( 31 'Monitors edits in Android source code and uploads the edit logs.' 32 ), 33 add_help=True, 34 formatter_class=argparse.RawDescriptionHelpFormatter, 35 ) 36 37 parser.add_argument( 38 '--path', 39 type=str, 40 required=True, 41 help='Root path to monitor the edit events.', 42 ) 43 44 parser.add_argument( 45 '--dry_run', 46 action='store_true', 47 help='Dry run the edit monitor. This starts the edit monitor process without actually send the edit logs to clearcut.', 48 ) 49 50 parser.add_argument( 51 '--force_cleanup', 52 action='store_true', 53 help=( 54 'Instead of start a new edit monitor, force stop all existing edit' 55 ' monitors in the system. This option is only used in emergent cases' 56 ' when we want to prevent user damage by the edit monitor.' 57 ), 58 ) 59 60 parser.add_argument( 61 '--verbose', 62 action='store_true', 63 help=( 64 'Log verbose info in the log file for debugging purpose.' 65 ), 66 ) 67 68 return parser 69 70 71def configure_logging(verbose=False): 72 root_logging_dir = tempfile.mkdtemp(prefix='edit_monitor_') 73 _, log_path = tempfile.mkstemp(dir=root_logging_dir, suffix='.log') 74 75 76 log_fmt = '%(asctime)s.%(msecs)03d %(filename)s:%(lineno)s:%(levelname)s: %(message)s' 77 date_fmt = '%Y-%m-%d %H:%M:%S' 78 log_level = logging.DEBUG if verbose else logging.INFO 79 80 logging.basicConfig( 81 filename=log_path, level=log_level, format=log_fmt, datefmt=date_fmt 82 ) 83 # Filter out logs from inotify_buff to prevent log pollution. 84 logging.getLogger('watchdog.observers.inotify_buffer').addFilter( 85 lambda record: record.filename != 'inotify_buffer.py') 86 print(f'logging to file {log_path}') 87 88 89def term_signal_handler(_signal_number, _frame): 90 logging.info('Process %d received SIGTERM, Terminating...', os.getpid()) 91 sys.exit(0) 92 93 94def main(argv: list[str]): 95 args = create_arg_parser().parse_args(argv[1:]) 96 configure_logging(args.verbose) 97 if args.dry_run: 98 logging.info('This is a dry run.') 99 dm = daemon_manager.DaemonManager( 100 binary_path=argv[0], 101 daemon_target=edit_monitor.start, 102 daemon_args=(args.path, args.dry_run), 103 ) 104 105 try: 106 if args.force_cleanup: 107 dm.cleanup() 108 else: 109 dm.start() 110 dm.monitor_daemon() 111 except Exception: 112 logging.exception('Unexpected exception raised when run daemon.') 113 finally: 114 dm.stop() 115 116 117if __name__ == '__main__': 118 signal.signal(signal.SIGTERM, term_signal_handler) 119 main(sys.argv) 120