Source code for display

#!/usr/bin/env python
# coding: utf8

from __future__ import (unicode_literals, absolute_import, division, print_function)

from threading import Thread, Lock
from string import center

import datetime
import io
import os
import time
import sys


[docs]class LogLevel: """ Stores the possible values of the log printing parameters ``log_level``. This is an ersatz to an Enumerate object (not available in python2). ``level_name`` is used to get the ``log_level`` name from its value. """ DEBUG = 10 INFO = 20 WARNING = 30 ERROR = 40 CRITICAL = 50 level_name = {10: 'DEBUG', 20: 'INFO', 30: 'WARNING', 40: 'ERROR', 50: 'CRITICAL'}
[docs]class Displayer(Thread): """ This thread object is responsible for the logging and the printing of the stats, the alerts and the log messages. The log messages are messages generated by the different threads to inform the user of a peculiar event. This thread can keep up to two output log files : one for the traffic alerts, and one for the log messages. The alert log is always used, the program log is optional, by default activated. The log messages can also be printed on the console. Note ---- Once it has been initiated (for instance with ``picasso = Displayer(debug=True)``, its :meth:`log` method can be simply called from any module with the following code:: import display as d d.log(self, d.LogLevel.INFO, "This is my log message !") This is possible because in :meth:`.__init__`, it defines a ``global displayer`` variable. Attributes ---------- statistician : Statistician A reference to the statistician that will be contacted to print the stats. display_period: int The stats will be printed with a period = display_period log_level: LogLevel The limit under which received log messages will simply be ignored in the log method console_print_program_log: bool If true, the receive log messages will be printed in the console, if their log_level is above or equal to the log_level write_program_log_file: bool If true, the receive log messages will be written in the program log file, if their log_level is above or equal to the log_level should_run: bool If False, the thread will shortly end stop its operation. Used to cleanly end the program. registered_object: list of Thread The list of other threads, used of INFO log messages console_lock: Lock Lock used to make the print function thread-safe display_width: int The typical width of the terminal name: string """ def __init__(self, statistician=None, display_period=10, console_print_program_log=True, write_program_log_file=True, log_level=LogLevel.WARNING, debug=False): Thread.__init__(self) # registers the displayer reference at the module level, so that other module can access it. global displayer displayer = self self.statistician = statistician self.display_period = display_period self.log_level = log_level self.console_print_program_log = console_print_program_log self.write_program_log_file = write_program_log_file # this is for the log messages self.program_log_path = '../log/log' self.program_log_opened = False # this is for the alerts self.alert_log_path = '../log/alert_log' self.alert_log_opened = False self.should_run = True self.registered_object = [] self.console_lock = Lock() self.display_width = 80 self.name = 'displayer thread' if debug: # debug is a short way to define a simple Displayer object, printing in the console self.log_level = LogLevel.DEBUG self.console_print_program_log = True self.write_program_log_file = False # if the log message are written in a file if self.write_program_log_file: try: os.remove(self.program_log_path) except OSError: pass try: self.program_log = io.open(self.program_log_path, 'at') self.program_log_opened = True except (IOError, ValueError): temp_c_print = self.console_print_program_log self.console_print_program_log = True self.log(self, LogLevel.WARNING, "Output log file couldn't have been opened") self.console_print_program_log = temp_c_print # we reinitialised and open the alert log file try: os.remove(self.alert_log_path) except OSError: pass try: self.alert_log = io.open(self.alert_log_path, 'at') self.alert_log_opened = True except (IOError, ValueError): temp_c_print = self.console_print_program_log self.console_print_program_log = True self.log(self, LogLevel.WARNING, "Alert log file couldn't have been opened") self.console_print_program_log = temp_c_print # We log an first WARNING if no statistician reference was given if self.statistician is None: self.log(self, LogLevel.WARNING, 'No statistician given')
[docs] def run(self): """ Regularly prints the stats, and ends when should_run = False (normally after 0.1s max) Called when the thread is started. """ # Welcome message program_stat = ' - '.join( ['{} is {}started'.format(thread.__class__.__name__, (not thread.is_alive()) * '*not* ') for thread in self.registered_object]) welcome_msg = '=' * self.display_width + '\n' + \ center('Welcome! HTTP-log Displayer is now running', self.display_width) + '\n' + \ center(program_stat, self.display_width) + '\n' + '=' * self.display_width self.lock_print(welcome_msg) # Never ending loop last_display_time = time.time() # we don't want to print something just after the beginning while self.should_run: delta = time.time() - last_display_time # print(delta) if delta >= self.display_period: if self.registered_object: self.log(self, LogLevel.INFO, self.stat_of_registered_object()) self.lock_print(self.stat_string()) last_display_time = time.time() else: time.sleep(min(self.display_period - delta, 0.1)) try: self.alert_log.close() except: pass try: self.program_log.close() except: pass
[docs] def stat_string(self): """This is called when stats need to be printed Returns ------- string The formatted message """ stats = self.statistician.stat.get_last_stats() self.statistician.stat.reset_short_stat() # stats['separation'] = '='*self.display_width stats['time'] = datetime.datetime.now() stats['total_MB_bytes'] = stats['total_bytes'] / (1024 ** 2) stats['display_period'] = self.display_period if stats['total_hits']: res = '{time:%X} - Most visited section in the past {display_period}s is ' \ '\'{max_section}\' with {max_hit} hits.' \ '\n\tTotal hits: {total_hits},' \ '\n\tTotal sent bytes: {total_bytes}, i.e. {total_MB_bytes:.03f} MB.' \ ''.format(**stats) else: res = '{time:%X} - No request in the last {display_period}s!'.format(**stats) return res
[docs] def log(self, sender, level, message): """ Receives messages from all the threads and "thread-safely" prints them depending of their level. Writes them on the log file if needed. Args ---- sender: Object A reference to the sender level: LogLevel The log level of the message message: string The message """ if level >= self.log_level: log_line = " : ".join((datetime.datetime.now().isoformat(), LogLevel.level_name[level], sender.__class__.__name__, message)) if self.console_print_program_log: self.lock_print(log_line) # print('statistician', self.statistician, self.console_print_program_log, self.log_level) if self.write_program_log_file and self.program_log_opened: self.program_log.write(log_line + '\n') self.program_log.flush()
[docs] def lock_print(self, *args, **kwargs): """Thread-safely print function""" with self.console_lock: print(*args, **kwargs)
[docs] def stat_of_registered_object(self): """Returns a string describing the registered threads.""" return '; '.join([': '.join((obj.__class__.__name__, obj.state())) for obj in self.registered_object])
[docs] def print_new_alert(self, alert_param, long_average, short_average): """Called when a alert is raised, prints and/or logs it Args ---- alert_param: AlertParam long_average: float short_average: float """ high_outflow = short_average / (1024 ** 2) / (alert_param.time_resolution * alert_param.short_median) msg = 'High traffic detected: {:%x %X}: outflow {:.03}MB/s'.format(datetime.datetime.now(), high_outflow) res = center('/' * 17 + ' ALERT ' + '\\' * 17, self.display_width) + '\n' + center(msg, self.display_width) self.lock_print(res) if self.alert_log_opened: self.alert_log.write(msg + '\n') self.alert_log.flush()
[docs] def print_end_alert(self, alert_param, long_average, short_average): """Called when a alert is shut down, prints and/or logs it Note ---- Could be merged with ``print_new_alert`` """ low_outflow = long_average / (1024 ** 2) / (alert_param.time_resolution * alert_param.long_median) msg = 'End of alert: {:%x %X}: outflow {:.03}MB/s'.format(datetime.datetime.now(), low_outflow) res = center('\\' * 15 + ' ALERT OVER ' + '/' * 15, self.display_width) + '\n' + \ center(msg, self.display_width) self.lock_print(res) if self.alert_log_opened: self.alert_log.write(msg + '\n') self.alert_log.flush()
if __name__ == '__main__': # Starts a Displayer and sends two messages (only one is printed, because of the log level) from log_writer import random_HTTP_request, uniform_random_local_URL_maker from reader import get_section from random import randint th = Displayer(display_period=1, debug=True) random_URL = uniform_random_local_URL_maker() th.stat_string = lambda: 'Most visited section: {0[0]} with {0[1]} hits.' \ ''.format((get_section(random_HTTP_request(random_URL())), randint(0, 1e4))) th.start() th.log(th, LogLevel.DEBUG, 'Msg debug 1') th.log_level = LogLevel.DEBUG th.log(th, LogLevel.DEBUG, 'Msg debug 2') time.sleep(3) th.should_run = False