Salome HOME
rename all the function to be pep0008 compliant
[tools/sat.git] / src / logger.py
1 #!/usr/bin/env python
2 #-*- coding:utf-8 -*-
3 #  Copyright (C) 2010-2012  CEA/DEN
4 #
5 #  This library is free software; you can redistribute it and/or
6 #  modify it under the terms of the GNU Lesser General Public
7 #  License as published by the Free Software Foundation; either
8 #  version 2.1 of the License.
9 #
10 #  This library 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 GNU
13 #  Lesser General Public License for more details.
14 #
15 #  You should have received a copy of the GNU Lesser General Public
16 #  License along with this library; if not, write to the Free Software
17 #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
18
19 import sys
20 import os
21 import datetime
22 import re
23
24 import src
25 from . import printcolors
26 from . import xmlManager
27
28 logCommandFileExpression = "^[0-9]{8}_+[0-9]{6}_+.*\.xml$"
29
30 class Logger(object):
31     '''Class to handle log mechanism.
32     '''
33     def __init__(self, config, silent_sysstd=False):
34         '''Initialization
35         
36         :param config pyconf.Config: The global configuration.
37         :param silent_sysstd boolean: if True, do not write anything in terminal.
38         '''
39         self.config = config
40         self.default_level = 3
41         self.silentSysStd = silent_sysstd
42         
43         # Construct xml log file location for sat prints.
44         logFileName = config.VARS.datehour + "_" + config.VARS.command + ".xml"
45         logFilePath = os.path.join(config.SITE.log.logDir, logFileName)
46         # Construct txt file location in order to log the external commands calls (cmake, make, git clone, etc...)
47         txtFileName = config.VARS.datehour + "_" + config.VARS.command + ".txt"
48         txtFilePath = os.path.join(config.SITE.log.logDir, "OUT", txtFileName)
49         
50         src.ensure_path_exists(os.path.dirname(logFilePath))
51         src.ensure_path_exists(os.path.dirname(txtFilePath))
52         
53         self.logFileName = logFileName
54         self.logFilePath = logFilePath
55         self.txtFileName = txtFileName
56         self.txtFilePath = txtFilePath
57         # Initialize xml instance and put first fields like beginTime, user, command, etc... 
58         self.xmlFile = xmlManager.XmlLogFile(logFilePath, "SATcommand", attrib = {"application" : config.VARS.application})
59         self.put_initial_xml_fields()
60         # Initialize the txt file for reading
61         self.logTxtFile = open(str(self.txtFilePath), 'w')
62         
63     def put_initial_xml_fields(self):
64         '''Method called at class initialization : Put all fields corresponding to the command context (user, time, ...)
65         '''
66         # command name
67         self.xmlFile.add_simple_node("Site", attrib={"command" : self.config.VARS.command})
68         # version of salomeTools
69         self.xmlFile.append_node_attrib("Site", attrib={"satversion" : self.config.INTERNAL.sat_version})
70         # machine name on which the command has been launched
71         self.xmlFile.append_node_attrib("Site", attrib={"hostname" : self.config.VARS.hostname})
72         # Distribution of the machine
73         self.xmlFile.append_node_attrib("Site", attrib={"OS" : self.config.VARS.dist})
74         # The user that have launched the command
75         self.xmlFile.append_node_attrib("Site", attrib={"user" : self.config.VARS.user})
76         # The time when command was launched
77         Y, m, dd, H, M, S = date_to_datetime(self.config.VARS.datehour)
78         date_hour = "%2s/%2s/%4s %2sh%2sm%2ss" % (dd, m, Y, H, M, S)
79         self.xmlFile.append_node_attrib("Site", attrib={"beginTime" : date_hour})
80         # The application if any
81         if "APPLICATION" in self.config:
82             self.xmlFile.append_node_attrib("Site", attrib={"application" : self.config.VARS.application})
83         # The initialization of the trace node
84         self.xmlFile.add_simple_node("Log",text="")
85         self.xmlFile.add_simple_node("OutLog",text=os.path.join("OUT", self.txtFileName))
86
87     def write(self, message, level=None, screenOnly=False):
88         '''the function used in the commands that will print in the terminal and the log file.
89         
90         :param message str: The message to print.
91         :param level int: The output level corresponding to the message 0 < level < 6.
92         :param screenOnly boolean: if True, do not write in log file.
93         '''
94         # do not write message starting with \r to log file
95         if not message.startswith("\r") and not screenOnly:
96             self.xmlFile.append_node_text("Log", printcolors.cleancolor(message))
97
98         # get user or option output level
99         current_output_level = self.config.USER.output_level
100         if not ('isatty' in dir(sys.stdout) and sys.stdout.isatty()):
101             # clean the message color if the terminal is redirected by user
102             # ex: sat compile appli > log.txt
103             message = printcolors.cleancolor(message)
104         
105         # Print message regarding the output level value
106         if level:
107             if level <= current_output_level and not self.silentSysStd:
108                 sys.stdout.write(message)
109         else:
110             if self.default_level <= current_output_level and not self.silentSysStd:
111                 sys.stdout.write(message)
112
113     def error(self, message):
114         '''Print an error.
115         
116         :param message str: The message to print.
117         '''
118         # Print in the log file
119         self.xmlFile.append_node_text("traces", _('ERROR:') + message)
120
121         # Print in the terminal and clean colors if the terminal is redirected by user
122         if not ('isatty' in dir(sys.stderr) and sys.stderr.isatty()):
123             sys.stderr.write(printcolors.printcError(_('ERROR:') + message))
124         else:
125             sys.stderr.write(_('ERROR:') + message)
126
127     def flush(self):
128         '''Flush terminal
129         '''
130         sys.stdout.flush()
131         
132     def end_write(self, attribute):
133         '''Method called just after command end : Put all fields corresponding to the command end context (time).
134         Write the log xml file on the hard drive.
135         And display the command to launch to get the log
136         
137         :param attribute dict: the attribute to add to the node "Site".
138         '''       
139         # Get current time (end of command) and format it
140         dt = datetime.datetime.now()
141         Y, m, dd, H, M, S = date_to_datetime(self.config.VARS.datehour)
142         t0 = datetime.datetime(int(Y), int(m), int(dd), int(H), int(M), int(S))
143         tf = dt
144         delta = tf - t0
145         total_time = timedelta_total_seconds(delta)
146         hours = int(total_time / 3600)
147         minutes = int((total_time - hours*3600) / 60)
148         seconds = total_time - hours*3600 - minutes*60
149         # Add the fields corresponding to the end time and the total time of command
150         endtime = dt.strftime('%d/%Y/%m %Hh%Mm%Ss')
151         self.xmlFile.append_node_attrib("Site", attrib={"endTime" : endtime})
152         self.xmlFile.append_node_attrib("Site", attrib={"TotalTime" : "%ih%im%is" % (hours, minutes, seconds)})
153         
154         # Add the attribute passed to the method
155         self.xmlFile.append_node_attrib("Site", attrib=attribute)
156         
157         # Call the method to write the xml file on the hard drive
158         self.xmlFile.write_tree(stylesheet = "command.xsl")
159         
160
161 def date_to_datetime(date):
162     '''Little method that gets year, mon, day, hour , minutes and seconds from a str in format YYYYMMDD_HHMMSS
163     
164     :param date str: The date in format YYYYMMDD_HHMMSS
165     :return: the same date and time in separate variables.
166     :rtype: (str,str,str,str,str,str)
167     '''
168     Y = date[:4]
169     m = date[4:6]
170     dd = date[6:8]
171     H = date[9:11]
172     M = date[11:13]
173     S = date[13:15]
174     return Y, m, dd, H, M, S
175
176 def timedelta_total_seconds(timedelta):
177     '''Little method to replace total_seconds from datetime module in order to be compatible with old python versions
178     
179     :param timedelta datetime.timedelta: The delta between two dates
180     :return: The number of seconds corresponding to timedelta.
181     :rtype: float
182     '''
183     return (
184         timedelta.microseconds + 0.0 +
185         (timedelta.seconds + timedelta.days * 24 * 3600) * 10 ** 6) / 10 ** 6
186         
187 def show_command_log(logFilePath, cmd, application, notShownCommands):
188     '''Used in updateHatXml. Determine if the log xml file logFilePath has to be shown or not in the hat log.
189     
190     :param logFilePath str: the path to the command xml log file
191     :param cmd str: the command of the log file
192     :param application str: the application passed as parameter to the salomeTools command
193     :param notShownCommands list: the list of commands that are not shown by default
194     
195     :return: True if cmd is not in notShownCommands and the application in the log file corresponds to application
196     :rtype: boolean
197     '''
198     # When the command is not in notShownCommands, no need to go further. Do not show
199     if cmd in notShownCommands:
200         return False, None
201  
202     # Get the application of the log file
203     logFileXml = src.xmlManager.readXmlFile(logFilePath)
204     if 'application' in logFileXml.xmlroot.keys():
205         appliLog = logFileXml.xmlroot.get('application')
206         # if it corresponds, then the log has to be shown
207         if appliLog == application:
208             return True, appliLog
209         elif application != 'None':
210             return False, appliLog
211         
212         return True, appliLog
213     
214     if application == 'None':
215             return True, None    
216         
217     return False, None
218
219 def list_log_file(dirPath, expression):
220     '''Find all files corresponding to expression in dirPath
221     
222     :param dirPath str: the directory where to search the files
223     :param expression str: the regular expression of files to find
224     :return: the list of files path and informations about it
225     :rtype: list
226     '''
227     lRes = []
228     for fileName in os.listdir(dirPath):
229         # YYYYMMDD_HHMMSS_namecmd.xml
230         sExpr = expression
231         oExpr = re.compile(sExpr)
232         if oExpr.search(fileName):
233             # get date and hour and format it
234             date_hour_cmd = fileName.split('_')
235             date_not_formated = date_hour_cmd[0]
236             date = "%s/%s/%s" % (date_not_formated[6:8], date_not_formated[4:6], date_not_formated[0:4] )
237             hour_not_formated = date_hour_cmd[1]
238             hour = "%s:%s:%s" % (hour_not_formated[0:2], hour_not_formated[2:4], hour_not_formated[4:6])
239             cmd = date_hour_cmd[2][:-len('.xml')]
240             lRes.append((os.path.join(dirPath, fileName), date_not_formated, date, hour_not_formated, hour, cmd))
241     return lRes
242
243 def update_hat_xml(logDir, application=None, notShownCommands = []):
244     '''Create the xml file in logDir that contain all the xml file and have a name like YYYYMMDD_HHMMSS_namecmd.xml
245     
246     :param logDir str: the directory to parse
247     :param application str: the name of the application if there is any
248     '''
249     # Create an instance of XmlLogFile class to create hat.xml file
250     xmlHatFilePath = os.path.join(logDir, 'hat.xml')
251     xmlHat = src.xmlManager.XmlLogFile(xmlHatFilePath,  "LOGlist", {"application" : application})
252     # parse the log directory to find all the command logs, then add it to the xml file
253     lLogFile = list_log_file(logDir, logCommandFileExpression)
254     for filePath, _, date, _, hour, cmd in lLogFile:
255         showLog, cmdAppli = show_command_log(filePath, cmd, application, notShownCommands)
256         #if cmd not in notShownCommands:
257         if showLog:
258             # add a node to the hat.xml file
259             xmlHat.add_simple_node("LogCommand", text=os.path.basename(filePath), attrib = {"date" : date, "hour" : hour, "cmd" : cmd, "application" : cmdAppli})
260     
261     # Write the file on the hard drive
262     xmlHat.write_tree('hat.xsl')