source: emailtotracscript/trunk/email2trac.py.in @ 89

Last change on this file since 89 was 89, checked in by bas, 18 years ago

EmailtoTracScript?:

email2trac.py.in:

  • Property svn:executable set to *
  • Property svn:keywords set to Id
File size: 20.5 KB
Line 
1#!@PYTHON@
2# Copyright (C) 2002
3#
4# This file is part of the email2trac utils
5#
6# This program is free software; you can redistribute it and/or modify it
7# under the terms of the GNU General Public License as published by the
8# Free Software Foundation; either version 2, or (at your option) any
9# later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
19#
20# For vi/emacs or other use tabstop=4 (vi: set ts=4)
21#
22"""
23email2trac.py -- Email tickets to Trac.
24
25A simple MTA filter to create Trac tickets from inbound emails.
26
27Copyright 2005, Daniel Lundin <daniel@edgewall.com>
28Copyright 2005, Edgewall Software
29
30Changed By: Bas van der Vlies <basv@sara.nl>
31Date      : 13 September 2005
32Descr.    : Added config file and command line options, spam level
33            detection, reply address and mailto option. Unicode support
34
35Changed By: Walter de Jong <walter@sara.nl>
36Descr.    : multipart-message code and trac attachments
37
38
39The scripts reads emails from stdin and inserts directly into a Trac database.
40MIME headers are mapped as follows:
41
42        * From:      => Reporter
43                     => CC (Optional via reply_address option)
44        * Subject:   => Summary
45        * Body       => Description
46        * Component  => Can be set to SPAM via spam_level option
47
48How to use
49----------
50 * Create an config file:
51        [DEFAULT]                      # REQUIRED
52        project      : /data/trac/test # REQUIRED
53        debug        : 1               # OPTIONAL, if set print some DEBUG info
54        spam_level   : 4               # OPTIONAL, if set check for SPAM mail
55        reply_address: 1               # OPTIONAL, if set then fill in ticket CC field
56        umask        : 022             # OPTIONAL, if set then use this umask for creation of the attachments
57        mailto_link  : 1               # OPTIONAL, if set then [mailto:<>] in description
58        mailto_cc    : basv@sara.nl    # OPTIONAL, use this address as CC in mailto line
59        ticket_update: 1               # OPTIONAL, if set then check if this is an update for a ticket
60        trac_version : 0.8             # OPTIONAL, default is 0.9
61
62        [jouvin]                         # OPTIONAL project declaration, if set both fields necessary
63        project      : /data/trac/jouvin # use -p|--project jouvin. 
64       
65 * default config file is : /etc/email2trac.conf
66
67 * Commandline opions:
68                -h | --help
69                -c <value> | --component=<value>
70                -f <config file> | --file=<config file>
71                -p <project name> | --project=<project name>
72
73SVN Info:
74        $Id: email2trac.py.in 89 2006-06-26 08:01:47Z bas $
75"""
76import os
77import sys
78import string
79import getopt
80import stat
81import time
82import email
83import re
84import urllib
85import unicodedata
86import ConfigParser
87from email import Header
88from stat import *
89import mimetypes
90
91trac_default_version = 0.9
92
93class TicketEmailParser(object):
94        env = None
95        comment = '> '
96   
97        def __init__(self, env, parameters, version):
98                self.env = env
99
100                # Database connection
101                #
102                self.db = None
103
104                # Some useful mail constants
105                #
106                self.author = None
107                self.email_addr = None
108                self.email_field = None
109
110                self.VERSION = version
111                if self.VERSION == 0.8:
112                        self.get_config = self.env.get_config
113                else:
114                        self.get_config = self.env.config.get
115
116                if parameters.has_key('umask'):
117                        os.umask(int(parameters['umask'], 8))
118
119                if parameters.has_key('debug'):
120                        self.DEBUG = int(parameters['debug'])
121                else:
122                        self.DEBUG = 0
123
124                if parameters.has_key('mailto_link'):
125                        self.MAILTO = int(parameters['mailto_link'])
126                        if parameters.has_key('mailto_cc'):
127                                self.MAILTO_CC = parameters['mailto_cc']
128                        else:
129                                self.MAILTO_CC = ''
130                else:
131                        self.MAILTO = 0
132
133                if parameters.has_key('spam_level'):
134                        self.SPAM_LEVEL = int(parameters['spam_level'])
135                else:
136                        self.SPAM_LEVEL = 0
137
138                if parameters.has_key('email_comment'):
139                        self.comment = str(parameters['email_comment'])
140
141                if parameters.has_key('email_header'):
142                        self.EMAIL_HEADER = int(parameters['email_header'])
143                else:
144                        self.EMAIL_HEADER = 0
145
146                if parameters.has_key('alternate_notify_template'):
147                        self.notify_template = str(parameters['alternate_notify_template'])
148                else:
149                        self.notify_template = None
150
151                if parameters.has_key('reply_all'):
152                        self.REPLY_ALL = int(parameters['reply_all'])
153                else:
154                        self.REPLY_ALL = 0
155
156                if parameters.has_key('ticket_update'):
157                        self.TICKET_UPDATE = int(parameters['ticket_update'])
158                else:
159                        self.TICKET_UPDATE = 0
160
161
162        # X-Spam-Score: *** (3.255) BAYES_50,DNS_FROM_AHBL_RHSBL,HTML_
163        # Note if Spam_level then '*' are included
164        def spam(self, message):
165                if message.has_key('X-Spam-Score'):
166                        spam_l = string.split(message['X-Spam-Score'])
167                        number = spam_l[0].count('*')
168
169                        if number >= self.SPAM_LEVEL:
170                                return 'Spam'
171
172                elif message.has_key('X-Virus-found'):                  # treat virus mails as spam
173                        return 'Spam'
174
175                return self.get_config('ticket', 'default_component')
176
177        def to_unicode(self, str):
178                """
179                Email has 7 bit ASCII code, convert it to unicode with the charset
180        that is encoded in 7-bit ASCII code and encode it as utf-8 so Trac
181                understands it.
182                """
183                results =  Header.decode_header(str)
184                str = None
185                for text,format in results:
186                        if format:
187                                try:
188                                        temp = unicode(text, format)
189                                except (UnicodeError,LookupError):
190                                        # This always works
191                                        #
192                                        temp = unicode(text, 'iso-8859-15')
193                                       
194                                temp =  temp.encode('utf-8')
195                        else:
196                                temp = string.strip(text)
197
198                        if str:
199                                str = '%s %s' %(str, temp)
200                        else:
201                                str = temp
202
203                return str
204
205        def debug_attachments(self, message):
206                n = 0
207                for part in message.walk():
208                        if part.get_content_maintype() == 'multipart':      # multipart/* is just a container
209                                print 'TD: multipart container'
210                                continue
211
212                        n = n + 1
213                        print 'TD: part%d: Content-Type: %s' % (n, part.get_content_type())
214                        print 'TD: part%d: filename: %s' % (n, part.get_filename())
215
216                        if part.is_multipart():
217                                print 'TD: this part is multipart'
218                                payload = part.get_payload(decode=1)
219                                print 'TD: payload:', payload
220                        else:
221                                print 'TD: this part is not multipart'
222
223                        part_file = '/var/tmp/part%d' % n
224                        print 'TD: writing part%d (%s)' % (n,part_file)
225                        fx = open(part_file, 'wb')
226                        text = part.get_payload(decode=1)
227                        if not text:
228                                text = '(None)'
229                        fx.write(text)
230                        fx.close()
231                        try:
232                                os.chmod(part_file,S_IRWXU|S_IRWXG|S_IRWXO)
233                        except OSError:
234                                pass
235
236        def email_header_txt(self, m):
237                """
238                Display To and CC addresses in description field
239                """
240                str = ''
241                if m['To'] and len(m['To']) > 0 and m['To'] != 'hic@sara.nl':
242                        str = "'''To:''' %s [[BR]]" %(m['To'])
243                if m['Cc'] and len(m['Cc']) > 0:
244                        str = "%s'''Cc:''' %s [[BR]]" % (str, m['Cc'])
245
246                return str
247
248        def set_owner(self, ticket):
249                """
250                Select default owner for ticket component
251                """
252                cursor = self.db.cursor()
253                sql = "SELECT owner FROM component WHERE name='%s'" % ticket['component']
254                cursor.execute(sql)
255                try:
256                        ticket['owner'] = cursor.fetchone()[0]
257                except TypeError, detail:
258                        ticket['owner'] = "UNKNOWN"
259
260        def get_author_emailaddrs(self, message):
261                """
262                Get the default author name and email address from the message
263                """
264                self.author, self.email_addr  = email.Utils.parseaddr(message['from'])
265
266                # Look for email address in registered trac users
267                #
268                if self.VERSION == 0.8:
269                        users = []
270                else:
271                        users = [ u for (u, n, e) in self.env.get_known_users(self.db)
272                                if e == self.email_addr ]
273
274                if len(users) == 1:
275                        self.email_field = users[0]
276                else:
277                        self.email_field =  self.to_unicode(message['from'])
278
279        def set_reply_fields(self, ticket, message):
280                """
281                Set all the right fields for a new ticket
282                """
283                ticket['reporter'] = self.email_field
284
285                # Put all CC-addresses in ticket CC field
286                #
287                if self.REPLY_ALL:
288                        #tos = message.get_all('to', [])
289                        ccs = message.get_all('cc', [])
290
291                        addrs = email.Utils.getaddresses(ccs)
292
293                        # Remove reporter email address if notification is
294                        # on
295                        #
296                        if self.notification:
297                                try:
298                                        addrs.remove((self.author, self.email_addr))
299                                except ValueError, detail:
300                                        pass
301
302                        for name,mail in addrs:
303                                        try:
304                                                ticket['cc'] = '%s,%s' %(ticket['cc'], mail)
305                                        except:
306                                                ticket['cc'] = mail
307
308        def save_email_for_debug(self, message):
309                msg_file = '/var/tmp/msg.txt'
310                print 'TD: saving email to %s' % msg_file
311                fx = open(msg_file, 'wb')
312                fx.write('%s' % message)
313                fx.close()
314                try:
315                        os.chmod(msg_file,S_IRWXU|S_IRWXG|S_IRWXO)
316                except OSError:
317                        pass
318
319        def ticket_update(self, m):
320                """
321                If the current email is a reply to an existing ticket, this function
322                will append the contents of this email to that ticket, instead of
323                creating a new one.
324                """
325                if not m['Subject']:
326                        return False
327                else:
328                        subject  = self.to_unicode(m['Subject'])
329
330                TICKET_RE = re.compile(r"""
331                                        (?P<ticketnr>[#][0-9]+:)
332                                        """, re.VERBOSE)
333
334                result =  TICKET_RE.search(subject)
335                if not result:
336                        return False
337
338                body_text = self.get_body_text(m)
339                body_text = '{{{\n%s\n}}}' %body_text
340
341                # Strip '#' and ':' from ticket_id
342                #
343                ticket_id = result.group('ticketnr')
344                ticket_id = int(ticket_id[1:-1])
345
346                # Get current time
347                #
348                when = int(time.time())
349
350                if self.VERSION  == 0.8:
351                        tkt = Ticket(self.db, ticket_id)
352                        tkt.save_changes(self.db, self.author, body_text, when)
353                else:
354                        tkt = Ticket(self.env, ticket_id, self.db)
355                        tkt.save_changes(self.author, body_text, when)
356                        tkt['id'] = ticket_id
357
358                self.attachments(m, tkt)
359
360                if self.notification:
361                        self.notify(tkt, False, when)
362
363                return True
364
365        def new_ticket(self, msg):
366                """
367                Create a new ticket
368                """
369                tkt = Ticket(self.env)
370                tkt['status'] = 'new'
371
372                # Some defaults
373                #
374                tkt['milestone'] = self.get_config('ticket', 'default_milestone')
375                tkt['priority'] = self.get_config('ticket', 'default_priority')
376                tkt['severity'] = self.get_config('ticket', 'default_severity')
377                tkt['version'] = self.get_config('ticket', 'default_version')
378
379                if not msg['Subject']:
380                        tkt['summary'] = '(geen subject)'
381                else:
382                        tkt['summary'] = self.to_unicode(msg['Subject'])
383
384
385                if settings.has_key('component'):
386                        tkt['component'] = settings['component']
387                else:
388                        tkt['component'] = self.spam(msg)
389
390                # Must make this an option or so, discard SPAM messages or save then
391                # and delete later
392                #
393                #if self.SPAM_LEVEL and self.spam(msg):
394                #       print 'This message is a SPAM. Automatic ticket insertion refused (SPAM level > %d' % self.SPAM_LEVEL
395                #       sys.exit(1)
396
397                # Set default owner for component
398                #
399                self.set_owner(tkt)
400                self.set_reply_fields(tkt, msg)
401
402                # produce e-mail like header
403                #
404                head = ''
405                if self.EMAIL_HEADER > 0:
406                        head = self.email_header_txt(msg)
407
408
409                body_text = self.get_body_text(msg)
410                tkt['description'] = ''
411
412                # Insert ticket in database with empty description
413                #
414                when = int(time.time())
415                if self.VERSION == 0.8:
416                        tkt['id'] = tkt.insert(self.db)
417                else:
418                        tkt['id'] = tkt.insert()
419
420                n =  self.attachments(msg, tkt)
421                if n:
422                        attach_str = '\nThis message has %d attachment(s)\n' %(n)
423                else:
424                        attach_str = ''
425
426                # Always update the description else we get two emails one for the new ticket
427                # and for the attachments. It is an ugly hack but with trac you can not add
428                # attachments without an ticket id
429                #
430                mailto = ''
431                if self.MAILTO:
432                        mailto = self.html_mailto_link(self.to_unicode(msg['subject']), tkt['id'], body_text)
433                        tkt['description'] = '%s%s%s\n{{{\n%s\n}}}\n' %(head, attach_str, mailto, body_text)
434                        comment = 'Added mailto: link + description'
435                else:
436                        tkt['description'] = '%s%s\n{{{\n%s\n}}}\n' %(head, attach_str, body_text)
437                        comment = 'Added description'
438
439                # Save the real description and other changes
440                #
441                if self.VERSION  == 0.8:
442                        tkt.save_changes(self.db, self.author, comment, when)
443                else:
444                        tkt.save_changes(self.author, comment, when)
445
446                if self.notification:
447                        self.notify(tkt, True, when)
448
449        def parse(self, fp):
450                m = email.message_from_file(fp)
451                if not m:
452                        return
453
454                if self.DEBUG > 1:        # save the entire e-mail message text
455                        self.save_email_for_debug(m)
456                        self.debug_attachments(m)
457
458                self.db = self.env.get_db_cnx()
459                self.get_author_emailaddrs(m)
460
461                if self.get_config('notification', 'smtp_enabled') in ['true']:
462                        self.notification = 1
463                else:
464                        self.notification = 0
465
466                # Must we update existing tickets
467                #
468                if self.TICKET_UPDATE > 0:
469                        if self.ticket_update(m):
470                                return True
471
472                self.new_ticket(m)
473
474        def get_body_text(self, msg):
475                """
476                put the message text in the ticket description or in the changes field.
477                message text can be plain text or html or something else
478                """
479                has_description = 0
480                ubody_text = '\n{{{\nNo plain text message\n}}}\n'
481                for part in msg.walk():
482
483                        # 'multipart/*' is a container for multipart messages
484                        #
485                        if part.get_content_maintype() == 'multipart':
486                                continue
487
488                        if part.get_content_type() == 'text/plain':
489                                # Try to decode, if fails then do not decode
490                                #
491                                body_text = part.get_payload(decode=1)         
492                                if not body_text:                       
493                                        body_text = part.get_payload(decode=0) 
494
495                                # Get contents charset (iso-8859-15 if not defined in mail headers)
496                                # UTF-8 encode body_text
497                                #
498                                charset = part.get_content_charset('iso-8859-15')
499
500                                try:
501                                        temp = unicode(body_text, charset)
502                                except (UnicodeError,LookupError):
503                                        temp = unicode(body_text, 'iso-8859-15')
504
505                                #ubody_text = unicode(body_text, charset).encode('utf-8')
506                                ubody_text = temp.encode('utf-8')
507
508                        elif part.get_content_type() == 'text/html':
509                                ubody_text = '\n\n(see attachment for HTML mail message)\n'
510
511                        else:
512                                ubody_text = '\n\n(see attachment for message)\n'
513
514                        has_description = 1
515                        break           # we have the description, so break
516
517                if not has_description:
518                        ubody_text = '\n\n(see attachment for message)\n'
519
520                return ubody_text
521
522        def notify(self, tkt , new=True, modtime=0):
523                """
524                A wrapper for the TRAC notify function. So we can use templates
525                """
526                try:
527                        # create false {abs_}href properties, to trick Notify()
528                        #
529                        self.env.abs_href = Href(self.get_config('project', 'url'))
530                        self.env.href = Href(self.get_config('project', 'url'))
531
532                        tn = TicketNotifyEmail(self.env)
533                        if self.notify_template:
534                                tn.template_name = self.notify_template;
535
536                        tn.notify(tkt, new, modtime)
537
538                except Exception, e:
539                        print 'TD: Failure sending notification on creation of ticket #%s: %s' %(tkt['id'], e)
540
541        def mail_line(self, str):
542                return '%s %s' % (self.comment, str)
543
544
545        def html_mailto_link(self, subject, id, body):
546                if not self.author:
547                        author = self.email_addr
548                else:   
549                        author = self.to_unicode(self.author)
550
551                # Must find a fix
552                #
553                #arr = string.split(body, '\n')
554                #arr = map(self.mail_line, arr)
555                #body = string.join(arr, '\n')
556                #body = '%s wrote:\n%s' %(author, body)
557
558                # Temporary fix
559                str = 'mailto:%s?Subject=%s&Cc=%s' %(
560                       urllib.quote(self.email_addr),
561                           urllib.quote('Re: #%s: %s' %(id, subject)),
562                           urllib.quote(self.MAILTO_CC)
563                           )
564
565                str = '\n{{{\n#!html\n<a href="%s">Reply to: %s</a>\n}}}\n' %(str, author)
566
567                return str
568
569        def attachments(self, message, ticket):
570                '''
571                save any attachments as files in the ticket's directory
572                '''
573                count = 0
574                first = 0
575                number = 0
576                for part in message.walk():
577                        if part.get_content_maintype() == 'multipart':          # multipart/* is just a container
578                                continue
579
580                        if not first:                                                                           # first content is the message
581                                first = 1
582                                if part.get_content_type() == 'text/plain':             # if first is text, is was already put in the description
583                                        continue
584
585                        filename = part.get_filename()
586                        count = count + 1
587                        if not filename:
588                                number = number + 1
589                                filename = 'part%04d' % number
590
591                                ext = mimetypes.guess_extension(part.get_content_type())
592                                if not ext:
593                                        ext = '.bin'
594
595                                filename = '%s%s' % (filename, ext)
596                        else:
597                                filename = self.to_unicode(filename)
598
599                        # From the trac code
600                        #
601                        filename = filename.replace('\\', '/').replace(':', '/')
602                        filename = os.path.basename(filename)
603
604                        # We try to normalize the filename to utf-8 NFC if we can.
605                        # Files uploaded from OS X might be in NFD.
606                        #
607                        if sys.version_info[0] > 2 or (sys.version_info[0] == 2 and sys.version_info[1] >= 3):
608                                filename = unicodedata.normalize('NFC', unicode(filename, 'utf-8')).encode('utf-8') 
609
610                        url_filename = urllib.quote(filename)
611                        if self.VERSION == 0.8:
612                                dir = os.path.join(self.env.get_attachments_dir(), 'ticket',
613                                                        urllib.quote(str(ticket['id'])))
614                                if not os.path.exists(dir):
615                                        mkdir_p(dir, 0755)
616                        else:
617                                dir = '/tmp'
618
619                        path, fd =  util.create_unique_file(os.path.join(dir, url_filename))
620                        text = part.get_payload(decode=1)
621                        if not text:
622                                text = '(None)'
623                        fd.write(text)
624                        fd.close()
625
626                        # get the filesize
627                        #
628                        stats = os.lstat(path)
629                        filesize = stats[stat.ST_SIZE]
630
631                        # Insert the attachment it differs for the different TRAC versions
632                        #
633                        if self.VERSION == 0.8:
634                                cursor = self.db.cursor()
635                                try:
636                                        cursor.execute('INSERT INTO attachment VALUES("%s","%s","%s",%d,%d,"%s","%s","%s")'
637                                                %('ticket', urllib.quote(str(ticket['id'])), filename + '?format=raw', filesize,
638                                                int(time.time()),'', self.author, 'e-mail') )
639
640                                # Attachment is already known
641                                #
642                                except sqlite.IntegrityError:   
643                                        #self.db.close()
644                                        return count
645
646                                self.db.commit()
647
648                        else:
649                                fd = open(path)
650                                att = attachment.Attachment(self.env, 'ticket', ticket['id'])
651                                att.insert(url_filename, fd, filesize)
652                                fd.close()
653
654                # Return how many attachments
655                #
656                return count
657
658
659def mkdir_p(dir, mode):
660        '''do a mkdir -p'''
661
662        arr = string.split(dir, '/')
663        path = ''
664        for part in arr:
665                path = '%s/%s' % (path, part)
666                try:
667                        stats = os.stat(path)
668                except OSError:
669                        os.mkdir(path, mode)
670
671
672def ReadConfig(file, name):
673        """
674        Parse the config file
675        """
676
677        if not os.path.isfile(file):
678                print 'File %s does not exist' %file
679                sys.exit(1)
680
681        config = ConfigParser.ConfigParser()
682        try:
683                config.read(file)
684        except ConfigParser.MissingSectionHeaderError,detail:
685                print detail
686                sys.exit(1)
687
688
689        # Use given project name else use defaults
690        #
691        if name:
692                if not config.has_section(name):
693                        print "Not a valid project name: %s" %name
694                        print "Valid names: %s" %config.sections()
695                        sys.exit(1)
696
697                project =  dict()
698                for option in  config.options(name):
699                        project[option] = config.get(name, option)
700
701        else:
702                project = config.defaults()
703
704        return project
705
706
707if __name__ == '__main__':
708        # Default config file
709        #
710        configfile = '@email2trac_conf@'
711        project = ''
712        component = ''
713        ENABLE_SYSLOG = 0
714               
715        try:
716                opts, args = getopt.getopt(sys.argv[1:], 'chf:p:', ['component=','help', 'file=', 'project='])
717        except getopt.error,detail:
718                print __doc__
719                print detail
720                sys.exit(1)
721       
722        project_name = None
723        for opt,value in opts:
724                if opt in [ '-h', '--help']:
725                        print __doc__
726                        sys.exit(0)
727                elif opt in ['-c', '--component']:
728                        component = value
729                elif opt in ['-f', '--file']:
730                        configfile = value
731                elif opt in ['-p', '--project']:
732                        project_name = value
733       
734        settings = ReadConfig(configfile, project_name)
735        if not settings.has_key('project'):
736                print __doc__
737                print 'No Trac project is defined in the email2trac config file.'
738                sys.exit(1)
739       
740        if component:
741                settings['component'] = component
742       
743        if settings.has_key('trac_version'):
744                version = float(settings['trac_version'])
745        else:
746                version = trac_default_version
747
748        if settings.has_key('enable_syslog'):
749                ENABLE_SYSLOG =  float(settings['enable_syslog'])
750                       
751        #debug HvB
752        #print settings
753       
754        try:
755                if version == 0.8:
756                        from trac.Environment import Environment
757                        from trac.Ticket import Ticket
758                        from trac.Notify import TicketNotifyEmail
759                        from trac.Href import Href
760                        from trac import util
761                        import sqlite
762                elif version == 0.9:
763                        from trac import attachment
764                        from trac.env import Environment
765                        from trac.ticket import Ticket
766                        from trac.web.href import Href
767                        from trac import util
768                        from trac.Notify import TicketNotifyEmail
769                elif version == 0.10:
770                        from trac import attachment
771                        from trac.env import Environment
772                        from trac.ticket import Ticket
773                        from trac.web.href import Href
774                        from trac import util
775                        # see http://projects.edgewall.com/trac/changeset/2799
776                        from trac.ticket.notification import TicketNotifyEmail
777       
778                env = Environment(settings['project'], create=0)
779                tktparser = TicketEmailParser(env, settings, version)
780                tktparser.parse(sys.stdin)
781
782        # Catch all errors ans log to SYSLOG if we have enabled this
783        # else stdout
784        #
785        except Exception, error:
786                import syslog, traceback
787
788                if ENABLE_SYSLOG:
789                        syslog.openlog('email2trac', syslog.LOG_NOWAIT)
790                        etype, evalue, etb = sys.exc_info()
791                        for e in traceback.format_exception(etype, evalue, etb):
792                                syslog.syslog(e)
793                        syslog.closelog()
794                else:
795                        traceback.print_exc()
796
797# EOB
Note: See TracBrowser for help on using the repository browser.