[CalendarServer-changes] [1962] CalendarServer/branches/users/cdaboo/better-logging-1957

source_changes at macosforge.org source_changes at macosforge.org
Mon Oct 15 13:39:54 PDT 2007


Revision: 1962
          http://trac.macosforge.org/projects/calendarserver/changeset/1962
Author:   cdaboo at apple.com
Date:     2007-10-15 13:39:54 -0700 (Mon, 15 Oct 2007)

Log Message:
-----------
Added per-user accounting capability. This allows per user logging. Options are in the logger.plist config file.
Logs are rooted to one directory, then by principal record type and name. A log can be a single file (appended to)
or a series of files in a directory - each file name being the timestamp. Currently logging of all iTIP Outbox POST
data can be enabled. In the future we might want to enable e.g. all PUTs to be logged.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/better-logging-1957/conf/logger.plist
    CalendarServer/branches/users/cdaboo/better-logging-1957/twistedcaldav/logger.py
    CalendarServer/branches/users/cdaboo/better-logging-1957/twistedcaldav/schedule.py

Added Paths:
-----------
    CalendarServer/branches/users/cdaboo/better-logging-1957/twistedcaldav/accounting.py
    CalendarServer/branches/users/cdaboo/better-logging-1957/twistedcaldav/test/test_accounting.py

Modified: CalendarServer/branches/users/cdaboo/better-logging-1957/conf/logger.plist
===================================================================
--- CalendarServer/branches/users/cdaboo/better-logging-1957/conf/logger.plist	2007-10-15 16:54:29 UTC (rev 1961)
+++ CalendarServer/branches/users/cdaboo/better-logging-1957/conf/logger.plist	2007-10-15 20:39:54 UTC (rev 1962)
@@ -41,5 +41,18 @@
 		-->
 	</dict>
 
+	<!-- Per User Accounting -->
+	<key>Accounting</key>
+	<dict>
+	    <key>Enabled</key>
+	    <false/>
+	    <key>LogDirectory</key>
+	    <string>/var/log/caldavd/logs</string>
+	    <key>iTIP</key>
+	    <false/>
+	    <key>principals</key>
+	    <array></array>
+	</dict>
+
 </dict>
 </plist>

