def test_clobber(config): config01 = { 'foo': 'bar' } config02 = { 'bar': 'foo' } assert config01 != config02 save_config('foo', config01) config03 = load_config('foo') assert config01 == config03 save_config('foo', config02) config04 = load_config('foo') assert config02 == config04 assert config01 != config04 conf_fn = '{}/conf_files/foo.yaml'.format(os.getenv('POCS')) os.remove(conf_fn) assert os.path.exists(conf_fn) is False
def test_multiple_config(): config01 = {'foo': 1} config02 = {'foo': 2, 'bar': 42} config03 = {'bam': 'boo'} assert config01 != config02 f01 = str(uuid.uuid4()) f02 = str(uuid.uuid4()) f03 = str(uuid.uuid4()) save_config(f01, config01) save_config(f02, config02) save_config(f03, config03) config04 = load_config(f01) config05 = load_config(f02) config06 = load_config(f03) assert config01 == config04 assert config02 == config05 assert config03 == config06 config07 = load_config([f01, f02], ignore_local=True) config08 = load_config([f02, f01], ignore_local=True) assert config07 != config01 assert config07 == config02 assert config08 != config01 assert config08 != config02 assert config08 != config05 assert 'foo' not in config06 assert 'bar' not in config06 assert 'foo' in config05 assert 'foo' in config07 assert 'foo' in config08 assert 'bar' in config05 assert 'bar' in config07 assert 'bar' in config08 assert 'bam' in config06 assert config07['foo'] == 2 assert config08['foo'] == 1 os.remove('{}/conf_files/{}.yaml'.format(os.getenv('POCS'), f01)) os.remove('{}/conf_files/{}.yaml'.format(os.getenv('POCS'), f02)) os.remove('{}/conf_files/{}.yaml'.format(os.getenv('POCS'), f03))
def _make_pretty_from_fits(fname, timeout=15, **kwargs): config = load_config() title = '{} {}'.format(kwargs.get('title', ''), current_time().isot) new_filename = fname.replace('.fits', '.jpg') data = fits.getdata(fname) plt.imshow(data, cmap='cubehelix_r', origin='lower') plt.title(title) plt.savefig(new_filename) image_dir = config['directories']['images'] ln_fn = '{}/latest.jpg'.format(image_dir) try: os.remove(ln_fn) except FileNotFoundError: pass try: os.symlink(new_filename, ln_fn) except Exception as e: warn("Can't link latest image: {}".format(e)) return new_filename
def __init__(self, *args, **kwargs): # Load the default and local config files global _config if _config is None: ignore_local_config = kwargs.get('ignore_local_config', False) _config = config.load_config(ignore_local=ignore_local_config) self.__version__ = __version__ # Update with run-time config if 'config' in kwargs: _config.update(kwargs['config']) self._check_config(_config) self.config = _config self.logger = kwargs.get('logger') if not self.logger: self.logger = get_root_logger() self.config['simulator'] = hardware.get_simulator_names(config=self.config, kwargs=kwargs) # Set up connection to database db = kwargs.get('db', self.config['db']['name']) _db = PanMongo(db=db) self.db = _db
def camera(request, images_dir): if request.param[0] == SimCamera: camera = request.param[0](focuser={'model': 'simulator', 'focus_port': '/dev/ttyFAKE', 'initial_position': 20000, 'autofocus_range': (40, 80), 'autofocus_step': (10, 20), 'autofocus_seconds': 0.1, 'autofocus_size': 500, 'autofocus_keep_files': False}) else: # Load the local config file and look for camera configurations of the specified type configs = [] local_config = load_config('pocs_local', ignore_local=True) camera_info = local_config.get('cameras') if camera_info: # Local config file has a cameras section camera_configs = camera_info.get('devices') if camera_configs: # Local config file camera section has a devices list for camera_config in camera_configs: if camera_config['model'] == request.param[1]: # Camera config is the right type configs.append(camera_config) if not configs: pytest.skip( "Found no {} configs in pocs_local.yaml, skipping tests".format(request.param[1])) # Create and return an camera based on the first config camera = request.param[0](**configs[0]) camera.config['directories']['images'] = images_dir return camera
def __init__(self, auto_detect=False, *args, **kwargs): self.config = load_config(config_files='peas') self.logger = get_root_logger() assert 'environment' in self.config assert type(self.config['environment']) is dict, \ self.logger.warning("Environment config variable not set correctly. No sensors listed") self.db = None self.messaging = None # Store each serial reader self.serial_readers = dict() if auto_detect: for port_num in range(9): port = '/dev/ttyACM{}'.format(port_num) if os.path.exists(port): self.logger.debug("Trying to connect on {}".format(port)) sensor_name = None serial_reader = self._connect_serial(port) num_tries = 5 self.logger.debug("Getting name on {}".format(port)) while num_tries > 0: try: data = serial_reader.get_reading() except yaml.parser.ParserError: pass except AttributeError: pass else: try: if 'name' in data: sensor_name = data['name'] num_tries = 0 except Exception as e: self.logger.warning("Read on serial: {}".format(e)) num_tries -= 1 if sensor_name is not None: self.serial_readers[sensor_name] = { 'reader': serial_reader, } else: # Try to connect to a range of ports for sensor_name in self.config['environment'].keys(): try: port = self.config['environment'][sensor_name]['serial_port'] except TypeError: continue except KeyError: continue serial_reader = self._connect_serial(port) self.serial_readers[sensor_name] = { 'reader': serial_reader, 'port': port, }
def pocs(target): try: del os.environ['POCSTIME'] except KeyError: pass config = load_config(ignore_local=False) pocs = POCS(simulator=['weather', 'night', 'camera'], run_once=True, config=config, db='panoptes_testing', messaging=True) pocs.observatory.scheduler.fields_list = [ { 'name': 'Testing Target', 'position': target.to_string(style='hmsdms'), 'priority': '100', 'exp_time': 2, 'min_nexp': 2, 'exp_set_size': 2, }, ] yield pocs pocs.power_down()
def focuser(request): if request.param[0] == SimFocuser: # Simulated focuser, just create one and return it return request.param[0]() else: # Load the local config file and look for focuser configurations of the specified type focuser_configs = [] local_config = load_config('pocs_local', ignore_local=True) camera_info = local_config.get('cameras') if camera_info: # Local config file has a cameras section camera_configs = camera_info.get('devices') if camera_configs: # Local config file camera section has a devices list for camera_config in camera_configs: focuser_config = camera_config.get('focuser', None) if focuser_config and focuser_config[ 'model'] == request.param[1]: # Camera config has a focuser section, and it's the right type focuser_configs.append(focuser_config) if not focuser_configs: pytest.skip( "Found no {} configurations in pocs_local.yaml, skipping tests" .format(request.param[1])) # Create and return a Focuser based on the first config return request.param[0](**focuser_configs[0])
def __init__(self, *args, **kwargs): # Load the default and local config files global _config if _config is None: ignore_local_config = kwargs.get('ignore_local_config', False) _config = config.load_config(ignore_local=ignore_local_config) self.__version__ = __version__ # Update with run-time config if 'config' in kwargs: _config.update(kwargs['config']) self._check_config(_config) self.config = _config self.logger = kwargs.get('logger') if not self.logger: self.logger = get_root_logger() self.config['simulator'] = hardware.get_simulator_names( config=self.config, kwargs=kwargs) # Set up connection to database db = kwargs.get('db', self.config['db']['name']) _db = PanMongo(db=db) self.db = _db
def config(): pocs.base.reset_global_config() global _one_time_config if not _one_time_config: _one_time_config = load_config(ignore_local=True, simulator=['all']) _one_time_config['db']['name'] = 'panoptes_testing' return copy.deepcopy(_one_time_config)
def test_full_path(): temp_config_path = '/tmp/{}.yaml'.format(uuid.uuid4()) temp_config = {'foo': 42} save_config(temp_config_path, temp_config) c = load_config(temp_config_path) assert c == temp_config os.remove(temp_config_path)
def test_local_config(): _local_config_file = '{}/conf_files/pocs_local.yaml'.format(os.getenv('POCS')) if not os.path.exists(_local_config_file): conf = load_config(ignore_local=True) assert conf['name'] == 'Generic PANOPTES Unit' local_yaml = { 'name': 'ConfTestName' } with open(_local_config_file, 'w') as f: f.write(yaml.dump(local_yaml)) conf = load_config() assert conf['name'] != 'Generic PANOPTES Unit' os.remove(_local_config_file) else: conf = load_config() assert conf['name'] != 'Generic PANOPTES Unit'
def test_no_config(): # Move existing config to temp _config_file = '{}/conf_files/pocs.yaml'.format(os.getenv('POCS')) _config_file_temp = '{}/conf_files/pocs_temp.yaml'.format(os.getenv('POCS')) os.rename(_config_file, _config_file_temp) config = load_config(ignore_local=True) assert len(config.keys()) == 0 os.rename(_config_file_temp, _config_file)
def setup(self): self.config = load_config(ignore_local=True) location = self.config['location'] with pytest.raises(AssertionError): mount = Mount(location) loc = EarthLocation(lon=location['longitude'], lat=location['latitude'], height=location['elevation']) mount = Mount(loc) assert mount is not None
def setup(self): self.config = load_config(ignore_local=True) location = self.config['location'] with pytest.raises(AssertionError): mount = Mount(location) loc = EarthLocation( lon=location['longitude'], lat=location['latitude'], height=location['elevation']) mount = Mount(loc) assert mount is not None
def __init__(self, date_string=None, data_file=None, *args, **kwargs): super(WeatherPlotter, self).__init__() self.args = args self.kwargs = kwargs config = load_config(config_files=['peas']) self.cfg = config['weather']['plot'] location_cfg = config.get('location', None) self.thresholds = config['weather'].get('aag_cloud', None) if not date_string: self.today = True self.date = dt.utcnow() self.date_string = self.date.strftime('%Y%m%dUT') self.start = self.date - tdelta(1, 0) self.end = self.date self.lhstart = self.date - tdelta(0, 60 * 60) self.lhend = self.date + tdelta(0, 5 * 60) else: self.today = False self.date = dt.strptime('{} 23:59:59'.format(date_string), '%Y%m%dUT %H:%M:%S') self.date_string = date_string self.start = dt(self.date.year, self.date.month, self.date.day, 0, 0, 0, 0) self.end = dt(self.date.year, self.date.month, self.date.day, 23, 59, 59, 0) print('Creating weather plotter for {}'.format(self.date_string)) self.twilights = self.get_twilights(location_cfg) self.table = self.get_table_data(data_file) if self.table is None: warnings.warn("No data") sys.exit(0) self.time = pd.to_datetime(self.table['date']) first = self.time[0].isoformat() last = self.time[-1].isoformat() print(' Retrieved {} entries between {} and {}'.format( len(self.table), first, last)) if self.today: self.current_values = self.table[-1] else: self.current_values = None
def __init__(self, webcam_config, frames=255, resolution="1600x1200", brightness="50%", gain="50%"): self.config = load_config(config_files='peas') self.logger = get_root_logger() self._today_dir = None self.webcam_dir = self.config['directories'].get('webcam', '/var/panoptes/webcams/') assert os.path.exists(self.webcam_dir), self.logger.warning( "Webcam directory must exist: {}".format(self.webcam_dir)) self.logger.info("Creating webcams") # Lookup the webcams if webcam_config is None: err_msg = "No webcams to connect. Please check config.yaml and all appropriate ports" self.logger.warning(err_msg) self.webcam_config = webcam_config self.name = self.webcam_config.get('name', 'GenericWebCam') self.port_name = self.webcam_config.get('port').split('/')[-1] # Command for taking pics self.cmd = shutil.which('fswebcam') # Defaults self._timestamp = "%Y-%m-%d %H:%M:%S" self._thumbnail_resolution = '240x120' # Create the string for the params self.base_params = "-F {} -r {} --set brightness={} --set gain={} --jpeg 100 --timestamp \"{}\" ".format( frames, resolution, brightness, gain, self._timestamp) self.logger.info("{} created".format(self.name))
def pocs(target): try: del os.environ['POCSTIME'] except KeyError: pass config = load_config(ignore_local=False) pocs = POCS(simulator=['weather', 'night', 'camera'], run_once=True, config=config, db='panoptes_testing', messaging=True) pocs.observatory.scheduler.fields_list = [ {'name': 'Testing Target', 'position': target.to_string(style='hmsdms'), 'priority': '100', 'exp_time': 2, 'min_nexp': 2, 'exp_set_size': 2, }, ] yield pocs pocs.power_down()
def location(): config = load_config(ignore_local=False) loc = config['location'] return EarthLocation(lon=loc['longitude'], lat=loc['latitude'], height=loc['elevation'])
def __init__(self, serial_address=None, use_mongo=True): self.config = load_config(config_files='peas') self.logger = get_root_logger() # Read configuration self.cfg = self.config['weather']['aag_cloud'] self.safety_delay = self.cfg.get('safety_delay', 15.) self.db = None if use_mongo: self.db = get_mongodb() self.messaging = None # Initialize Serial Connection if serial_address is None: serial_address = self.cfg.get('serial_port', '/dev/ttyUSB0') self.logger.debug('Using serial address: {}'.format(serial_address)) if serial_address: self.logger.info('Connecting to AAG Cloud Sensor') try: self.AAG = serial.Serial(serial_address, 9600, timeout=2) self.logger.info(" Connected to Cloud Sensor on {}".format(serial_address)) except OSError as e: self.logger.error('Unable to connect to AAG Cloud Sensor') self.logger.error(' {}'.format(e.errno)) self.logger.error(' {}'.format(e.strerror)) self.AAG = None except BaseException: self.logger.error("Unable to connect to AAG Cloud Sensor") self.AAG = None else: self.AAG = None # Thresholds # Initialize Values self.last_update = None self.safe = None self.ambient_temp = None self.sky_temp = None self.wind_speed = None self.internal_voltage = None self.LDR_resistance = None self.rain_sensor_temp = None self.PWM = None self.errors = None self.switch = None self.safe_dict = None self.hibernate = 0.500 # time to wait after failed query # Set Up Heater if 'heater' in self.cfg: self.heater_cfg = self.cfg['heater'] else: self.heater_cfg = { 'low_temp': 0, 'low_delta': 6, 'high_temp': 20, 'high_delta': 4, 'min_power': 10, 'impulse_temp': 10, 'impulse_duration': 60, 'impulse_cycle': 600, } self.heater_PID = PID(Kp=3.0, Ki=0.02, Kd=200.0, max_age=300, output_limits=[self.heater_cfg['min_power'], 100]) self.impulse_heating = None self.impulse_start = None # Command Translation self.commands = {'!A': 'Get internal name', '!B': 'Get firmware version', '!C': 'Get values', '!D': 'Get internal errors', '!E': 'Get rain frequency', '!F': 'Get switch status', '!G': 'Set switch open', '!H': 'Set switch closed', 'P\d\d\d\d!': 'Set PWM value', '!Q': 'Get PWM value', '!S': 'Get sky IR temperature', '!T': 'Get sensor temperature', '!z': 'Reset RS232 buffer pointers', '!K': 'Get serial number', 'v!': 'Query if anemometer enabled', 'V!': 'Get wind speed', 'M!': 'Get electrical constants', '!Pxxxx': 'Set PWM value to xxxx', } self.expects = {'!A': '!N\s+(\w+)!', '!B': '!V\s+([\d\.\-]+)!', '!C': '!6\s+([\d\.\-]+)!4\s+([\d\.\-]+)!5\s+([\d\.\-]+)!', '!D': '!E1\s+([\d\.]+)!E2\s+([\d\.]+)!E3\s+([\d\.]+)!E4\s+([\d\.]+)!', '!E': '!R\s+([\d\.\-]+)!', '!F': '!Y\s+([\d\.\-]+)!', 'P\d\d\d\d!': '!Q\s+([\d\.\-]+)!', '!Q': '!Q\s+([\d\.\-]+)!', '!S': '!1\s+([\d\.\-]+)!', '!T': '!2\s+([\d\.\-]+)!', '!K': '!K(\d+)\s*\\x00!', 'v!': '!v\s+([\d\.\-]+)!', 'V!': '!w\s+([\d\.\-]+)!', 'M!': '!M(.{12})', } self.delays = { '!E': 0.350, 'P\d\d\d\d!': 0.750, } self.weather_entries = list() if self.AAG: # Query Device Name result = self.query('!A') if result: self.name = result[0].strip() self.logger.info(' Device Name is "{}"'.format(self.name)) else: self.name = '' self.logger.warning(' Failed to get Device Name') sys.exit(1) # Query Firmware Version result = self.query('!B') if result: self.firmware_version = result[0].strip() self.logger.info(' Firmware Version = {}'.format(self.firmware_version)) else: self.firmware_version = '' self.logger.warning(' Failed to get Firmware Version') sys.exit(1) # Query Serial Number result = self.query('!K') if result: self.serial_number = result[0].strip() self.logger.info(' Serial Number: {}'.format(self.serial_number)) else: self.serial_number = '' self.logger.warning(' Failed to get Serial Number') sys.exit(1)
def test_no_parse(): config = load_config(parse=False, ignore_local=True) lat = config['location']['latitude'] assert isinstance(lat, u.Quantity) is False assert isinstance(lat, float)
def get_root_logger(profile='panoptes', log_config=None): """Creates a root logger for PANOPTES used by the PanBase object. Args: profile (str, optional): The name of the logger to use, defaults to 'panoptes'. log_config (dict|None, optional): Configuration options for the logger. See https://docs.python.org/3/library/logging.config.html for available options. Default is `None`, which then looks up the values in the `log.yaml` config file. Returns: logger(logging.logger): A configured instance of the logger """ # Get log info from config log_config = log_config if log_config else load_config('log').get('logger', {}) # If we already created a logger for this profile and log_config, return that. logger_key = (profile, json.dumps(log_config, sort_keys=True)) try: return all_loggers[logger_key] except KeyError: pass # Alter the log_config to use UTC times if log_config.get('use_utc', True): for name, formatter in log_config['formatters'].items(): log_config['formatters'][name].setdefault('()', _UTCFormatter) log_fname_datetime = datetime.datetime.utcnow().strftime('%Y%m%dT%H%M%SZ') else: log_fname_datetime = datetime.datetime.now().strftime('%Y%m%dT%H%M%SZ') # Setup log file names invoked_script = os.path.basename(sys.argv[0]) log_dir = '{}/logs'.format(os.getenv('PANDIR', gettempdir())) log_fname = '{}-{}-{}'.format(invoked_script, os.getpid(), log_fname_datetime) log_symlink = '{}/{}.log'.format(log_dir, invoked_script) # Set log filename and rotation for handler in log_config.get('handlers', []): # Set the filename full_log_fname = '{}/{}-{}.log'.format(log_dir, log_fname, handler) log_config['handlers'][handler].setdefault('filename', full_log_fname) # Setup the TimedRotatingFileHandler for middle of day log_config['handlers'][handler].setdefault('atTime', datetime.time(hour=11, minute=30)) if handler == 'all': # Create a symlink to the log file with just the name of the script, # not the date and pid, as this makes it easier to find the latest file. try: os.unlink(log_symlink) except FileNotFoundError: # pragma: no cover pass finally: os.symlink(full_log_fname, log_symlink) # Configure the logger logging.config.dictConfig(log_config) # Get the logger and set as attribute to class logger = logging.getLogger(profile) # Don't want log messages from state machine library, it is very noisy and # we have our own way of logging state transitions logging.getLogger('transitions.core').setLevel(logging.WARNING) # Set custom LogRecord logging.setLogRecordFactory(StrFormatLogRecord) # Add a filter for better filename/lineno logger.addFilter(FilenameLineFilter()) logger.info('{:*^80}'.format(' Starting PanLogger ')) all_loggers[logger_key] = logger return logger
default=None, help= "Connect to a specific database, otherwise connect to all in config.") parser.add_argument('--key-file', default=None, help="JSON service account key location.") parser.add_argument('--proxy-cmd', default=None, help="The Google Cloud SQL proxy script") parser.add_argument('--verbose', action='store_true', default=False, help="Print results to stdout, default False.") args = parser.parse_args() config = load_config(args.config) try: network_config = config['panoptes_network'] if args.verbose: print("Found config:") pprint(network_config) except KeyError as e: print( "Invalid configuration. Check panoptes_network config. {}".format( e)) sys.exit(1) # Try to lookup service account key from config if none provided key_file = args.key_file if not key_file: try:
def __init__(self, serial_address=None, use_mongo=True): self.config = load_config(config_files='peas') self.logger = get_root_logger() # Read configuration self.cfg = self.config['weather']['aag_cloud'] self.safety_delay = self.cfg.get('safety_delay', 15.) self.db = None if use_mongo: self.db = get_mongodb() self.messaging = None # Initialize Serial Connection if serial_address is None: serial_address = self.cfg.get('serial_port', '/dev/ttyUSB0') self.logger.debug('Using serial address: {}'.format(serial_address)) if serial_address: self.logger.info('Connecting to AAG Cloud Sensor') try: self.AAG = serial.Serial(serial_address, 9600, timeout=2) self.logger.info( " Connected to Cloud Sensor on {}".format(serial_address)) except OSError as e: self.logger.error('Unable to connect to AAG Cloud Sensor') self.logger.error(' {}'.format(e.errno)) self.logger.error(' {}'.format(e.strerror)) self.AAG = None except BaseException: self.logger.error("Unable to connect to AAG Cloud Sensor") self.AAG = None else: self.AAG = None # Thresholds # Initialize Values self.last_update = None self.safe = None self.ambient_temp = None self.sky_temp = None self.wind_speed = None self.internal_voltage = None self.LDR_resistance = None self.rain_sensor_temp = None self.PWM = None self.errors = None self.switch = None self.safe_dict = None self.hibernate = 0.500 # time to wait after failed query # Set Up Heater if 'heater' in self.cfg: self.heater_cfg = self.cfg['heater'] else: self.heater_cfg = { 'low_temp': 0, 'low_delta': 6, 'high_temp': 20, 'high_delta': 4, 'min_power': 10, 'impulse_temp': 10, 'impulse_duration': 60, 'impulse_cycle': 600, } self.heater_PID = PID( Kp=3.0, Ki=0.02, Kd=200.0, max_age=300, output_limits=[self.heater_cfg['min_power'], 100]) self.impulse_heating = None self.impulse_start = None # Command Translation self.commands = { '!A': 'Get internal name', '!B': 'Get firmware version', '!C': 'Get values', '!D': 'Get internal errors', '!E': 'Get rain frequency', '!F': 'Get switch status', '!G': 'Set switch open', '!H': 'Set switch closed', 'P\d\d\d\d!': 'Set PWM value', '!Q': 'Get PWM value', '!S': 'Get sky IR temperature', '!T': 'Get sensor temperature', '!z': 'Reset RS232 buffer pointers', '!K': 'Get serial number', 'v!': 'Query if anemometer enabled', 'V!': 'Get wind speed', 'M!': 'Get electrical constants', '!Pxxxx': 'Set PWM value to xxxx', } self.expects = { '!A': '!N\s+(\w+)!', '!B': '!V\s+([\d\.\-]+)!', '!C': '!6\s+([\d\.\-]+)!4\s+([\d\.\-]+)!5\s+([\d\.\-]+)!', '!D': '!E1\s+([\d\.]+)!E2\s+([\d\.]+)!E3\s+([\d\.]+)!E4\s+([\d\.]+)!', '!E': '!R\s+([\d\.\-]+)!', '!F': '!Y\s+([\d\.\-]+)!', 'P\d\d\d\d!': '!Q\s+([\d\.\-]+)!', '!Q': '!Q\s+([\d\.\-]+)!', '!S': '!1\s+([\d\.\-]+)!', '!T': '!2\s+([\d\.\-]+)!', '!K': '!K(\d+)\s*\\x00!', 'v!': '!v\s+([\d\.\-]+)!', 'V!': '!w\s+([\d\.\-]+)!', 'M!': '!M(.{12})', } self.delays = { '!E': 0.350, 'P\d\d\d\d!': 0.750, } self.weather_entries = list() if self.AAG: # Query Device Name result = self.query('!A') if result: self.name = result[0].strip() self.logger.info(' Device Name is "{}"'.format(self.name)) else: self.name = '' self.logger.warning(' Failed to get Device Name') sys.exit(1) # Query Firmware Version result = self.query('!B') if result: self.firmware_version = result[0].strip() self.logger.info(' Firmware Version = {}'.format( self.firmware_version)) else: self.firmware_version = '' self.logger.warning(' Failed to get Firmware Version') sys.exit(1) # Query Serial Number result = self.query('!K') if result: self.serial_number = result[0].strip() self.logger.info(' Serial Number: {}'.format( self.serial_number)) else: self.serial_number = '' self.logger.warning(' Failed to get Serial Number') sys.exit(1)
def config(): config = load_config(ignore_local=True, simulator=['all']) config['db']['name'] = 'panoptes_testing' return config
def get_root_logger(profile='panoptes', log_config=None): """Creates a root logger for PANOPTES used by the PanBase object. Args: profile (str, optional): The name of the logger to use, defaults to 'panoptes'. log_config (dict|None, optional): Configuration options for the logger. See https://docs.python.org/3/library/logging.config.html for available options. Default is `None`, which then looks up the values in the `log.yaml` config file. Returns: logger(logging.logger): A configured instance of the logger """ # Get log info from config log_config = log_config if log_config else load_config('log').get( 'logger', {}) # If we already created a logger for this profile and log_config, return that. logger_key = (profile, json.dumps(log_config, sort_keys=True)) try: return all_loggers[logger_key] except KeyError: pass # Alter the log_config to use UTC times if log_config.get('use_utc', True): for name, formatter in log_config['formatters'].items(): log_config['formatters'][name].setdefault('()', _UTCFormatter) log_fname_datetime = datetime.datetime.utcnow().strftime( '%Y%m%dT%H%M%SZ') else: log_fname_datetime = datetime.datetime.now().strftime('%Y%m%dT%H%M%SZ') # Setup log file names invoked_script = os.path.basename(sys.argv[0]) log_dir = '{}/logs'.format(os.getenv('PANDIR', gettempdir())) log_fname = '{}-{}-{}'.format(invoked_script, os.getpid(), log_fname_datetime) log_symlink = '{}/{}.log'.format(log_dir, invoked_script) # Set log filename and rotation for handler in log_config.get('handlers', []): # Set the filename full_log_fname = '{}/{}-{}.log'.format(log_dir, log_fname, handler) log_config['handlers'][handler].setdefault('filename', full_log_fname) # Setup the TimedRotatingFileHandler for middle of day log_config['handlers'][handler].setdefault( 'atTime', datetime.time(hour=11, minute=30)) if handler == 'all': # Create a symlink to the log file with just the name of the script, # not the date and pid, as this makes it easier to find the latest file. try: os.unlink(log_symlink) except FileNotFoundError: # pragma: no cover pass finally: os.symlink(full_log_fname, log_symlink) # Configure the logger logging.config.dictConfig(log_config) # Get the logger and set as attribute to class logger = logging.getLogger(profile) # Don't want log messages from state machine library, it is very noisy and # we have our own way of logging state transitions logging.getLogger('transitions.core').setLevel(logging.WARNING) # Set custom LogRecord logging.setLogRecordFactory(StrFormatLogRecord) # Add a filter for better filename/lineno logger.addFilter(FilenameLineFilter()) logger.info('{:*^80}'.format(' Starting PanLogger ')) all_loggers[logger_key] = logger return logger