]> SALOME platform Git repositories - tools/sat.git/blob - src/logger.py
Salome HOME
Dump config in log file at end of each command
[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
38                                       in terminal.
39         '''
40         self.config = config
41         self.default_level = 3
42         self.silentSysStd = silent_sysstd
43         
44         # Construct xml log file location for sat prints.
45         logFileName = config.VARS.datehour + "_" + config.VARS.command + ".xml"
46         logFilePath = os.path.join(config.SITE.log.logDir, logFileName)
47         # Construct txt file location in order to log 
48         # the external commands calls (cmake, make, git clone, etc...)
49         txtFileName = config.VARS.datehour + "_" + config.VARS.command + ".txt"
50         txtFilePath = os.path.join(config.SITE.log.logDir, "OUT", txtFileName)
51         
52         src.ensure_path_exists(os.path.dirname(logFilePath))
53         src.ensure_path_exists(os.path.dirname(txtFilePath))
54         
55         self.logFileName = logFileName
56         self.logFilePath = logFilePath
57         self.txtFileName = txtFileName
58         self.txtFilePath = txtFilePath
59         # Initialize xml instance and put first fields 
60         # like beginTime, user, command, etc... 
61         self.xmlFile = xmlManager.XmlLogFile(logFilePath, "SATcommand", 
62                             attrib = {"application" : config.VARS.application})
63         self.put_initial_xml_fields()
64         # Initialize the txt file for reading
65         self.logTxtFile = open(str(self.txtFilePath), 'w')
66         
67     def put_initial_xml_fields(self):
68         '''Method called at class initialization : Put all fields 
69            corresponding to the command context (user, time, ...)
70         '''
71         # command name
72         self.xmlFile.add_simple_node("Site", attrib={"command" : 
73                                                      self.config.VARS.command})
74         # version of salomeTools
75         self.xmlFile.append_node_attrib("Site", attrib={"satversion" : 
76                                             self.config.INTERNAL.sat_version})
77         # machine name on which the command has been launched
78         self.xmlFile.append_node_attrib("Site", attrib={"hostname" : 
79                                                     self.config.VARS.hostname})
80         # Distribution of the machine
81         self.xmlFile.append_node_attrib("Site", attrib={"OS" : 
82                                                         self.config.VARS.dist})
83         # The user that have launched the command
84         self.xmlFile.append_node_attrib("Site", attrib={"user" : 
85                                                         self.config.VARS.user})
86         # The time when command was launched
87         Y, m, dd, H, M, S = date_to_datetime(self.config.VARS.datehour)
88         date_hour = "%2s/%2s/%4s %2sh%2sm%2ss" % (dd, m, Y, H, M, S)
89         self.xmlFile.append_node_attrib("Site", attrib={"beginTime" : 
90                                                         date_hour})
91         # The application if any
92         if "APPLICATION" in self.config:
93             self.xmlFile.append_node_attrib("Site", 
94                         attrib={"application" : self.config.VARS.application})
95         # The initialization of the trace node
96         self.xmlFile.add_simple_node("Log",text="")
97         self.xmlFile.add_simple_node("OutLog",
98                                     text=os.path.join("OUT", self.txtFileName))
99
100     def write(self, message, level=None, screenOnly=False):
101         '''the function used in the commands 
102         that will print in the terminal and the log file.
103         
104         :param message str: The message to print.
105         :param level int: The output level corresponding 
106                           to the message 0 < level < 6.
107         :param screenOnly boolean: if True, do not write in log file.
108         '''
109         # do not write message starting with \r to log file
110         if not message.startswith("\r") and not screenOnly:
111             self.xmlFile.append_node_text("Log", 
112                                           printcolors.cleancolor(message))
113
114         # get user or option output level
115         current_output_level = self.config.USER.output_level
116         if not ('isatty' in dir(sys.stdout) and sys.stdout.isatty()):
117             # clean the message color if the terminal is redirected by user
118             # ex: sat compile appli > log.txt
119             message = printcolors.cleancolor(message)
120         
121         # Print message regarding the output level value
122         if level:
123             if level <= current_output_level and not self.silentSysStd:
124                 sys.stdout.write(message)
125         else:
126             if self.default_level <= current_output_level and not self.silentSysStd:
127                 sys.stdout.write(message)
128
129     def error(self, message):
130         '''Print an error.
131         
132         :param message str: The message to print.
133         '''
134         # Print in the log file
135         self.xmlFile.append_node_text("traces", _('ERROR:') + message)
136
137         # Print in the terminal and clean colors if the terminal 
138         # is redirected by user
139         if not ('isatty' in dir(sys.stderr) and sys.stderr.isatty()):
140             sys.stderr.write(printcolors.printcError(_('ERROR:') + message))
141         else:
142             sys.stderr.write(_('ERROR:') + message)
143
144     def flush(self):
145         '''Flush terminal
146         '''
147         sys.stdout.flush()
148         
149     def end_write(self, attribute):
150         '''Method called just after command end : Put all fields 
151            corresponding to the command end context (time).
152            Write the log xml file on the hard drive.
153            And display the command to launch to get the log
154         
155         :param attribute dict: the attribute to add to the node "Site".
156         '''       
157         # Get current time (end of command) and format it
158         dt = datetime.datetime.now()
159         Y, m, dd, H, M, S = date_to_datetime(self.config.VARS.datehour)
160         t0 = datetime.datetime(int(Y), int(m), int(dd), int(H), int(M), int(S))
161         tf = dt
162         delta = tf - t0
163         total_time = timedelta_total_seconds(delta)
164         hours = int(total_time / 3600)
165         minutes = int((total_time - hours*3600) / 60)
166         seconds = total_time - hours*3600 - minutes*60
167         # Add the fields corresponding to the end time
168         # and the total time of command
169         endtime = dt.strftime('%d/%Y/%m %Hh%Mm%Ss')
170         self.xmlFile.append_node_attrib("Site", attrib={"endTime" : endtime})
171         self.xmlFile.append_node_attrib("Site", 
172                 attrib={"TotalTime" : "%ih%im%is" % (hours, minutes, seconds)})
173         
174         # Add the attribute passed to the method
175         self.xmlFile.append_node_attrib("Site", attrib=attribute)
176         
177         # Call the method to write the xml file on the hard drive
178         self.xmlFile.write_tree(stylesheet = "command.xsl")
179         
180         # Dump the config in a pyconf file in the log directory
181         logDir = self.config.SITE.log.logDir
182         dumpedPyconfFileName = (self.config.VARS.datehour 
183                                 + "_" 
184                                 + self.config.VARS.command 
185                                 + ".pyconf")
186         dumpedPyconfFilePath = os.path.join(logDir, 'OUT', dumpedPyconfFileName)
187         f = open(dumpedPyconfFilePath, 'w')
188         self.config.__save__(f)
189         f.close()
190         
191
192 def date_to_datetime(date):
193     '''Little method that gets year, mon, day, hour , 
194        minutes and seconds from a str in format YYYYMMDD_HHMMSS
195     
196     :param date str: The date in format YYYYMMDD_HHMMSS
197     :return: the same date and time in separate variables.
198     :rtype: (str,str,str,str,str,str)
199     '''
200     Y = date[:4]
201     m = date[4:6]
202     dd = date[6:8]
203     H = date[9:11]
204     M = date[11:13]
205     S = date[13:15]
206     return Y, m, dd, H, M, S
207
208 def timedelta_total_seconds(timedelta):
209     '''Little method to replace total_seconds from 
210        datetime module in order to be compatible with old python versions
211     
212     :param timedelta datetime.timedelta: The delta between two dates
213     :return: The number of seconds corresponding to timedelta.
214     :rtype: float
215     '''
216     return (
217         timedelta.microseconds + 0.0 +
218         (timedelta.seconds + timedelta.days * 24 * 3600) * 10 ** 6) / 10 ** 6
219         
220 def show_command_log(logFilePath, cmd, application, notShownCommands):
221     '''Used in updateHatXml. Determine if the log xml file logFilePath 
222        has to be shown or not in the hat log.
223     
224     :param logFilePath str: the path to the command xml log file
225     :param cmd str: the command of the log file
226     :param application str: the application passed as parameter 
227                             to the salomeTools command
228     :param notShownCommands list: the list of commands 
229                                   that are not shown by default
230     
231     :return: True if cmd is not in notShownCommands and the application 
232              in the log file corresponds to application
233     :rtype: boolean
234     '''
235     # When the command is not in notShownCommands, no need to go further :
236     # Do not show
237     if cmd in notShownCommands:
238         return False, None
239  
240     # Get the application of the log file
241     logFileXml = src.xmlManager.ReadXmlFile(logFilePath)
242     if 'application' in logFileXml.xmlroot.keys():
243         appliLog = logFileXml.xmlroot.get('application')
244         # if it corresponds, then the log has to be shown
245         if appliLog == application:
246             return True, appliLog
247         elif application != 'None':
248             return False, appliLog
249         
250         return True, appliLog
251     
252     if application == 'None':
253             return True, None    
254         
255     return False, None
256
257 def list_log_file(dirPath, expression):
258     '''Find all files corresponding to expression in dirPath
259     
260     :param dirPath str: the directory where to search the files
261     :param expression str: the regular expression of files to find
262     :return: the list of files path and informations about it
263     :rtype: list
264     '''
265     lRes = []
266     for fileName in os.listdir(dirPath):
267         # YYYYMMDD_HHMMSS_namecmd.xml
268         sExpr = expression
269         oExpr = re.compile(sExpr)
270         if oExpr.search(fileName):
271             # get date and hour and format it
272             date_hour_cmd = fileName.split('_')
273             date_not_formated = date_hour_cmd[0]
274             date = "%s/%s/%s" % (date_not_formated[6:8], 
275                                  date_not_formated[4:6], date_not_formated[0:4] )
276             hour_not_formated = date_hour_cmd[1]
277             hour = "%s:%s:%s" % (hour_not_formated[0:2], 
278                                  hour_not_formated[2:4], hour_not_formated[4:6])
279             cmd = date_hour_cmd[2][:-len('.xml')]
280             lRes.append((os.path.join(dirPath, fileName), 
281                          date_not_formated, date, hour_not_formated, hour, cmd))
282     return lRes
283
284 def update_hat_xml(logDir, application=None, notShownCommands = []):
285     '''Create the xml file in logDir that contain all the xml file 
286        and have a name like YYYYMMDD_HHMMSS_namecmd.xml
287     
288     :param logDir str: the directory to parse
289     :param application str: the name of the application if there is any
290     '''
291     # Create an instance of XmlLogFile class to create hat.xml file
292     xmlHatFilePath = os.path.join(logDir, 'hat.xml')
293     xmlHat = src.xmlManager.XmlLogFile(xmlHatFilePath,
294                                     "LOGlist", {"application" : application})
295     # parse the log directory to find all the command logs, 
296     # then add it to the xml file
297     lLogFile = list_log_file(logDir, logCommandFileExpression)
298     for filePath, _, date, _, hour, cmd in lLogFile:
299         showLog, cmdAppli = show_command_log(filePath, cmd,
300                                               application, notShownCommands)
301         #if cmd not in notShownCommands:
302         if showLog:
303             # add a node to the hat.xml file
304             xmlHat.add_simple_node("LogCommand", 
305                                    text=os.path.basename(filePath), 
306                                    attrib = {"date" : date, 
307                                              "hour" : hour, 
308                                              "cmd" : cmd, 
309                                              "application" : cmdAppli})
310     
311     # Write the file on the hard drive
312     xmlHat.write_tree('hat.xsl')