Added: CalendarServer/branches/users/cdaboo/better-logging-1957/twistedcaldav/accounting.py
===================================================================
--- CalendarServer/branches/users/cdaboo/better-logging-1957/twistedcaldav/accounting.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/better-logging-1957/twistedcaldav/accounting.py	2007-10-15 20:39:54 UTC (rev 1962)
@@ -0,0 +1,146 @@
+##
+# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# DRI: Cyrus Daboo, cdaboo at apple.com
+##
+
+from twistedcaldav.logger import logger
+
+import itertools
+import datetime
+import os
+
+"""
+Classes and functions for user accounting.
+"""
+
+class accounting(object):
+    
+    # Types of account data to store. The first item in the tuple is a name for the account file/directory,
+    # the second item is True if its a directory, False if its a file.
+    type_iTIP  = ("iTIP", True)
+
+    _all_types = (type_iTIP, )
+
+    @staticmethod
+    def isEnabled():
+        """
+        Is accounting enabled.
+        """
+        return logger.accounting().get("Enabled", False)
+    
+    @staticmethod
+    def isiTIPEnabled(principal):
+        """
+        Is iTIP accounting enabled.
+
+        @param principal: the principal for whom a log entry is to be created.
+        @type principal: L{DirectoryPrincipalResource}
+        """
+        return (
+            accounting.isEnabled() and
+            logger.accounting().get("iTIP", False) and
+            accounting.isPrincipalEnabled(principal)
+        )
+    
+    @staticmethod
+    def isPrincipalEnabled(principal):
+        """
+        Is principal accounting enabled.
+
+        @param principal: the principal for whom a log entry is to be created.
+        @type principal: L{DirectoryPrincipalResource}
+        """
+        
+        # None specified => all enabled
+        enabled = logger.accounting().get("principals", ())
+        if not enabled:
+            return True
+        
+        for url in itertools.chain((principal.principalURL(),), principal.alternateURIs()):
+            if url in enabled:
+                return True
+            
+        return False
+            
+    @staticmethod
+    def getLog(principal, accounting_type=None):
+        """
+        Get the log file/directory path.
+
+        @param principal: the principal for whom a log entry is to be created.
+        @type principal: L{DirectoryPrincipalResource}
+        @param accounting_type: a tuple of log name and file/directory log type.
+        @type accounting_type: C{tuple}
+        
+        @return: the file/directory path.
+        @type: C{str}
+        """
+
+        assert (accounting_type in accounting._all_types) or (accounting_type is None), "Supplied type not valid: %s" % (accounting_type,)
+        
+        # Path is the config value + record type + short name + type (if provided)
+        log_path = logger.accounting().get("LogDirectory", "")
+        
+        record = principal.record
+        
+        log_path = os.path.join(log_path, record.recordType)
+        log_path = os.path.join(log_path, record.shortName)
+
+        # Make sure this path exists
+        if not os.path.exists(log_path):
+            os.makedirs(log_path)
+
+        if type:
+            type_name, type_isdirectory = accounting_type
+            log_path = os.path.join(log_path, type_name)
+            if not os.path.exists(log_path) and type_isdirectory:
+                os.mkdir(log_path)
+
+        return log_path
+
+    @staticmethod
+    def writeData(principal, accounting_type, data):
+        """
+        Write the supplied data to the appropriate location for the principal.
+        
+        @param principal: the principal for whom a log entry is to be created.
+        @type principal: L{DirectoryPrincipalResource}
+        @param accounting_type: a tuple of log name and file/directory log type.
+        @type accounting_type: C{tuple}
+        @param data: data to write.
+        @type data: C{str}
+        """
+        
+        assert accounting_type in accounting._all_types, "Supplied type not valid: %s" % (accounting_type,)
+
+        if not accounting.isPrincipalEnabled(principal):
+            return
+
+        _ignore_type_name, type_isdirectory = accounting_type
+        log_path = accounting.getLog(principal, accounting_type)
+        if type_isdirectory:
+            # Generate a timestamp
+            log_path = os.path.join(log_path, datetime.datetime.now().isoformat())
+            if os.path.exists(log_path):
+                for ctr in range(1, 100):
+                    if not os.path.exists(log_path + "-%02d" % (ctr,)):
+                        log_path += "-%02d" % (ctr,)
+                        break
+                    
+        # Now write out the data to the file
+        file = open(log_path, "a")
+        file.write(data)
+        file.close()

Modified: CalendarServer/branches/users/cdaboo/better-logging-1957/twistedcaldav/logger.py
===================================================================
--- CalendarServer/branches/users/cdaboo/better-logging-1957/twistedcaldav/logger.py	2007-10-15 16:54:29 UTC (rev 1961)
+++ CalendarServer/branches/users/cdaboo/better-logging-1957/twistedcaldav/logger.py	2007-10-15 20:39:54 UTC (rev 1962)
@@ -43,6 +43,8 @@
         
         self.systemLogLevels = {}
         
+        self.accounting = {}
+        
     def read(self, fname=None):
         
         if not fname:
@@ -66,6 +68,8 @@
                     log.msg("Ignoring unknown logging level '%s' for system: %s" % (value, key,), system="Logger")
             
             self.systemLogLevels = newLogLevels
+            
+            self.accounting = options.get("Accounting", {"Enabled":False,})
 
 class Logger(object):
     #
@@ -189,5 +193,8 @@
         if self.canLog("debug", kwargs):
             log.msg(message, debug=True, **kwargs)
 
+    def accounting(self):
+        return self.options.accounting
+
 # Create the global instance of the logger
 logger = Logger()

