blob: 1aafe7c9be7b749f9fb57ea49c3f9195f4856199 [file] [log] [blame]
# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import argparse
import datetime
import logging
import os
import sys
import psutil
from infra_libs import logs
from infra_libs import ts_mon
class BaseApplication(object):
"""Encapsulates common boilerplate for setting up an application.
Subclasses must implement the main() method, and will usually also implement
add_argparse_options().
By default this will initialise logging and timeseries monitoring (ts_mon)
modules.
Minimal example::
from infra_libs import app
class MyApplication(app.BaseApplication):
def main(self, opts):
# Do stuff.
if __name__ == '__main__':
MyApplication().run()
Class variables (override these in your class definition):
PROG_NAME: The program name to display in the --help message. Defaults to
sys.argv[0]. Passed to argparse.ArgumentParser.
DESCRIPTION: Text to display in the --help message. Passed to
argparse.ArgumentParser.
USES_STANDARD_LOGGING: Whether to configure the standard logging libraries.
Defaults to True.
USES_TS_MON: Whether to configure timeseries monitoring. Defaults to True.
Instance variables (use these in your application):
opts: The argparse.Namespace containing parsed commandline arguments.
"""
PROG_NAME = None
DESCRIPTION = None
USES_STANDARD_LOGGING = True
USES_TS_MON = True
def __init__(self):
self.opts = None
self.parser = None
def add_argparse_options(self, parser):
"""Register any arguments used by this application.
Override this method and call parser.add_argument().
Args:
parser: An argparse.ArgumentParser object.
"""
if self.USES_STANDARD_LOGGING:
logs.add_argparse_options(parser)
if self.USES_TS_MON:
ts_mon.add_argparse_options(parser)
def process_argparse_options(self, options):
"""Process any commandline arguments.
Args:
options: An argparse.Namespace object.
"""
if self.USES_STANDARD_LOGGING:
logs.process_argparse_options(options)
if self.USES_TS_MON:
ts_mon.process_argparse_options(options)
def main(self, opts):
"""Your application's main method.
Do the work of your application here. When this method returns the
application will exit.
Args:
opts: An argparse.Namespace containing parsed commandline options. This
is passed as an argument for convenience but is also accessible as an
instance variable (self.opts).
Return:
An integer exit status, or None to use an exit status of 0.
"""
raise NotImplementedError
def run(self, args=None):
"""Main application entry point."""
if args is None: # pragma: no cover
args = sys.argv
# Add and parse commandline args.
self.parser = argparse.ArgumentParser(
description=self.DESCRIPTION,
prog=self.PROG_NAME or args[0],
formatter_class=argparse.RawTextHelpFormatter)
self.add_argparse_options(self.parser)
self.opts = self.parser.parse_args(args[1:])
self.process_argparse_options(self.opts)
# Print a startup log message.
logging.info('Process started at %s', datetime.datetime.utcfromtimestamp(
psutil.Process().create_time()).isoformat())
logging.info('Command line arguments:')
for index, arg in enumerate(sys.argv):
logging.info('argv[%d]: %s', index, arg)
logging.info('Process id %d', os.getpid())
logging.info('Current working directory %s', os.getcwd())
# Run the application's main function.
try:
status = self.main(self.opts)
except Exception:
logging.exception('Uncaught exception, exiting:')
if self.USES_TS_MON:
# Flushing ts_mon to try to report the exception.
ts_mon.flush()
status = 1
sys.exit(status)