import repository from arizona
[raven.git] / lib / arizona-lib / arizonareport.py
1 # /usr/bin/env python
2
3 """
4 <Program Name>
5    storkreport.py
6
7 <Started>   
8    February 4, 2005
9
10 <Author>
11    sethh@cs.arizona.edu
12    Justin Cappos
13    Jeffry Johnston
14
15 <Purpose>
16    Provides Error Reporting/Logging Utility Functions.
17
18    The functions are named loosely based on the syslog levels.  However,
19    because syslog has 7 priority levels, and stork only has 4, the 
20    send_error and send_out_* series only contain debug, info, warning, and
21    err (notice, alert, and emergency were omitted).  send_syslog supports 
22    all seven levels, however.
23
24    Current syslog implementation assumes local syslog (no remote servers),
25    and a facility of LOG_USER (defaults from the syslog module).  
26
27    On July 10th, 2007, Justin is doing a pretty substantial rewrite.   Blame
28    him for any resulting cruft.
29 """
30
31 #           [option, long option,      variable,  action,        data,  default, metavar, description]
32 """arizonaconfig
33    options=[["-Q",   "--veryquiet",    "verbose", "store_const", 0,     2,       None,    "be very quiet"],
34             ["-q",   "--quiet",        "verbose", "store_const", 1,     2,       None,    "be quiet"],
35             ["-v",   "--verbose",      "verbose", "store_const", 2,     2,       None,    "be verbose (default)"],
36             ["-V",   "--veryverbose",  "verbose", "store_const", 3,     2,       None,    "be very verbose (useful for debugging)"],
37             ["",     "--ultraverbose", "verbose", "store_const", 4,     2,       None,    "be extremely verbose (might be useful for debugging)"] ]
38    includes=[]        
39 """
40
41 import arizonaconfig
42 import struct
43 import sys
44 import traceback
45
46 # Justin: This is needed because syslog does not exist on Windows.   We don't
47 # use any function that does a syslog on Windows so this should be okay.
48 try:
49   import syslog
50 except ImportError:
51   pass
52
53
54
55 # syslog severity levels
56 EMERG = 0
57 ALERT = 1
58 CRIT = 2
59 ERR = 3
60 WARNING = 4
61 NOTICE = 5
62 INFO = 6
63 DEBUG = 7 
64
65 # aliases for the above (why remember cryptic names?)
66 EMERGENCY = 0
67 CRITICAL = 2
68 ERROR = 3
69 WARN = 4
70 INFORM = 6
71 INFORMATION = 6
72 INFORMATIONAL = 6
73
74 class capture_handler:
75    """
76    <Purpose>
77       An object that can be used to capture the output of arizonalib. See
78       start_capture and stop_capture.
79    """
80    
81    def __init__(self):
82       self.verb = 0
83       self.output = ""
84       pass
85
86    def flush(self, verb):
87       pass
88
89    def send_out(self, verb, msg):
90       self.send(verb, msg + "\n")
91
92    def send_out_comma(self, verb, msg):
93       self.send(verb, msg + " ")
94
95    def send_error(self, verb, msg):
96       self.send(verb, msg + "\n")
97
98    def send(self, verb, msg):
99       if verb >= self.verb:
100          self.output = self.output + msg
101
102 # a list of capture objects that can be used to capture the output
103 glo_captures = []
104
105 def start_capture():
106    """
107    <Purpose>
108       Adds a capture object to capture the output of arizonareport. Multiple
109       capture objects may be used by different callers at the same time. Capture
110       is stopped by passing the capture object to stop_capture()
111
112    <Returns>
113       Capture object of type capture_handler
114    """
115    capture = capture_handler()
116    glo_captures.append(capture)
117    return capture
118
119 def stop_capture(capture):
120    """
121    <Purpose>
122       Removes a capture object.
123
124    <Returns>
125       String containing output that was captured
126    """
127    glo_captures.remove(capture)
128    return capture.output
129
130
131
132 # This contains the list of output functions that will be called (see
133 # set_output_function() for more information).
134 output_function_list = []
135
136 def set_output_function(func):
137    """
138    <Purpose>
139       Sets a function that is called with all output.   The function must take
140       take three arguments:
141
142         call_type: This is either "syslog", "send_out", "send_out_comma", "send_error",
143                    or "send_error_comma"
144
145         call_verbosity: This is the verbosity the print statement was called
146                         with.
147
148         outputstring: This is the string which would have been output
149
150       Multiple functions can be set using this and they will be "chained"
151       in a first set, first called fashion.   The output from the first 
152       function will be used as the outputstring for the second, and so on...
153
154    <Arguments>   
155       func -- The above mentioned function
156    
157    <Exceptions>
158       None.
159    
160    <Side Effects>
161       None.
162
163    <Returns>
164       None.
165    """
166
167    global output_function_list
168
169    output_function_list.append(func)
170
171
172
173
174
175
176 def unset_output_function(func):
177    """
178    <Purpose>
179       Unsets an output function 
180
181    <Arguments>   
182       func -- The output function to be removed
183    
184    <Exceptions>
185       ValueError (when the function isn't in the list)
186    
187    <Side Effects>
188       None.
189
190    <Returns>
191       None.
192    """
193
194    global output_function_list
195
196    output_function_list.remove(func)
197
198
199
200
201 def __get_output_string(call_type,call_verbosity,outputstring):
202    """
203    <Purpose>
204       Constructs the output string by calling all of the output functions. 
205       The arguments are passed to the output_functions.
206
207    <Arguments>   
208       call_type: This is either "syslog", "send_out", "send_out_comma", or 
209                  "send_error"
210
211       call_verbosity: This is the verbosity the output statement was called
212                       with.
213
214       outputstring: This is the string which would have been output
215
216    <Exceptions>
217       Any exceptions caused by the output functions
218    
219    <Side Effects>
220       Any side effects caused by the output functions
221
222    <Returns>
223       The output string
224    """
225
226    for function in output_function_list:
227      outputstring = function(call_type,call_verbosity,outputstring)
228
229    return outputstring
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245 def get_verbosity():
246    """
247    <Purpose>
248       Returns the current verbosity level:
249         4 - ULTRAVERBOSE aka DEBUG
250         3 - VERYVERBOSE aka DEBUG
251         2 - VERBOSE
252         1 - QUIET
253         0 - VERYQUIET
254        -1 - ABSOLUTE SILENCE
255  
256    <Arguments>   
257       None. 
258    
259    <Exceptions>
260       None.
261    
262    <Side Effects>
263       None.
264
265    <Returns>
266       Returns the current verbosity level.  See "Purpose", above.
267    """
268    verbosity = arizonaconfig.get_option("verbose")
269    if verbosity == None:
270       arizonaconfig.init_options("arizonareport")
271       # stork default (--verbose)
272       return 2            
273    else: 
274       return verbosity  
275
276
277
278
279
280 def set_verbosity(verbosity):
281    """
282    <Purpose>
283       Sets the current verbosity level.
284
285    <Arguments>   
286       verbosity:
287               The new verbosity level:
288                 4 - ULTRAVERBOSE aka DEBUG
289                 3 - VERYVERBOSE aka DEBUG
290                 2 - VERBOSE
291                 1 - QUIET
292                 0 - VERYQUIET
293                -1 - ABSOLUTE SILENCE
294    
295    <Exceptions>
296       None.
297    
298    <Side Effects>
299       Changes the storkconfig "verbose" setting. 
300
301    <Returns>
302       None.
303    """
304    # check params
305    if not isinstance(verbosity, int): 
306       raise TypeError, "The parameter 'verbosity' of the function 'set_verbosity' must be an integer."
307    if verbosity < -1 or verbosity > 4:
308       raise TypeError, "The parameter 'verbosity' of the function 'set_verbosity' must be from -1 to 4, inclusive."
309
310    if arizonaconfig.get_option("verbose") == None:
311       arizonaconfig.init_options("arizonareport")
312       
313    arizonaconfig.set_option("verbose", verbosity)
314
315
316
317
318
319 def console_size():
320    """ 
321    <Purpose>
322       Finds the current console width and height using ioctl.
323
324    <Arguments>
325       None.
326
327    <Exceptions>
328       None.
329    
330    <Side Effects>
331       None.
332
333    <Returns>
334       (height, width), or (None, None) on error
335    """
336
337    # Justin: we do this here so that this is imported only in applications that
338    # need it
339    import fcntl
340    import termios
341
342    try:
343       size = struct.unpack("hhhh", fcntl.ioctl(0, termios.TIOCGWINSZ, "\000" * 8))[0:2]
344    except IOError:
345       size = (None, None)
346    return size
347
348
349
350
351
352 original_stdout = []
353 def redirect_stdout(stream):
354    """
355    <Purpose>
356       Redirects the standard output stream (stdout) to a new file stream.
357       If this is the first time that output has been redirected, the
358       original stdout stream will be saved for use with the restore_stdout
359       function.
360
361    <Arguments>
362       stream:
363               The new file stream for stdout.
364
365    <Exceptions>
366       TypeError on bad parameters.
367
368    <Side Effects>
369       Changes sys.stdout.
370
371    <Returns>
372       None.
373    """
374    global original_stdout
375    
376    # save the original stdout 
377    original_stdout.insert(0, sys.stdout)
378    
379    # redirect stdout to new stream  
380    sys.stdout = stream   
381
382
383
384
385
386 def restore_stdout():
387    """
388    <Purpose>
389       Restores the standard output stream (stdout) to the original file 
390       stream.  This function only has an effect if stdout was previously 
391       redirected with redirect_stdout.
392  
393    <Arguments>   
394       None.
395    
396    <Exceptions>
397       None.
398    
399    <Side Effects>
400       Changes sys.stdout.
401
402    <Returns>
403       None.
404    """
405    global original_stdout
406    
407    if len(original_stdout) > 0:
408       sys.stdout = original_stdout[0]
409       del original_stdout[0]
410    else:
411       raise ValueError   
412       
413
414
415
416
417 original_stderr = []
418 def redirect_stderr(stream):
419    """
420    <Purpose>
421       Redirects the standard error stream (stderr) to a new file stream. 
422       If this is the first time that output has been redirected, the 
423       original stderr stream will be saved for use with the restore_stderr
424       function.
425  
426    <Arguments>   
427       stream:
428               The new file stream for stderr.
429    
430    <Exceptions>
431       TypeError on bad parameters.
432    
433    <Side Effects>
434       Changes sys.stderr.
435
436    <Returns>
437       None.
438    """
439    global original_stderr
440
441    # save the original stderr
442    original_stderr.append(sys.stderr)
443    
444    # redirect stderr to new stream  
445    sys.stderr = stream
446
447
448
449
450 def restore_stderr():
451    """
452    <Purpose>
453       Restores the standard error stream (stderr) to the original file 
454       stream.  This function only has an effect if stderr was previously 
455       redirected with redirect_stderr.
456  
457    <Arguments>   
458       None.
459    
460    <Exceptions>
461       None.
462    
463    <Side Effects>
464       Changes sys.stderr.
465
466    <Returns>
467       None.
468    """
469    global original_stderr
470    
471    if len(original_stderr) > 0:
472       sys.stderr = original_stderr[0]
473       del original_stderr[0]
474    else:
475       raise ValueError   
476
477
478
479    
480   
481 def __verbosity_is_sufficient(required_verbosity):
482    """
483    <Purpose>
484       Checks given verbosity against current verbosity level.  If the 
485       current verbosity level is greater than or equal to the required
486       verbosity level, returns True, otherwise returns False.
487  
488    <Arguments>   
489       required_verbosity:
490               The verbosity level required:
491                 4 - ULTRAVERBOSE aka DEBUG
492                 3 - VERYVERBOSE aka DEBUG
493                 2 - VERBOSE
494                 1 - QUIET
495                 0 - VERYQUIET
496    
497    <Exceptions>
498       TypeError on bad parameters.
499    
500    <Side Effects>
501       None.
502
503    <Returns>
504       True if current verbosity level is sufficient for operation, 
505       False otherwise.
506    """
507    # check params
508    if not isinstance(required_verbosity, int): 
509       raise TypeError, "The parameter 'required_verbosity' of the function '__verbosity_is_sufficient' must be an integer."
510    if required_verbosity < 0 or required_verbosity > 4:
511       raise TypeError, "The parameter 'required_verbosity' of the function '__verbosity_is_sufficient' must be from 0 to 4, inclusive."
512    
513    # if they requested silence, be sure that request is honored
514    if get_verbosity() < 0:
515       return False   
516    
517    return get_verbosity() >= required_verbosity
518       
519    
520
521
522
523 def flush_out(required_verbosity):
524    """
525    <Purpose>
526       Flushes stdout if the current verbosity level is greater than
527       or equal to required_verbosity.
528  
529    <Arguments>   
530       required_verbosity:
531               The verbosity level required:
532                 4 - ULTRAVERBOSE aka DEBUG
533                 3 - VERYVERBOSE aka DEBUG
534                 2 - VERBOSE
535                 1 - QUIET
536                 0 - VERYQUIET
537    
538    <Exceptions>
539       TypeError on bad parameters.
540    
541    <Side Effects>
542       None.
543
544    <Returns>
545       None.
546    """
547    if __verbosity_is_sufficient(required_verbosity):
548       sys.stdout.flush()
549
550    for capture in glo_captures:
551       capture.flush(required_verbosity)
552
553
554
555
556
557 def flush_error(required_verbosity):
558    """
559    <Purpose>
560       Flushes stderr if the current verbosity level is greater than
561       or equal to required_verbosity.
562
563    <Arguments>
564       required_verbosity:
565               The verbosity level required:
566                 4 - ULTRAVERBOSE aka DEBUG
567                 3 - VERYVERBOSE aka DEBUG
568                 2 - VERBOSE
569                 1 - QUIET
570                 0 - VERYQUIET
571
572    <Exceptions>
573       TypeError on bad parameters.
574
575    <Side Effects>
576       None.
577
578    <Returns>
579       None.
580    """
581    if __verbosity_is_sufficient(required_verbosity):
582       sys.stderr.flush()
583
584    for capture in glo_captures:
585       capture.flush(required_verbosity)
586
587
588
589 # The purpose of this variable is to be able to check the messages sent by other modules.
590 # This is used in tests.
591 message = None
592
593 def send_out(required_verbosity, mesg):
594    """
595    <Purpose>
596       Sends mesg to stdout if the current verbosity level is greater than
597       or equal to required_verbosity.
598
599    <Arguments>
600       required_verbosity:
601               The verbosity level required:
602                 4 - ULTRAVERBOSE aka DEBUG
603                 3 - VERYVERBOSE aka DEBUG
604                 2 - VERBOSE
605                 1 - QUIET
606                 0 - VERYQUIET
607       mesg:
608               The message to display via stdout.
609
610    <Exceptions>
611       TypeError on bad parameters.
612
613    <Side Effects>
614       None.
615
616    <Returns>
617       None.
618    """
619    mesg = __get_output_string("send_out",required_verbosity,mesg)
620    if __verbosity_is_sufficient(required_verbosity):
621       print >> sys.stdout, mesg
622       sys.stdout.flush()
623
624    for capture in glo_captures:
625       capture.send_out(required_verbosity, mesg)
626
627    global message
628    message = mesg
629
630
631
632
633
634 def send_out_comma(required_verbosity, mesg):
635    """
636    <Purpose>
637       Sends mesg to stdout if the current verbosity level is greater than
638       or equal to required_verbosity.  Does not output a newline after the
639       message, similar to Python's "print ,".
640
641    <Arguments>
642       required_verbosity:
643               The verbosity level required:
644                 4 - ULTRAVERBOSE aka DEBUG
645                 3 - VERYVERBOSE aka DEBUG
646                 2 - VERBOSE
647                 1 - QUIET
648                 0 - VERYQUIET
649       mesg:
650               The message to display via stdout.
651
652    <Exceptions>
653       TypeError on bad parameters.
654
655    <Side Effects>
656       None.
657
658    <Returns>
659       None.
660    """
661    mesg = __get_output_string("send_out_comma",required_verbosity,mesg)
662    if __verbosity_is_sufficient(required_verbosity):
663       sys.stdout.write(mesg)
664       sys.stdout.flush()
665
666    for capture in glo_captures:
667       capture.send_comma(required_verbosity, mesg)
668
669    global message
670    message = mesg
671
672
673
674
675
676 def send_error(required_verbosity, mesg, program=None):
677    """
678    <Purpose>
679       Sends mesg to stderr if the current verbosity level is greater than
680       or equal to required_verbosity.  Also sends the mesg to the syslog,
681       with type ERR.
682
683    <Arguments>
684       required_verbosity:
685               The verbosity level required:
686                 4 - ULTRAVERBOSE aka DEBUG
687                 3 - VERYVERBOSE aka DEBUG
688                 2 - VERBOSE
689                 1 - QUIET
690                 0 - VERYQUIET
691       mesg:
692               The message to display via stderr.
693       module:
694               (default: None)
695               An optional string giving the name of the program or module
696               where the error occurred.  This will be prepended to the
697               syslog message in the form "program mesg".
698
699    <Exceptions>
700       TypeError on bad parameters.
701
702    <Side Effects>
703       None.
704
705    <Returns>
706       None.
707    """
708    mesg = __get_output_string("send_error",required_verbosity,mesg)
709    # check params
710    if not isinstance(program, str) and program != None:
711       raise TypeError, "The parameter 'program' of the function 'send_error' must be either a string or None."
712
713    # prepend module name, function and line no from the function that called us
714    (module_name, lineno, function, junk_text) = traceback.extract_stack()[-2]
715
716    # prepend "[program] " to syslog output, and log more info for debugging
717    syslog_mesg = "An error occurred in module " + module_name + ", on line " + str(lineno) + " of function " + function + ": " + mesg
718    if program != None:
719       syslog_mesg = program + " " + syslog_mesg
720
721    # always syslog, regardless of verbosity
722    send_syslog(ERR, syslog_mesg)
723
724    # output mesg to stderr
725    if __verbosity_is_sufficient(required_verbosity):
726       print >> sys.stderr, mesg
727       sys.stderr.flush()
728
729    for capture in glo_captures:
730       capture.send_error(required_verbosity, mesg)
731
732    global message
733    message = mesg
734
735
736
737
738
739 def send_syslog(severity, mesg):
740    """
741    <Purpose>
742       Adds "debug" mesg to the syslog.  For debug-level messages.
743  
744    <Arguments>
745       severity:
746               The type of message to send:
747                 0 or arizonareport.EMERG 
748                   System is unusable. 
749                 1 or arizonareport.ALERT
750                   Action must be taken immediately. 
751                 2 or arizonareport.CRIT
752                   Critical conditions. 
753                 3 or arizonareport.ERR
754                   Error conditions. 
755                 4 or arizonareport.WARNING
756                   Warning conditions. 
757                 5 or arizonareport.NOTICE
758                   Normal, but significant, condition. 
759                 6 or arizonareport.INFO
760                   Informational message. 
761                 7 or arizonareport.DEBUG
762                   Debug-level message. 
763       mesg:
764               The message to add to the syslog.
765    
766    <Exceptions>
767       TypeError on bad params.
768    
769    <Side Effects>
770       None.
771
772    <Returns>
773       None.
774    """
775    mesg = __get_output_string("send_syslog",severity,mesg)
776    # check params
777    if not isinstance(severity, int): 
778       raise TypeError, "The parameter 'severity' of the function 'send_syslog' must be an integer."
779    if severity < 0 or severity > 7:
780       raise TypeError, "The parameter 'severity' of the function 'send_syslog' must be from 0 to 7, inclusive."
781
782    global message
783    message = mesg
784    
785    # prepend module name, function and line no from the function that called us
786    (module_name, lineno, function, junk_text) = traceback.extract_stack()[-2]
787    mesg = "In module: " + module_name + " line " + str(lineno) + " of function " + function + ": " + mesg
788    try:
789         syslog.syslog(severity, mesg)
790    except NameError:
791         pass
792