Modified: CalendarServer/branches/users/cdaboo/better-logging-1957/twistedcaldav/schedule.py
===================================================================
--- CalendarServer/branches/users/cdaboo/better-logging-1957/twistedcaldav/schedule.py	2007-10-15 16:54:29 UTC (rev 1961)
+++ CalendarServer/branches/users/cdaboo/better-logging-1957/twistedcaldav/schedule.py	2007-10-15 20:39:54 UTC (rev 1962)
@@ -38,6 +38,7 @@
 
 from twistedcaldav import caldavxml
 from twistedcaldav import itip
+from twistedcaldav.accounting import accounting
 from twistedcaldav.logger import logger
 from twistedcaldav.resource import CalDAVResource
 from twistedcaldav.caldavxml import caldav_namespace, TimeRange
@@ -335,6 +336,11 @@
             # Do regular invite (fan-out)
             freebusy = False
 
+        # Do accounting
+        if not freebusy and accounting.isiTIPEnabled(oprincipal):
+            data = "Originator: %s\nRecipients: %s\n\n%s" % (originator, ", ".join(recipients), str(calendar),)
+            accounting.writeData(oprincipal, accounting.type_iTIP, data)
+
         # Prepare for multiple responses
         responses = ScheduleResponseQueue("POST", responsecode.OK)
     

