Tizen 2.1 base
[platform/upstream/hplip.git] / base / logger.py
1 # -*- coding: utf-8 -*-
2 #
3 # (c) Copyright 2002-2008 Hewlett-Packard Development Company, L.P.
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
18 #
19 # Authors: Doug Deprenger, Don Welch
20 #
21
22 # Std Lib
23 import sys
24 import thread # TODO: Use threading instead (thread deprecated in Python 3.0)
25 import syslog
26 import traceback
27 import string
28 import os
29 import re
30 import pprint
31
32 identity = string.maketrans('','')
33 unprintable = identity.translate(identity, string.printable)
34
35 def printable(s):
36     return s.translate(identity, unprintable)
37
38 DEFAULT_LOG_LEVEL = 'info'
39
40 class Logger(object):
41     LOG_LEVEL_NONE = 99
42     #LOG_LEVEL_INFO = 50
43     LOG_LEVEL_FATAL = 40
44     LOG_LEVEL_ERROR = 30
45     LOG_LEVEL_WARN = 20
46     LOG_LEVEL_INFO = 10
47     LOG_LEVEL_DEBUG3 = 3
48     LOG_LEVEL_DBG3 = 3
49     LOG_LEVEL_DEBUG2 = 2
50     LOG_LEVEL_DBG2 = 2
51     LOG_LEVEL_DEBUG = 1
52     LOG_LEVEL_DBG = 1
53
54     logging_levels = {'none' : LOG_LEVEL_NONE,
55                        'fata' : LOG_LEVEL_FATAL,
56                        'fatal' : LOG_LEVEL_FATAL,
57                        'erro' : LOG_LEVEL_ERROR,
58                        'error' : LOG_LEVEL_ERROR,
59                        'warn' : LOG_LEVEL_WARN,
60                        'info' : LOG_LEVEL_INFO,
61                        'debug' : LOG_LEVEL_DEBUG,
62                        'dbg'  : LOG_LEVEL_DEBUG,
63                        'debug2' : LOG_LEVEL_DEBUG2,
64                        'dbg2' : LOG_LEVEL_DEBUG2,
65                        'debug3' : LOG_LEVEL_DEBUG3,
66                        'dbg3' : LOG_LEVEL_DEBUG3,
67                        }
68
69     LOG_TO_DEV_NULL = 0
70     LOG_TO_CONSOLE = 1
71     LOG_TO_SCREEN = 1
72     LOG_TO_FILE = 2
73     LOG_TO_CONSOLE_AND_FILE = 3
74     LOG_TO_BOTH = 3
75
76     # Copied from Gentoo Portage output.py
77     # Copyright 1998-2003 Daniel Robbins, Gentoo Technologies, Inc.
78     # Distributed under the GNU Public License v2
79     codes={}
80     codes["reset"]="\x1b[0m"
81     codes["bold"]="\x1b[01m"
82
83     codes["teal"]="\x1b[36;06m"
84     codes["turquoise"]="\x1b[36;01m"
85
86     codes["fuscia"]="\x1b[35;01m"
87     codes["purple"]="\x1b[35;06m"
88
89     codes["blue"]="\x1b[34;01m"
90     codes["darkblue"]="\x1b[34;06m"
91
92     codes["green"]="\x1b[32;01m"
93     codes["darkgreen"]="\x1b[32;06m"
94
95     codes["yellow"]="\x1b[33;01m"
96     codes["brown"]="\x1b[33;06m"
97
98     codes["red"]="\x1b[31;01m"
99     codes["darkred"]="\x1b[31;06m"
100
101
102     def __init__(self, module='', level=LOG_LEVEL_INFO, where=LOG_TO_CONSOLE_AND_FILE,
103                  log_datetime=False, log_file=None):
104
105         self._where = where
106         self._log_file = log_file
107         self._log_file_f = None
108         self._log_datetime = log_datetime
109         self._lock = thread.allocate_lock()
110         self.module = module
111         self.pid = os.getpid()
112         self.fmt = True
113         self.set_level(level)
114
115
116     def set_level(self, level):
117         if isinstance(level, str):
118             level = level.lower()
119             if level in Logger.logging_levels.keys():
120                 self._level = Logger.logging_levels.get(level, Logger.LOG_LEVEL_INFO)
121                 return True
122             else:
123                 self.error("Invalid logging level: %s" % level)
124                 return False
125
126         elif isinstance(level, int):
127             if Logger.LOG_LEVEL_DEBUG3 <= level <= Logger.LOG_LEVEL_FATAL:
128                 self._level = level
129             else:
130                 self._level = Logger.LOG_LEVEL_ERROR
131                 self.error("Invalid logging level: %d" % level)
132                 return False
133
134         else:
135             return False
136
137
138     def set_module(self, module):
139         self.module = module
140         self.pid = os.getpid()
141
142
143     def no_formatting(self):
144         self.fmt = False
145
146
147     def set_logfile(self, log_file):
148         self._log_file = log_file
149         try:
150             self._log_file_f = file(self._log_file, 'w')
151         except IOError:
152             self._log_file = None
153             self._log_file_f = None
154             self._where = Logger.LOG_TO_SCREEN
155
156
157     def get_logfile(self):
158         return self._log_file
159
160
161     def set_where(self, where):
162         self._where = where
163
164
165     def get_level(self):
166         return self._level
167
168
169     def is_debug(self):
170         return self._level <= Logger.LOG_LEVEL_DEBUG3
171
172     level = property(get_level, set_level)
173
174
175     def log(self, message, level, newline=True):
176         if self._level <= level:
177             if self._where in (Logger.LOG_TO_CONSOLE, Logger.LOG_TO_CONSOLE_AND_FILE):
178                 try:
179                     self._lock.acquire()
180                     if level >= Logger.LOG_LEVEL_WARN:
181                         out = sys.stderr
182                     else:
183                         out = sys.stdout
184
185                     try:
186                         out.write(message)
187                     except UnicodeEncodeError:
188                         out.write(message.encode("utf-8"))
189
190                     if newline:
191                         out.write('\n')
192
193                     out.flush()
194                 finally:
195                     self._lock.release()
196
197
198     def log_to_file(self, message):
199         if self._log_file_f is not None:
200             try:
201                 self._lock.acquire()
202                 self._log_file_f.write(message.replace('\x1b', ''))
203                 self._log_file_f.write('\n')
204
205             finally:
206                 self._lock.release()
207
208
209     def stderr(self, message):
210         try:
211             self._lock.acquire()
212             sys.stderr.write("%s: %s\n" % (self.module, message))
213         finally:
214             self._lock.release()
215
216
217     def debug(self, message):
218         if self._level <= Logger.LOG_LEVEL_DEBUG:
219             txt = "%s[%d]: debug: %s" % (self.module, self.pid, message)
220             self.log(self.color(txt, 'blue'), Logger.LOG_LEVEL_DEBUG)
221
222             if self._log_file is not None and \
223                 self._where in (Logger.LOG_TO_FILE, Logger.LOG_TO_CONSOLE_AND_FILE):
224                 self.log_to_file(txt)
225
226     dbg = debug
227
228     def debug2(self, message):
229         if self._level <= Logger.LOG_LEVEL_DEBUG2:
230             txt = "%s[%d]: debug2: %s" % (self.module, self.pid, message)
231             self.log(self.color(txt, 'blue'), Logger.LOG_LEVEL_DEBUG2)
232
233             if self._log_file is not None and \
234                 self._where in (Logger.LOG_TO_FILE, Logger.LOG_TO_CONSOLE_AND_FILE):
235                 self.log_to_file(txt)
236     dbg2 = debug2
237
238     def debug3(self, message):
239         if self._level <= Logger.LOG_LEVEL_DEBUG3:
240             txt = "%s[%d]: debug3: %s" % (self.module, self.pid, message)
241             self.log(self.color(txt, 'blue'), Logger.LOG_LEVEL_DEBUG3)
242
243             if self._log_file is not None and \
244                 self._where in (Logger.LOG_TO_FILE, Logger.LOG_TO_CONSOLE_AND_FILE):
245                 self.log_to_file(txt)
246     dbg3 = debug3
247
248
249     def debug_block(self, title, block):
250         if self._level <= Logger.LOG_LEVEL_DEBUG:
251             line = "%s[%d]: debug: %s:" % (self.module,  self.pid, title)
252             self.log(self.color(line, 'blue'), Logger.LOG_LEVEL_DEBUG)
253             self.log(self.color(block, 'blue'), Logger.LOG_LEVEL_DEBUG)
254
255             if self._log_file is not None and \
256                 self._where in (Logger.LOG_TO_FILE, Logger.LOG_TO_CONSOLE_AND_FILE):
257
258                 self.log_to_file(line % (self.module, self.pid, title))
259                 self.log_to_file(block)
260
261
262     printable = """ !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~  """
263
264     def log_data(self, data, width=16):
265         if self._level <= Logger.LOG_LEVEL_DEBUG:
266             if data:
267                 index, line = 0, data[0:width]
268                 while line:
269                     txt = ' '.join(['%04x: ' % index, ' '.join(['%02x' % ord(d) for d in line]),
270                         ' '*(width*3-3*len(line)), ''.join([('.', i)[i in Logger.printable] for i in line])])
271
272                     self.log(self.color("%s[%d]: debug: %s" % (self.module,  self.pid, txt), 'blue'),
273                         Logger.LOG_LEVEL_DEBUG)
274
275                     index += width
276                     line = data[index:index+width]
277             else:
278                 self.log(self.color("%s[%d]: debug: %s" % (self.module,  self.pid, "0000: (no data)"), 'blue'),
279                         Logger.LOG_LEVEL_DEBUG)
280
281
282     def info(self, message=''):
283         if self._level <= Logger.LOG_LEVEL_INFO:
284             self.log(message, Logger.LOG_LEVEL_INFO)
285
286             if self._log_file is not None and \
287                 self._where in (Logger.LOG_TO_FILE, Logger.LOG_TO_CONSOLE_AND_FILE):
288                 self.log_to_file("%s[%d]: info: :%s" % (self.module, self.pid, message))
289
290     information = info
291
292
293     def warn(self, message):
294         if self._level <= Logger.LOG_LEVEL_WARN:
295             txt = "warning: %s" % message.encode('utf-8')
296             self.log(self.color(txt, 'fuscia'), Logger.LOG_LEVEL_WARN)
297
298             syslog.syslog(syslog.LOG_WARNING, "%s[%d]: %s" % (self.module, self.pid, txt))
299
300             if self._log_file is not None and \
301                 self._where in (Logger.LOG_TO_FILE, Logger.LOG_TO_CONSOLE_AND_FILE):
302                 self.log_to_file(txt)
303
304     warning = warn
305
306
307     def note(self, message):
308         if self._level <= Logger.LOG_LEVEL_WARN:
309             txt = "note: %s" % message
310             self.log(self.color(txt, 'green'), Logger.LOG_LEVEL_WARN)
311
312             if self._log_file is not None and \
313                 self._where in (Logger.LOG_TO_FILE, Logger.LOG_TO_CONSOLE_AND_FILE):
314                 self.log_to_file(txt)
315
316     notice = note
317
318
319     def error(self, message):
320         if self._level <= Logger.LOG_LEVEL_ERROR:
321             txt = "error: %s" % message.encode("utf-8")
322             self.log(self.color(txt, 'red'), Logger.LOG_LEVEL_ERROR)
323
324             syslog.syslog(syslog.LOG_ALERT, "%s[%d]: %s" % (self.module, self.pid, txt))
325
326             if self._log_file is not None and \
327                 self._where in (Logger.LOG_TO_FILE, Logger.LOG_TO_CONSOLE_AND_FILE):
328                 self.log_to_file(txt)
329
330
331     def fatal(self, message):
332         if self._level <= Logger.LOG_LEVEL_FATAL:
333             txt = "fatal error: :%s" % self.module.encode('utf-8')
334             self.log(self.color(txt, 'red'), Logger.LOG_LEVEL_DEBUG)
335
336             syslog.syslog(syslog.LOG_ALERT, "%s[%d]: %s" % (self.module, self.pid, txt))
337
338             if self._log_file is not None and \
339                 self._where in (Logger.LOG_TO_FILE, Logger.LOG_TO_CONSOLE_AND_FILE):
340                 self.log_to_file(txt)
341
342
343     def exception(self):
344         typ, value, tb = sys.exc_info()
345         body = "Traceback (innermost last):\n"
346         lst = traceback.format_tb(tb) + traceback.format_exception_only(typ, value)
347         body = body + "%-20s %s" % (''.join(lst[:-1]), lst[-1],)
348         self.fatal(body)
349
350
351     def color(self, text, color):
352         if self.fmt:
353             return ''.join([Logger.codes.get(color, 'bold'), text, Logger.codes['reset']])
354         return text
355
356
357     def bold(self, text):
358         return self.color(text, 'bold')
359
360
361     def red(self, text):
362         return self.color(text, 'red')
363
364
365     def green(self, text):
366         return self.color(text, 'green')
367
368
369     def purple(self, text):
370         return self.color(text, 'purple')
371
372
373     def yellow(self, text):
374         return self.color(text, 'yellow')
375
376
377     def darkgreen(self, text):
378         return self.color(text, 'darkgreen')
379
380
381     def blue(self, text):
382         return self.color(text, 'blue')
383
384     """Pretty print an XML document.
385
386     LICENCE:
387     Copyright (c) 2008, Fredrik Ekholdt
388     All rights reserved.
389
390     Redistribution and use in source and binary forms, with or without
391     modification, are permitted provided that the following conditions are met:
392
393     * Redistributions of source code must retain the above copyright notice,
394     this list of conditions and the following disclaimer.
395
396     * Redistributions in binary form must reproduce the above copyright notice,
397     this list of conditions and the following disclaimer in the documentation
398     and/or other materials provided with the distribution.
399
400     * Neither the name of None nor the names of its contributors may be used to
401     endorse or promote products derived from this software without specific prior
402     written permission.
403
404     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
405     AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
406     IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
407     ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
408     LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
409     CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
410     SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
411     INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
412     CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
413     ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
414     POSSIBILITY OF SUCH DAMAGE."""
415
416     def _pprint_line(self, indent_level, line, width=100, level=LOG_LEVEL_DEBUG):
417         if line.strip():
418             start = ""
419             number_chars = 0
420             for l in range(indent_level):
421                 start = start + " "
422                 number_chars = number_chars + 1
423             try:
424                 elem_start = re.findall("(\<\W{0,1}\w+) ?", line)[0]
425                 elem_finished = re.findall("([?|\]\]]*\>)", line)[0]
426                 #should not have *
427                 attrs = re.findall("(\S*?\=\".*?\")", line)
428                 #output.write(start + elem_start)
429                 self.log(start+elem_start, level, False)
430                 number_chars = len(start + elem_start)
431                 for attr in attrs:
432                     if (attrs.index(attr) + 1) == len(attrs):
433                         number_chars = number_chars + len(elem_finished)
434
435                     if (number_chars + len(attr) + 1) > width:
436                         #output.write("\n")
437                         self.log("\n", level, False)
438                         #for i in range(len(start + elem_start) + 1):
439                             ##output.write(" ")
440                             #self.log(" ", level, False)
441                         self.log(" "*(len(start + elem_start) + 1), level, False)
442                         number_chars = len(start + elem_start) + 1
443
444                     else:
445                         #output.write(" ")
446                         self.log(" ", level, False)
447                         number_chars = number_chars + 1
448                     #output.write(attr)
449                     self.log(attr, level, False)
450                     number_chars = number_chars + len(attr)
451                 #output.write(elem_finished + "\n")
452                 self.log(elem_finished + "\n", level, False)
453
454             except IndexError:
455                 #give up pretty print this line
456                 #output.write(start + line + "\n")
457                 self.log(start + line + "\n", level, False)
458
459
460     def _pprint_elem_content(self, indent_level, line, level=LOG_LEVEL_DEBUG):
461         if line.strip():
462             #for l in range(indent_level):
463                 ##output.write(" ")
464                 #self.log(" ", level, False)
465             self.log(" "*indent_level, level, False)
466
467             #output.write(line + "\n")
468             self.log(line + "\n", level, False)
469
470
471     def _get_next_elem(self, data):
472         start_pos = data.find("<")
473         end_pos = data.find(">") + 1
474         retval = data[start_pos:end_pos]
475         stopper = retval.rfind("/")
476         if stopper < retval.rfind("\""):
477             stopper = -1
478
479         single = (stopper > -1 and ((retval.find(">") - stopper) < (stopper - retval.find("<"))))
480
481         ignore_excl = retval.find("<!") > -1
482         ignore_question =  retval.find("<?") > -1
483
484         if ignore_excl:
485             cdata = retval.find("<![CDATA[") > -1
486             if cdata:
487                 end_pos = data.find("]]>")
488                 if end_pos > -1:
489                     end_pos = end_pos + len("]]>")
490
491         elif ignore_question:
492             end_pos = data.find("?>") + len("?>")
493
494         ignore = ignore_excl or ignore_question
495
496         no_indent = ignore or single
497
498         #print retval, end_pos, start_pos, stopper > -1, no_indent
499         return start_pos, \
500             end_pos, \
501             stopper > -1, \
502             no_indent
503
504
505     def xml(self, xml, level=LOG_LEVEL_DEBUG, indent=4, width=80):
506         """Pretty print xml.
507         Use indent to select indentation level. Default is 4   """
508         data = xml
509         indent_level = 0
510         start_pos, end_pos, is_stop, no_indent  = self._get_next_elem(data)
511         while ((start_pos > -1 and end_pos > -1)):
512             self._pprint_elem_content(indent_level, data[:start_pos].strip(), level=level)
513             data = data[start_pos:]
514             if is_stop and not no_indent:
515                 indent_level = indent_level - indent
516
517             self._pprint_line(indent_level,
518                         data[:end_pos - start_pos],
519                         width=width, level=level)
520
521             data = data[end_pos - start_pos:]
522             if not is_stop and not no_indent :
523                 indent_level = indent_level + indent
524
525             if not data:
526                 break
527             else:
528                 start_pos, end_pos, is_stop, no_indent  = self._get_next_elem(data)
529
530     # END: Pretty print an XML document.
531
532
533     def pprint(self, data):
534         self.info(pprint.pformat(data))