Changeset 495:2fa51824742b

Show
Ignore:
Timestamp:
10/14/08 16:14:28 (3 months ago)
Author:
Brian Warner <warner@allmydata.com>
branch:
default
Message:

flogtool classify-incident: new subcommand. Closes #102.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • ChangeLog

    r494 r495  
    112008-10-14  Brian Warner  <warner@allmydata.com> 
     2 
     3        * foolscap/logging/cli.py: new "flogtool classify-incident" 
     4        subcommand: given an incident, say what categories it falls into. 
     5        Closes #102. 
     6        * foolscap/logging/gatherer.py (IncidentGathererService): factor 
     7        out the classification pieces into IncidentClassifierBase 
     8        * foolscap/logging/incident.py (IncidentClassifierBase): same 
     9        (IncidentClassifier.run): support for the new CLI command 
     10        * foolscap/test/test_logging.py (Incidents.test_classify): test it 
    211 
    312        * foolscap/logging/gatherer.py 
  • foolscap/logging/cli.py

    r459 r495  
    55 
    66import foolscap 
    7 from foolscap.logging.tail import TailOptions 
    8 from foolscap.logging.gatherer import CreateGatherOptions, \ 
    9      CreateIncidentGatherOptions 
    10 from foolscap.logging.dumper import DumpOptions 
    11 from foolscap.logging.web import WebViewerOptions 
    12 from foolscap.logging.filter import FilterOptions 
     7from foolscap.logging.tail import TailOptions, LogTail 
     8from foolscap.logging.gatherer import \ 
     9     CreateGatherOptions, create_log_gatherer, \ 
     10     CreateIncidentGatherOptions, create_incident_gatherer 
     11from foolscap.logging.dumper import DumpOptions, LogDumper 
     12from foolscap.logging.web import WebViewerOptions, WebViewer 
     13from foolscap.logging.filter import FilterOptions, Filter 
     14from foolscap.logging.incident import ClassifyOptions, IncidentClassifier 
    1315 
    1416class Options(usage.Options): 
     
    2729        ("web-viewer", None, WebViewerOptions, 
    2830         "view the logs through a web page"), 
     31        ("classify-incident", None, ClassifyOptions, 
     32         "classify a stored Incident file"), 
    2933        ] 
    3034 
     
    4650def dispatch(command, options): 
    4751    if command == "tail": 
    48         from foolscap.logging.tail import LogTail 
    4952        lt = LogTail(options) 
    5053        lt.run(options.target_furl) 
    5154 
    5255    elif command == "create-gatherer": 
    53         from foolscap.logging.gatherer import create_log_gatherer 
    5456        create_log_gatherer(options) 
    5557 
    5658    elif command == "create-incident-gatherer": 
    57         from foolscap.logging.gatherer import create_incident_gatherer 
    5859        create_incident_gatherer(options) 
    5960 
    6061    elif command == "dump": 
    61         from foolscap.logging.dumper import LogDumper 
    6262        ld = LogDumper() 
    6363        ld.run(options) 
    6464 
    6565    elif command == "filter": 
    66         from foolscap.logging.filter import Filter 
    6766        f = Filter() 
    6867        f.run(options) 
    6968 
    7069    elif command == "web-viewer": 
    71         from foolscap.logging.web import WebViewer 
    7270        wv = WebViewer() 
    7371        wv.run(options) 
     72 
     73    elif command == "classify-incident": 
     74        ic = IncidentClassifier() 
     75        ic.run(options) 
    7476 
    7577    else: 
  • foolscap/logging/gatherer.py

    r494 r495  
    1212import foolscap 
    1313from foolscap.logging.interfaces import RILogGatherer, RILogObserver 
     14from foolscap.logging.incident import IncidentClassifierBase 
    1415from foolscap.util import get_local_ip_for 
    1516 
     
    391392        return None 
    392393 
    393 class IncidentGathererService(GatheringBase): 
     394class IncidentGathererService(GatheringBase, IncidentClassifierBase): 
    394395    # create this with 'flogtool create-incident-gatherer BASEDIR' 
    395396    # run this as 'cd BASEDIR && twistd -y gatherer.tac' 
     
    420421    def __init__(self, classifiers=[], basedir=None, stdout=None): 
    421422        GatheringBase.__init__(self, basedir) 
    422         self.classifiers = [] 
     423        IncidentClassifierBase.__init__(self) 
    423424        self.classifiers.extend(classifiers) 
    424425        self.stdout = stdout 
    425426        self.incidents_received = 0 # for tests 
    426  
    427     def add_classifier(self, f): 
    428         # there are old .tac files that call this explicitly 
    429         self.classifiers.append(f) 
    430427 
    431428 
     
    437434        if not os.path.isdir(outputdir): 
    438435            os.makedirs(outputdir) 
    439         self.add_classify_files(
     436        self.add_classify_files(self.basedir
    440437        self.classify_stored_incidents(indir) 
    441438        GatheringBase.startService(self) 
    442  
    443     def add_classify_files(self): 
    444         for fn in os.listdir(self.basedir): 
    445             if not (fn.startswith("classify_") and fn.endswith(".py")): 
    446                 continue 
    447             f = open(os.path.join(self.basedir, fn), "r") 
    448             localdict = {} 
    449             exec f in localdict 
    450             self.add_classifier(localdict["classify_incident"]) 
    451439 
    452440    def classify_stored_incidents(self, indir): 
     
    472460                    incident = self.load_incident(abs_fn) 
    473461                    rel_fn = os.path.join("incidents", tubid_s, fn) 
    474                     self.classify_incident(rel_fn, tubid_s, incident) 
     462                    self.move_incident(rel_fn, tubid_s, incident) 
    475463                    count += 1 
    476464        print >>stdout, "done classifying %d stored incidents" % count 
    477  
    478     def load_incident(self, abs_fn): 
    479         assert abs_fn.endswith(".bz2") 
    480         f = bz2.BZ2File(abs_fn, "r") 
    481         header = pickle.load(f)["header"] 
    482         events = [] 
    483         while True: 
    484             try: 
    485                 wrapped = pickle.load(f) 
    486             except (EOFError, ValueError): 
    487                 break 
    488             events.append(wrapped["d"]) 
    489         f.close() 
    490         return (header, events) 
    491465 
    492466    def remote_logport(self, nodeid, publisher): 
     
    504478    def new_incident(self, abs_fn, rel_fn, tubid_s, incident): 
    505479        stdout = self.stdout or sys.stdout 
    506         self.classify_incident(rel_fn, tubid_s, incident) 
     480        self.move_incident(rel_fn, tubid_s, incident) 
    507481        self.incidents_received += 1 
    508482 
    509     def classify_incident(self, rel_fn, tubid_s, incident): 
     483    def move_incident(self, rel_fn, tubid_s, incident): 
    510484        stdout = self.stdout or sys.stdout 
    511         categories = set() 
    512         for f in self.classifiers: 
    513             (header, events) = incident 
    514             trigger = header["trigger"] 
    515             c = f(trigger) 
    516             if c: # allow the classifier to return None, or [], or ["foo"] 
    517                 if isinstance(c, str): 
    518                     c = [c] # or just "foo" 
    519                 categories.update(c) 
    520         if not categories: 
    521             categories.add("unknown") 
     485        categories = self.classify_incident(incident) 
    522486        for c in categories: 
    523487            fn = os.path.join(self.basedir, "classified", c) 
  • foolscap/logging/incident.py

    r477 r495  
    11 
    2 import os.path, time, pickle, bz2 
     2import sys, os.path, time, pickle, bz2 
    33from zope.interface import implements 
     4from twisted.python import usage 
    45from twisted.internet import reactor 
    56from foolscap.logging.interfaces import IIncidentReporter 
     
    165166class NonTrailingIncidentReporter(IncidentReporter): 
    166167    TRAILING_DELAY = None 
     168 
     169 
     170class ClassifyOptions(usage.Options): 
     171    stdout = sys.stdout 
     172    stderr = sys.stderr 
     173    synopsis = "Usage: flogtool classify-incident [options] INCIDENTFILE.." 
     174 
     175    optParameters = [ 
     176        ("classifier-directory", "c", ".", 
     177         "directory with classify_*.py functions to import"), 
     178        ] 
     179 
     180    def parseArgs(self, *files): 
     181        self.files = files 
     182 
     183 
     184class IncidentClassifierBase: 
     185 
     186    def __init__(self): 
     187        self.classifiers = [] 
     188 
     189    def add_classifier(self, f): 
     190        # there are old .tac files that call this explicitly 
     191        self.classifiers.append(f) 
     192 
     193    def add_classify_files(self, plugindir): 
     194        plugindir = os.path.expanduser(plugindir) 
     195        for fn in os.listdir(plugindir): 
     196            if not (fn.startswith("classify_") and fn.endswith(".py")): 
     197                continue 
     198            f = open(os.path.join(plugindir, fn), "r") 
     199            localdict = {} 
     200            exec f in localdict 
     201            self.add_classifier(localdict["classify_incident"]) 
     202 
     203    def load_incident(self, abs_fn): 
     204        assert abs_fn.endswith(".bz2") 
     205        f = bz2.BZ2File(abs_fn, "r") 
     206        header = pickle.load(f)["header"] 
     207        events = [] 
     208        while True: 
     209            try: 
     210                wrapped = pickle.load(f) 
     211            except (EOFError, ValueError): 
     212                break 
     213            events.append(wrapped["d"]) 
     214        f.close() 
     215        return (header, events) 
     216 
     217    def classify_incident(self, incident): 
     218        categories = set() 
     219        for f in self.classifiers: 
     220            (header, events) = incident 
     221            trigger = header["trigger"] 
     222            c = f(trigger) 
     223            if c: # allow the classifier to return None, or [], or ["foo"] 
     224                if isinstance(c, str): 
     225                    c = [c] # or just "foo" 
     226                categories.update(c) 
     227        if not categories: 
     228            categories.add("unknown") 
     229        return categories 
     230 
     231class IncidentClassifier(IncidentClassifierBase): 
     232    def run(self, options): 
     233        self.add_classify_files(options["classifier-directory"]) 
     234        out = options.stdout 
     235        for f in options.files: 
     236            abs_fn = os.path.expanduser(f) 
     237            incident = self.load_incident(abs_fn) 
     238            categories = self.classify_incident(incident) 
     239            print >>out, "%s: %s" % (f, ",".join(sorted(categories))) 
     240 
  • foolscap/test/test_logging.py

    r494 r495  
    352352        return d 
    353353 
     354    def test_classify(self): 
     355        l = log.FoolscapLogger() 
     356        l.setIncidentReporterFactory(incident.NonTrailingIncidentReporter) 
     357        l.setLogDir("logging/Incidents/classify") 
     358        got_logdir = l.logdir 
     359        l.msg("foom", level=log.BAD) 
     360        d = fireEventually() 
     361        def _check(res): 
     362            files = [fn for fn in os.listdir(got_logdir) if fn.endswith(".bz2")] 
     363            self.failUnlessEqual(len(files), 1) 
     364 
     365            ic = incident.IncidentClassifier() 
     366            def classify_foom(trigger): 
     367                if "foom" in trigger.get("message",""): 
     368                    return "foom" 
     369            ic.add_classifier(classify_foom) 
     370            options = incident.ClassifyOptions() 
     371            options.parseOptions([os.path.join(got_logdir, fn) for fn in files]) 
     372            options.stdout = StringIO() 
     373            ic.run(options) 
     374            out = options.stdout.getvalue() 
     375            self.failUnless(out.strip().endswith(": foom"), out) 
     376        d.addCallback(_check) 
     377        return d 
    354378 
    355379class Observer(Referenceable):