Added: CalendarServer/branches/users/cdaboo/better-logging-1957/twistedcaldav/test/test_accounting.py
===================================================================
--- CalendarServer/branches/users/cdaboo/better-logging-1957/twistedcaldav/test/test_accounting.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/better-logging-1957/twistedcaldav/test/test_accounting.py	2007-10-15 20:39:54 UTC (rev 1962)
@@ -0,0 +1,200 @@
+##
+# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# DRI: David Reid, dreid at apple.com
+##
+
+from twisted.trial import unittest
+
+from twistedcaldav.config import config, defaultConfig
+from twistedcaldav.logger import logger, Logger
+from twistedcaldav.logger import LoggerOptions
+from twistedcaldav.directory.directory import DirectoryRecord
+from twistedcaldav.directory.directory import DirectoryService
+from twistedcaldav.accounting import accounting
+import os
+
+testConfig = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+    <key>LoggerOptionsFile</key>
+    <string>%s</string>
+</dict>
+</plist>
+"""
+
+testAccounting_Disabled = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+    <key>Default Log Level</key>
+    <string>error</string>
+    <key>System Log Levels</key>
+    <dict>
+        <key>Startup</key>
+        <string>info</string>
+    </dict>
+    <key>Accounting</key>
+    <dict>
+        <key>Enabled</key>
+        <false/>
+        <key>LogDirectory</key>
+        <string>%s</string>
+        <key>iTIP</key>
+        <false/>
+        <key>principals</key>
+        <array></array>
+    </dict>
+</dict>
+</plist>
+"""
+
+testAccounting_EnabledAll = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+    <key>Default Log Level</key>
+    <string>error</string>
+    <key>System Log Levels</key>
+    <dict>
+        <key>Startup</key>
+        <string>info</string>
+    </dict>
+    <key>Accounting</key>
+    <dict>
+        <key>Enabled</key>
+        <true/>
+        <key>LogDirectory</key>
+        <string>%s</string>
+        <key>iTIP</key>
+        <true/>
+        <key>principals</key>
+        <array></array>
+    </dict>
+</dict>
+</plist>
+"""
+
+testAccounting_EnabledSome = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+    <key>Default Log Level</key>
+    <string>error</string>
+    <key>System Log Levels</key>
+    <dict>
+        <key>Startup</key>
+        <string>info</string>
+    </dict>
+    <key>Accounting</key>
+    <dict>
+        <key>Enabled</key>
+        <true/>
+        <key>LogDirectory</key>
+        <string>%s</string>
+        <key>iTIP</key>
+        <true/>
+        <key>principals</key>
+        <array>
+            <string>/principals/users/user01/</string>
+            <string>/principals/__uids__/user02/</string>
+        </array>
+    </dict>
+</dict>
+</plist>
+"""
+
+class LoggerTests(unittest.TestCase):
+
+    class DummyPrincipal(object):
+        
+        class DummyDirectoryService(DirectoryService):
+            def __init__(self):
+                self.realmName = "example.com"
+
+        def __init__(self, url, alturis, rtype, rname):
+            self.url = url
+            self.alturis = alturis
+            self.record = DirectoryRecord(self.DummyDirectoryService(), rtype, rname, rname, rname, set(), False, True)
+
+        def principalURL(self):
+            return self.url
+            
+        def alternateURIs(self):
+            return self.alturis
+
+    principal01 = DummyPrincipal("/principals/__uids__/user01/", ("/principals/users/user01/",), DirectoryService.recordType_users, "user01")
+    principal02 = DummyPrincipal("/principals/__uids__/user02/", ("/principals/users/user02/",), DirectoryService.recordType_users, "user02")
+    principal03 = DummyPrincipal("/principals/__uids__/user03/", ("/principals/users/user03/",), DirectoryService.recordType_users, "user03")
+
+    def setUp(self):
+        config.update(defaultConfig)
+        self.testLogger = self.mktemp()
+        self.testConfig = self.mktemp()
+        open(self.testConfig, 'w').write(testConfig % (self.testLogger,))
+        config.loadConfig(self.testConfig)
+        
+        self.temp_disabled = self.mktemp()
+        self.test_Disabled = testAccounting_Disabled % (self.temp_disabled,)
+        self.temp_enabledall = self.mktemp()
+        self.test_EnabledAll = testAccounting_EnabledAll % (self.temp_enabledall,)
+        self.temp_enabledsome = self.mktemp()
+        self.test_EnabledSome = testAccounting_EnabledSome % (self.temp_enabledsome,)
+
+    def tearDown(self):
+        self.loadLogfile(self.test_Disabled)
+
+    def loadLogfile(self, plist):
+        open(self.testLogger, 'w').write(plist)
+        logger.readOptions()
+
+    def validAccount(self, principal, root_path):
+        self.assertTrue(accounting.isiTIPEnabled(principal))
+        self.assertEquals(accounting.getLog(principal, accounting.type_iTIP), os.path.join(root_path, principal.record.recordType, principal.record.shortName, "iTIP",))
+        self.assertTrue(os.path.exists(os.path.join(root_path, principal.record.recordType, principal.record.shortName, "iTIP",)))
+    
+    def testDefaultAccounting(self):
+        self.assertFalse(accounting.isEnabled())
+        self.assertFalse(accounting.isiTIPEnabled(LoggerTests.principal01))
+        self.assertFalse(accounting.isiTIPEnabled(LoggerTests.principal02))
+        self.assertFalse(accounting.isiTIPEnabled(LoggerTests.principal03))
+
+    def testDisabledAccounting(self):
+        self.loadLogfile(self.test_Disabled)
+
+        self.assertFalse(accounting.isEnabled())
+        self.assertFalse(accounting.isiTIPEnabled(LoggerTests.principal01))
+        self.assertFalse(accounting.isiTIPEnabled(LoggerTests.principal02))
+        self.assertFalse(accounting.isiTIPEnabled(LoggerTests.principal03))
+
+    def testEnabledAllAccounting(self):
+        self.loadLogfile(self.test_EnabledAll)
+
+        self.validAccount(LoggerTests.principal01, self.temp_enabledall)
+        self.validAccount(LoggerTests.principal02, self.temp_enabledall)
+        self.validAccount(LoggerTests.principal03, self.temp_enabledall)
+
+    def testEnabledSomeAccounting(self):
+        self.loadLogfile(self.test_EnabledSome)
+
+        self.validAccount(LoggerTests.principal01, self.temp_enabledsome)
+        self.validAccount(LoggerTests.principal02, self.temp_enabledsome)
+        self.assertFalse(accounting.isiTIPEnabled(LoggerTests.principal03))
+
+    def testInvalidAccountingType(self):
+        self.loadLogfile(self.test_EnabledSome)
+
+        self.assertRaises(AssertionError, accounting.getLog, LoggerTests.principal02, ("PUT", True,))

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20071015/2c1af5d3/attachment-0001.html


More information about the calendarserver-changes mailing list