class TestMetlog(object): logger = 'tests' def setUp(self): self.mock_sender = Mock() self.client = MetlogClient(self.mock_sender, self.logger) # overwrite the class-wide threadlocal w/ an instance one # so values won't persist btn tests self.client.timer._local = threading.local() plugin = config_plugin({'net':True}) self.client.add_method('procinfo', plugin) def test_add_procinfo(self): HOST = 'localhost' # Symbolic name meaning the local host PORT = 50017 # Arbitrary non-privileged port def echo_serv(): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind((HOST, PORT)) s.listen(1) conn, addr = s.accept() data = conn.recv(1024) conn.send(data) conn.close() s.close() t = threading.Thread(target=echo_serv) t.start() time.sleep(1) def client_code(): client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect((HOST, PORT)) client.send('Hello, world') data = client.recv(1024) client.close() time.sleep(1) self.client.procinfo(net=True) eq_(1, len(self.client.sender.method_calls)) fields = self.client.sender.method_calls[0][1][0]['fields'] assert fields == {u'net': [{u'status': u'LISTEN', u'type': u'TCP', u'local': u'127.0.0.1:50017', u'remote': u'*:*'}]} # Start the client up just so that the server will die gracefully tc = threading.Thread(target=client_code) tc.start()
class TestConfiguration(object): """ Configuration for plugin based loggers should *override* what the developer uses. IOTW - developers are overridden by ops. """ logger = 'tests' def setUp(self): self.mock_sender = Mock() self.client = MetlogClient(self.mock_sender, self.logger) # overwrite the class-wide threadlocal w/ an instance one # so values won't persist btn tests self.client.timer._local = threading.local() plugin = config_plugin({'net':False}) self.client.add_method('procinfo', plugin) def test_no_netlogging(self): self.client.procinfo(net=True) eq_(0, len(self.client.sender.method_calls))
def client_from_dict_config(config, client=None, clear_global=False): """ Configure a metlog client, fully configured w/ sender and plugins. :param config: Configuration dictionary. :param client: MetlogClient instance to configure. If None, one will be created. :param clear_global: If True, delete any existing global config on the CLIENT_HOLDER before applying new config. The configuration dict supports the following values: logger Metlog client default logger value. severity Metlog client default severity value. disabled_timers Sequence of string tokens identifying timers that are to be deactivated. filters Sequence of 2-tuples `(filter_provider, config)`. Each `filter_provider` is a dotted name referring to a function which, when called and passed the associated `config` dict as kwargs, will return a usable MetlogClient filter function. plugins Nested dictionary containing plugin configuration. Keys are the plugin names (i.e. the name the method will be given when attached to the client). Values are 2-tuples `(plugin_provider, config)`. Each `plugin_provider` is a dotted name referring to a function which, when called and passed the associated `config`, will return the usable plugin method. sender Nested dictionary containing sender configuration. global Dictionary to be applied to CLIENT_HOLDER's `global_config` storage. New config will overwrite any conflicting values, but will not delete other config entries. To delete, calling code should call the function with `clear_global` set to True. All of the configuration values are optional, but failure to include a sender may result in a non-functional Metlog client. Any unrecognized keys will be ignored. Note that any top level config values starting with `sender_` will be added to the `sender` config dictionary, overwriting any values that may already be set. The sender configuration supports the following values: class (required) Dotted name identifying the sender class to instantiate. args Sequence of non-keyword args to pass to sender constructor. <kwargs> All remaining key-value pairs in the sender config dict will be passed as keyword arguments to the sender constructor. """ # Make a deep copy of the configuration so that subsequent uses of # the config won't blow up config = nest_prefixes(copy.deepcopy(config)) sender_config = config.get('sender', {}) logger = config.get('logger', '') severity = config.get('severity', 6) disabled_timers = config.get('disabled_timers', []) filter_specs = config.get('filters', []) plugins_data = config.pop('plugins', {}) global_conf = config.get('global', {}) # update global config stored in CLIENT_HOLDER from metlog.holder import CLIENT_HOLDER if clear_global: CLIENT_HOLDER.global_config = {} CLIENT_HOLDER.global_config.update(global_conf) resolver = DottedNameResolver() # instantiate sender sender_clsname = sender_config.pop('class') sender_cls = resolver.resolve(sender_clsname) sender_args = sender_config.pop('args', tuple()) sender = sender_cls(*sender_args, **sender_config) # initialize filters filters = [resolver.resolve(dotted_name)(**cfg) for (dotted_name, cfg) in filter_specs] # instantiate and/or configure client if client is None: client = MetlogClient(sender, logger, severity, disabled_timers, filters) else: client.setup(sender, logger, severity, disabled_timers, filters) # initialize plugins and attach to client for section_name, plugin_spec in plugins_data.items(): # each plugin spec is a 2-tuple: (dotted_name, cfg) plugin_config = plugin_spec[1] plugin_override = plugin_config.pop('override', False) plugin_fn = resolver.resolve(plugin_spec[0])(plugin_config) client.add_method(plugin_fn, plugin_override) return client
def client_from_dict_config(config, client=None, clear_global=False): """ Configure a metlog client, fully configured w/ sender and plugins. :param config: Configuration dictionary. :param client: MetlogClient instance to configure. If None, one will be created. :param clear_global: If True, delete any existing global config on the CLIENT_HOLDER before applying new config. The configuration dict supports the following values: logger Metlog client default logger value. severity Metlog client default severity value. disabled_timers Sequence of string tokens identifying timers that are to be deactivated. filters Sequence of 2-tuples `(filter_provider, config)`. Each `filter_provider` is a dotted name referring to a function which, when called and passed the associated `config` dict as kwargs, will return a usable MetlogClient filter function. plugins Nested dictionary containing plugin configuration. Keys are the plugin names (i.e. the name the method will be given when attached to the client). Values are 2-tuples `(plugin_provider, config)`. Each `plugin_provider` is a dotted name referring to a function which, when called and passed the associated `config`, will return the usable plugin method. sender Nested dictionary containing sender configuration. global Dictionary to be applied to CLIENT_HOLDER's `global_config` storage. New config will overwrite any conflicting values, but will not delete other config entries. To delete, calling code should call the function with `clear_global` set to True. All of the configuration values are optional, but failure to include a sender may result in a non-functional Metlog client. Any unrecognized keys will be ignored. Note that any top level config values starting with `sender_` will be added to the `sender` config dictionary, overwriting any values that may already be set. The sender configuration supports the following values: class (required) Dotted name identifying the sender class to instantiate. args Sequence of non-keyword args to pass to sender constructor. <kwargs> All remaining key-value pairs in the sender config dict will be passed as keyword arguments to the sender constructor. """ # Make a deep copy of the configuration so that subsequent uses of # the config won't blow up config = nest_prefixes(copy.deepcopy(config)) config_copy = json.dumps(copy.deepcopy(config)) sender_config = config.get('sender', {}) logger = config.get('logger', '') severity = config.get('severity', 6) disabled_timers = config.get('disabled_timers', []) filter_specs = config.get('filters', []) plugins_data = config.pop('plugins', {}) global_conf = config.get('global', {}) # update global config stored in CLIENT_HOLDER from metlog.holder import CLIENT_HOLDER if clear_global: CLIENT_HOLDER.global_config = {} CLIENT_HOLDER.global_config.update(global_conf) resolver = DottedNameResolver() # instantiate sender sender_clsname = sender_config.pop('class') sender_cls = resolver.resolve(sender_clsname) sender_args = sender_config.pop('args', tuple()) sender = sender_cls(*sender_args, **sender_config) # initialize filters filters = [ resolver.resolve(dotted_name)(**cfg) for (dotted_name, cfg) in filter_specs ] # instantiate and/or configure client if client is None: client = MetlogClient(sender, logger, severity, disabled_timers, filters) else: client.setup(sender, logger, severity, disabled_timers, filters) # initialize plugins and attach to client for section_name, plugin_spec in plugins_data.items(): # each plugin spec is a 2-tuple: (dotted_name, cfg) plugin_config = plugin_spec[1] plugin_override = plugin_config.pop('override', False) plugin_fn = resolver.resolve(plugin_spec[0])(plugin_config) client.add_method(plugin_fn, plugin_override) # We bind the configuration into the client itself to ease # debugging client._config = config_copy return client
def __init__(self, ini_path=None, ini_dir=None, load_sections=None): """ :param ini_dir: Directory path in which to start looking for the ini file. Will climb the file tree from here looking for 'tests.ini' file, unless 'WEAVE_TESTFILE' env var is set, in which case it will climb the file tree from here looking for 'tests_${WEAVE_TESTFILE}.ini'. :param ini_path: Full path to configuration file. Takes precedence over ini_dir, if both are provided. Raises IOError if file doesn't exist. One or the other of `ini_dir` or `ini_path` arguments MUST be provided. :param load_sections: A sequence of strings that name the configuration sections that should be dynamically loaded. Any entry in this sequence could alternately be a 2-tuple containing the name of the section and the corresponding class parameter value to use. """ self.start_dir = ini_dir if ini_path: if not os.path.isfile(ini_path): raise IOError("invalid config file: %s" % ini_path) ini_dir = os.path.dirname(ini_path) elif ini_dir: if 'WEAVE_TESTFILE' in os.environ: test_filename = 'tests_%s.ini' % os.environ['WEAVE_TESTFILE'] else: test_filename = 'tests.ini' while True: ini_path = os.path.join(ini_dir, test_filename) if os.path.exists(ini_path): break if ini_path == ("/%s" % test_filename) \ or ini_path == test_filename: raise IOError("cannot locate %s" % test_filename) ini_dir = os.path.split(ini_dir)[0] else: raise ValueError('No ini_path or ini_dir specified.') self.ini_dir = ini_dir self.ini_path = ini_path ini_cfg = RawConfigParser() ini_cfg.read(ini_path) # loading loggers self.config = self.convert_config(ini_cfg, ini_path) # Ensure that metlog is available, either from the config # or by setting up a default client. try: loader = load_and_configure(self.config, "metlog_loader") client = loader.default_client except KeyError: sender = DebugCaptureSender() client = MetlogClient(sender, "syncserver") CLIENT_HOLDER.set_client(client.logger, client) if not hasattr(client, "cef"): log_cef_fn = metlog_cef.cef_plugin.config_plugin(dict()) client.add_method(log_cef_fn) if load_sections is not None: for section in load_sections: if isinstance(section, tuple): self.add_class(section[0], cls_param=section[1]) else: self.add_class(section)