Changeset 470:e81a69790aa2

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

add code to bridge foolscap log events into twisted. Closes #93.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • ChangeLog

    r469 r470  
     12008-08-28  Brian Warner  <warner@lothar.com> 
     2 
     3        * foolscap/logging/log.py: add bridge-foolscap-logs-to-twisted 
     4        functionality, set up by either calling bridgeLogsToTwisted(), or 
     5        by setting the FLOGTOTWISTED environment variable (to anything). 
     6        The default filter will exclude events below the OPERATIONAL 
     7        severity level, and those generated by foolscap internals (i.e. 
     8        facility.startswith("foolscap") ). Closes #93. 
     9        * foolscap/pb.py (Tub.setOption): update to match 
     10        * foolscap/test/test_logging.py (Bridge): tests for it 
     11        (Publish): update to use new APIs 
     12        * doc/logging.xhtml: docs 
     13 
    1142008-08-25  Brian Warner  <warner@lothar.com> 
    215 
  • doc/logging.xhtml

    r461 r470  
    670670method is until its stopService() method is called). TODO: review this 
    671671behavior, we want earlier messages to be bridged too.</p> 
     672 
     673<p>To bridge log events in the other direction (i.e. taking foolscap log 
     674messages and copying them into twisted), use the 
     675<code>log.bridgeLogsToTwisted()</code> call, or the 
     676<code>FLOGTOTWISTED</code> environment variable. This is useful to get 
     677foolscap.logging.log.msg() events copied into <code>twistd.log</code>. The 
     678default filter only bridges non-noisy events (i.e. those at level OPERATIONAL 
     679or higher), and does not bridge foolscal internal events.</p> 
     680 
     681<p>You might use this if you don't buy into the foolscap logging philosophy 
     682and really want log events to be continually written out to disk. You might 
     683also use it if you want a long-term record of operationally-significant 
     684events, or a record that will survive application crashes which don't get 
     685handled by the existing Incident-recording mechanism.</p> 
     686 
     687<pre class="python"> 
     688from foolscap.logging import log 
     689log.bridgeLogsToTwisted() 
     690</pre> 
     691 
    672692 
    673693<h4>stdlib 'logging' module</h4> 
  • foolscap/logging/log.py

    r458 r470  
    295295 
    296296class TwistedLogBridge: 
    297     def __init__(self, tubID=None): 
     297    def __init__(self, tubID=None, foolscap_logger=theLogger): 
    298298        self.tubID = tubID 
     299        self.logger = foolscap_logger 
    299300 
    300301        # newer versions of Twisted have a function called 
     
    315316        #  ['system']: string 
    316317 
     318        if "from-foolscap" in d: 
     319            return 
    317320        event = d.copy() 
    318321        event['tubID'] = self.tubID 
    319322        message = twisted_log.textFromEventDict(d) 
    320323        event.pop('message', None) 
    321         msg(message, **event) 
     324        event['from-twisted'] = True 
     325        self.logger.msg(message, **event) 
    322326 
    323327    def _old_twisted_log_observer(self, d): 
     328        if "from-foolscap" in d: 
     329            return 
    324330        event = d.copy() 
    325331        if "format" in d: 
     
    333339        event.pop('args', None) 
    334340        event['tubID'] = self.tubID 
    335         msg(**event) 
    336  
    337 theTwistedLogBridge = None 
    338  
    339 def setTwistedLogBridge(bridge): 
    340     global theTwistedLogBridge 
    341     if theTwistedLogBridge: 
    342         try: 
    343             twisted_log.removeObserver(theTwistedLogBridge.observer) 
    344         except ValueError: 
    345             pass 
    346     if bridge: 
    347         theTwistedLogBridge = bridge 
    348         twisted_log.addObserver(bridge.observer) 
    349  
    350 def bridgeTwistedLogs(): 
    351     setTwistedLogBridge(TwistedLogBridge()) 
     341        event['from-twisted'] = True 
     342        self.logger.msg(**event) 
     343 
     344def bridgeLogsFromTwisted(tubID=None, 
     345                          twisted_logger=twisted_log.theLogPublisher, 
     346                          foolscap_logger=theLogger): 
     347    """Called without arguments, this arranges for all twisted log messages 
     348    to be bridged into the default foolscap logger. 
     349 
     350    I can also be called with a specific twisted and/or foolscap logger, 
     351    mostly for unit tests that don't want to modify the default instances. 
     352    For their benefit, I return the bridge. 
     353    """ 
     354    tlb = TwistedLogBridge(tubID, foolscap_logger) 
     355    twisted_logger.addObserver(tlb.observer) 
     356    return tlb 
     357 
     358def bridgeLogsToTwisted(filter=None, 
     359                        foolscap_logger=theLogger, 
     360                        twisted_logger=twisted_log): 
     361    # foolscap_logger and twisted_logger are for testing purposes 
     362    def non_foolscap_operational_or_better(e): 
     363        if e.get("facility","").startswith("foolscap"): 
     364            return False 
     365        if e['level'] < OPERATIONAL: 
     366            return False 
     367        return True 
     368    if not filter: 
     369        filter = non_foolscap_operational_or_better 
     370    def _to_twisted(event): 
     371        if "from-twisted" in event: 
     372            return 
     373        if not filter(event): 
     374            return 
     375        args = {"from-foolscap": True, 
     376                "num": event["num"], 
     377                "level": event["level"], 
     378                } 
     379        twisted_logger.msg(format_message(event), **args) 
     380    foolscap_logger.addObserver(_to_twisted) 
    352381 
    353382class LogFileObserver: 
     
    402431 
    403432if "FLOGTWISTED" in os.environ: 
    404     bridgeTwistedLogs() 
     433    bridgeLogsFromTwisted() 
     434 
     435if "FLOGTOTWISTED" in os.environ: 
     436    bridgeLogsToTwisted() 
  • foolscap/pb.py

    r438 r470  
    340340            self.setLogGathererFURLFile(value) 
    341341        elif name == "bridge-twisted-logs": 
    342             if value: 
    343                 tlb = flog.TwistedLogBridge(self.tubID) 
    344                 flog.setTwistedLogBridge(tlb
     342            assert value is not False, "cannot unbridge twisted logs" 
     343            if value is True: 
     344                return flog.bridgeLogsFromTwisted(self.tubID
    345345            else: 
    346                 flog.setTwistedLogBridge(None) 
     346                # for tests, bridge logs from a specific twisted LogPublisher 
     347                return flog.bridgeLogsFromTwisted(self.tubID, 
     348                                                  twisted_logger=value) 
    347349        elif name == "handle-old-duplicate-connections": 
    348350            if value is True: 
  • foolscap/test/test_logging.py

    r465 r470  
    349349    def tearDown(self): 
    350350        publish.Subscription.MAX_QUEUE_SIZE = self.saved_queue_size 
    351         log.setTwistedLogBridge(None) # disable any bridge still in place 
    352351        d = defer.succeed(None) 
    353352        d.addCallback(lambda res: self.parent.stopService()) 
     
    402401        logport_furl2 = open(furlfile, "r").read().strip() 
    403402        self.failUnlessEqual(logport_furl, logport_furl2) 
    404         t.setOption("bridge-twisted-logs", True) 
     403        tw_log = twisted_log.LogPublisher() 
     404        tlb = t.setOption("bridge-twisted-logs", tw_log) 
    405405 
    406406        t2 = GoodEnoughTub() 
    407407        t2.setServiceParent(self.parent) 
    408408        ob = Observer() 
    409  
    410         do_twisted_errors = hasattr(self, "flushLoggedErrors") 
    411409 
    412410        d = t2.getReference(logport_furl) 
     
    425423                ob.last_received = time.time() 
    426424                log.msg("message 1 here") 
    427                 twisted_log.msg("message 2 here") 
     425                tw_log.msg("message 2 here") 
     426 
    428427                # switch to generic (no tubid) bridge 
    429                 log.bridgeTwistedLogs() 
    430                 twisted_log.msg("message 3 here") 
    431                 twisted_log.msg(format="%(foo)s is foo", foo="foo") 
     428                tw_log.removeObserver(tlb.observer) 
     429                log.bridgeLogsFromTwisted(None, tw_log) 
     430 
     431                tw_log.msg("message 3 here") 
     432                tw_log.msg(format="%(foo)s is foo", foo="foo") 
    432433                log.err(failure.Failure(SampleError("err1"))) 
    433434                log.err(SampleError("err2")) 
    434                 if do_twisted_errors: 
    435                     twisted_log.err(failure.Failure(SampleError("err3"))) 
    436                     twisted_log.err(SampleError("err4")) 
    437                     # twisted-2.5.0 added flushLoggedErrors, which makes 
    438                     # it much easier for unit test to exercise error logging 
    439                     errors = self.flushLoggedErrors(SampleError) 
    440                     self.failUnlessEqual(len(errors), 2) 
     435                # simulate twisted.python.log.err, which is unfortunately 
     436                # not a method of LogPublisher 
     437                def err(_stuff=None, _why=None): 
     438                    if isinstance(_stuff, Exception): 
     439                        tw_log.msg(failure=failure.Failure(_stuff), 
     440                                   isError=1, why=_why) 
     441                    else: 
     442                        tw_log.msg(failure=_stuff, isError=1, why=_why) 
     443                err(failure.Failure(SampleError("err3"))) 
     444                err(SampleError("err4")) 
    441445            d.addCallback(_emit) 
    442446            # now we wait until the observer has seen nothing for a full 
     
    454458            def _check_observer(res): 
    455459                msgs = ob.messages 
    456                 expected = 6 
    457                 if do_twisted_errors: 
    458                     expected += 2 
    459                 self.failUnlessEqual(len(msgs), expected) 
     460                self.failUnlessEqual(len(msgs), 8) 
    460461                #print msgs 
    461462                self.failUnlessEqual(msgs[0]["message"], "message 1 here") 
     
    479480                self.failUnless("err2" in str(msgs[5]["failure"])) 
    480481 
    481                 if do_twisted_errors: 
    482                     # twisted-8.0 has textFromEventDict, which means we get a 
    483                     # ["message"] key from log.err . In older version of 
    484                     # twisted, we don't. 
    485                     if msgs[6]["message"]: 
    486                         self.failUnless("Unhandled Error" in msgs[6]["message"]) 
    487                         self.failUnless("SampleError: err3" in msgs[6]["message"]) 
    488                     self.failUnless(msgs[6]["isError"]) 
    489                     self.failUnless("failure" in msgs[6]) 
    490                     self.failUnless(msgs[6]["failure"].check(SampleError)) 
    491                     self.failUnless("err3" in str(msgs[6]["failure"])) 
    492  
    493                     # same 
    494                     if msgs[7]["message"]: 
    495                         self.failUnless("Unhandled Error" in msgs[7]["message"]) 
    496                         self.failUnless("SampleError: err4" in msgs[7]["message"]) 
    497                     self.failUnless(msgs[7]["isError"]) 
    498                     self.failUnless("failure" in msgs[7]) 
    499                     self.failUnless(msgs[7]["failure"].check(SampleError)) 
    500                     self.failUnless("err4" in str(msgs[7]["failure"])) 
     482                # twisted-8.0 has textFromEventDict, which means we get a 
     483                # ["message"] key from log.err . In older version of 
     484                # twisted, we don't. 
     485                if msgs[6]["message"]: 
     486                    self.failUnless("Unhandled Error" in msgs[6]["message"]) 
     487                    self.failUnless("SampleError: err3" in msgs[6]["message"]) 
     488                self.failUnless(msgs[6]["isError"]) 
     489                self.failUnless("failure" in msgs[6]) 
     490                self.failUnless(msgs[6]["failure"].check(SampleError)) 
     491                self.failUnless("err3" in str(msgs[6]["failure"])) 
     492 
     493                # same 
     494                if msgs[7]["message"]: 
     495                    self.failUnless("Unhandled Error" in msgs[7]["message"]) 
     496                    self.failUnless("SampleError: err4" in msgs[7]["message"]) 
     497                self.failUnless(msgs[7]["isError"]) 
     498                self.failUnless("failure" in msgs[7]) 
     499                self.failUnless(msgs[7]["failure"].check(SampleError)) 
     500                self.failUnless("err4" in str(msgs[7]["failure"])) 
    501501 
    502502            d.addCallback(_check_observer) 
     
    10491049 
    10501050    def tearDown(self): 
    1051         log.setTwistedLogBridge(None) # disable any bridge still in place 
    10521051        d = defer.succeed(None) 
    10531052        d.addCallback(lambda res: self.parent.stopService()) 
     
    18471846        return d 
    18481847 
     1848 
     1849class Bridge(unittest.TestCase): 
     1850    def test_foolscap_to_twisted(self): 
     1851        fl = log.FoolscapLogger() 
     1852        tw = twisted_log.LogPublisher() 
     1853        log.bridgeLogsToTwisted(None, fl, tw) 
     1854        tw_out = [] 
     1855        tw.addObserver(tw_out.append) 
     1856        fl_out = [] 
     1857        fl.addObserver(fl_out.append) 
     1858 
     1859        fl.msg("one") 
     1860        fl.msg(format="two %(two)d", two=2) 
     1861        fl.msg("three", level=log.NOISY) # should be removed 
     1862        d = flushEventualQueue() 
     1863        def _check(res): 
     1864            self.failUnlessEqual(len(fl_out), 3) 
     1865            self.failUnlessEqual(fl_out[0]["message"], "one") 
     1866            self.failUnlessEqual(fl_out[1]["format"], "two %(two)d") 
     1867            self.failUnlessEqual(fl_out[2]["message"], "three") 
     1868 
     1869            self.failUnlessEqual(len(tw_out), 2) 
     1870            self.failUnlessEqual(tw_out[0]["message"], ("one",)) 
     1871            self.failUnless(tw_out[0]["from-foolscap"]) 
     1872            self.failUnlessEqual(tw_out[1]["message"], ("two 2",)) 
     1873            self.failUnless(tw_out[1]["from-foolscap"]) 
     1874 
     1875        d.addCallback(_check) 
     1876        return d 
     1877 
     1878    def test_twisted_to_foolscap(self): 
     1879        fl = log.FoolscapLogger() 
     1880        tw = twisted_log.LogPublisher() 
     1881        log.bridgeLogsFromTwisted(None, tw, fl) 
     1882        tw_out = [] 
     1883        tw.addObserver(tw_out.append) 
     1884        fl_out = [] 
     1885        fl.addObserver(fl_out.append) 
     1886 
     1887        tw.msg("one") 
     1888        tw.msg(format="two %(two)d", two=2) 
     1889        d = flushEventualQueue() 
     1890        def _check(res): 
     1891            self.failUnlessEqual(len(tw_out), 2) 
     1892            self.failUnlessEqual(tw_out[0]["message"], ("one",)) 
     1893            self.failUnlessEqual(tw_out[1]["format"], "two %(two)d") 
     1894            self.failUnlessEqual(tw_out[1]["two"], 2) 
     1895 
     1896            self.failUnlessEqual(len(fl_out), 2) 
     1897            self.failUnlessEqual(fl_out[0]["message"], "one") 
     1898            self.failUnless(fl_out[0]["from-twisted"]) 
     1899            self.failUnlessEqual(fl_out[1]["format"], "two %(two)d") 
     1900            self.failUnless(fl_out[1]["from-twisted"]) 
     1901 
     1902        d.addCallback(_check) 
     1903        return d 
     1904 
     1905 
     1906    def test_no_loops(self): 
     1907        fl = log.FoolscapLogger() 
     1908        tw = twisted_log.LogPublisher() 
     1909        log.bridgeLogsFromTwisted(None, tw, fl) 
     1910        log.bridgeLogsToTwisted(None, fl, tw) 
     1911        tw_out = [] 
     1912        tw.addObserver(tw_out.append) 
     1913        fl_out = [] 
     1914        fl.addObserver(fl_out.append) 
     1915 
     1916        tw.msg("one") 
     1917        fl.msg("two") 
     1918 
     1919        d = flushEventualQueue() 
     1920        def _check(res): 
     1921            self.failUnlessEqual(len(tw_out), 2) 
     1922            self.failUnlessEqual(tw_out[0]["message"], ("one",)) 
     1923            self.failUnlessEqual(tw_out[1]["message"], ("two",)) 
     1924 
     1925            self.failUnlessEqual(len(fl_out), 2) 
     1926            self.failUnlessEqual(fl_out[0]["message"], "one") 
     1927            self.failUnlessEqual(fl_out[1]["message"], "two") 
     1928 
     1929        d.addCallback(_check) 
     1930        return d 
     1931