This is a script suitable for use with logwatch that parses the syslog debug output from a Sipura SPA3000 VoIP ATA and prints a list of call made and received calls and their times. It might also work with other Sipura models.
1 #!/usr/bin/env python2.4 2 3 # logwatch script for parsing syslog output from Sipura SPA3000 devices 4 # andrewb@cse.unsw.edu.au, 2007/01/12 5 6 import sys, re, time, datetime 7 8 # list of all IP addresses for the Sipura; used to detect PSTN calls 9 # FIXME: make this automatic, or configurable elsewhere 10 LOCAL_IPS = ['127.0.0.1', '192.168.0.6'] 11 12 SYSLOG_RE = re.compile(r'^ *(?Ptime... .. ..:..:..) (?Phost\S*) (?Pdata.*)') 13 NEWMSG_RE = re.compile(r'^\[\d+:\d+\](?Pdir|-)(?Ppeer\d+\.\d+\.\d+\.\d+:\d+)$') 14 SIP_URI_RE = re.compile(r'^sip:(?Puser.*)@(?Phost[^:]+)(:(?Pport\d+))?$') 15 SYSLOG_TIME = '%b %d %H:%M:%S' 16 DISPLAY_TIME = '%b %d %H:%M' 17 18 STATE_SETUP = 1 19 STATE_RINGING = 2 20 STATE_INPROGRESS = 3 21 STATE_TERMINATED = 4 22 23 class SIPCallState: 24 def __init__(self): 25 self.originator = None 26 self.state = None 27 self.uri = None 28 self.starttime = None 29 self.duration = None 30 31 def got_cmd(self, ts, cmd, arg): 32 if cmd == 'INVITE': 33 self.originator = False 34 self.state = STATE_SETUP 35 elif cmd == 'BYE' or cmd == 'CANCEL': 36 self.state = STATE_TERMINATED 37 if not self.uri: 38 self.uri = arg 39 if self.starttime and not self.duration: 40 self.duration = ts - self.starttime 41 elif cmd == 'ACK': 42 if self.state == STATE_SETUP or self.state == STATE_RINGING: 43 assert(self.uri == arg) 44 self.state = STATE_INPROGRESS 45 self.starttime = ts 46 47 def got_reply(self, ts, status): 48 if self.state == STATE_SETUP and status / 10 == 18: 49 self.state = STATE_RINGING 50 elif self.state in [STATE_SETUP, STATE_RINGING] and status / 100 == 2: 51 self.state = STATE_INPROGRESS 52 self.starttime = ts 53 elif status / 100 = 4: 54 self.state = STATE_TERMINATED 55 56 def sent_cmd(self, ts, cmd, arg): 57 if cmd == 'INVITE': 58 self.originator = True 59 self.state = STATE_SETUP 60 self.uri = arg 61 else: 62 self.got_cmd(ts, cmd, arg) 63 64 def sent_reply(self, ts, status): 65 return self.got_reply(ts, status) 66 67 class StatsGatherer: 68 def __init__(self): 69 self.last_peer = self.last_inout = None 70 self.peers = {} 71 self.calls_made = [] 72 self.calls_received = [] 73 74 def process_entry(self, timestamp, line): 75 match = NEWMSG_RE.match(line) 76 if match: 77 self.last_inout = (match.group('dir') == '-') 78 ip, port = match.group('peer').split(':', 1) 79 if ip in LOCAL_IPS: 80 ip = LOCAL_IPS[0] 81 self.last_peer = ip + ':' + port 82 else: 83 words = line.split() 84 is_reply = words[0].startswith('SIP/') 85 is_command = words[-1].startswith('SIP/') 86 if is_reply or is_command: 87 assert(len(words) 2) 88 callstate = self.peers.setdefault(self.last_peer, SIPCallState()) 89 if is_reply: 90 status = int(words[1]) 91 if self.last_inout: 92 callstate.sent_reply(timestamp, status) 93 else: 94 callstate.got_reply(timestamp, status) 95 else: 96 if self.last_inout: 97 callstate.sent_cmd(timestamp, words[0], words[1]) 98 else: 99 callstate.got_cmd(timestamp, words[0], words[1]) 100 if callstate.state == STATE_TERMINATED: 101 self.__process_call(callstate) 102 del self.peers[self.last_peer] 103 104 def __process_call(self, callstate): 105 if not callstate.duration: 106 return 107 if callstate.originator: 108 self.calls_made.append((callstate.uri, callstate.starttime, callstate.duration)) 109 else: 110 self.calls_received.append((callstate.uri, callstate.starttime, callstate.duration)) 111 112 def print_stats(self, fh): 113 for (calls, desc, received) in [(self.calls_made, 'made', False), 114 (self.calls_received, 'received', True)]: 115 if calls != []: 116 fh.write('Calls %s:\n' % desc) 117 for (uri, start, duration) in calls: 118 if uri is None: 119 uri = 'unknown' 120 else: 121 match = SIP_URI_RE.match(uri) 122 if match: 123 host = match.group('host') 124 if host in LOCAL_IPS: 125 if received: 126 uri = '(PSTN)' 127 else: 128 uri = match.group('user') 129 else: 130 uri = match.group('user') + '@' + host 131 fh.write(' %s %-30s %s\n' % (start.strftime(DISPLAY_TIME), uri, duration)) 132 133 def process_syslog(fh, statobj): 134 prevline = None 135 for line in fh.readlines(): 136 if line == prevline: 137 continue 138 match = SYSLOG_RE.match(line) 139 timestr = match.group('time') 140 timestamp = datetime.datetime(*(time.strptime(timestr, SYSLOG_TIME)[:6])) 141 contents = match.group('data').strip() 142 if contents != '': 143 statobj.process_entry(timestamp, contents) 144 prevline = line 145 146 if __name__ == '__main__': 147 statobj = StatsGatherer() 148 process_syslog(sys.stdin, statobj) 149 statobj.print_stats(sys.stdout)