Salome HOME
complexify the name of the log files in order to make them unique
[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 '''In this file are implemented the classes and method relative to the logging
19 '''
20
21 import sys
22 import os
23 import datetime
24 import re
25
26 import src
27 from . import printcolors
28 from . import xmlManager
29
30 logCommandFileExpression = "^[0-9]{8}_+[0-9]{6}_+.*\.xml$"
31
32 class Logger(object):
33     '''Class to handle log mechanism.
34     '''
35     def __init__(self,
36                  config,
37                  silent_sysstd=False,
38                  all_in_terminal=False,
39                  micro_command = False):
40         '''Initialization
41         
42         :param config pyconf.Config: The global configuration.
43         :param silent_sysstd boolean: if True, do not write anything
44                                       in terminal.
45         '''
46         self.config = config
47         self.default_level = 3
48         self.silentSysStd = silent_sysstd
49         
50         # Construct xml log file location for sat prints.
51         prefix = ""
52         if micro_command:
53             prefix = "micro_"
54         hour_command_host = (config.VARS.datehour + "_" + 
55                              config.VARS.command + "_" + 
56                              config.VARS.hostname)
57         logFileName = prefix + hour_command_host + ".xml"
58         logFilePath = os.path.join(config.USER.log_dir, logFileName)
59         # Construct txt file location in order to log 
60         # the external commands calls (cmake, make, git clone, etc...)
61         txtFileName = prefix + hour_command_host + ".txt"
62         txtFilePath = os.path.join(config.USER.log_dir, "OUT", txtFileName)
63         
64         src.ensure_path_exists(os.path.dirname(logFilePath))
65         src.ensure_path_exists(os.path.dirname(txtFilePath))
66         
67         # The path of the log files (one for sat traces, and the other for 
68         # the system commands traces)
69         self.logFileName = logFileName
70         self.logFilePath = logFilePath
71         self.txtFileName = txtFileName
72         self.txtFilePath = txtFilePath
73         
74         # The list of all log files corresponding to the current command and
75         # the commands called by the current command
76         self.l_logFiles = [logFilePath, txtFilePath]
77         
78         # Initialize xml instance and put first fields 
79         # like beginTime, user, command, etc... 
80         self.xmlFile = xmlManager.XmlLogFile(logFilePath, "SATcommand", 
81                             attrib = {"application" : config.VARS.application})
82         self.put_initial_xml_fields()
83         # Initialize the txt file for reading
84         self.logTxtFile = open(str(self.txtFilePath), 'w')
85         # If the option all_in_terminal was called, all the system commands
86         # are redirected to the terminal
87         if all_in_terminal:
88             self.logTxtFile = sys.__stdout__
89         
90     def put_initial_xml_fields(self):
91         '''Method called at class initialization : Put all fields 
92            corresponding to the command context (user, time, ...)
93         '''
94         # command name
95         self.xmlFile.add_simple_node("Site", attrib={"command" : 
96                                                      self.config.VARS.command})
97         # version of salomeTools
98         self.xmlFile.append_node_attrib("Site", attrib={"satversion" : 
99                                             self.config.INTERNAL.sat_version})
100         # machine name on which the command has been launched
101         self.xmlFile.append_node_attrib("Site", attrib={"hostname" : 
102                                                     self.config.VARS.hostname})
103         # Distribution of the machine
104         self.xmlFile.append_node_attrib("Site", attrib={"OS" : 
105                                                         self.config.VARS.dist})
106         # The user that have launched the command
107         self.xmlFile.append_node_attrib("Site", attrib={"user" : 
108                                                         self.config.VARS.user})
109         # The time when command was launched
110         Y, m, dd, H, M, S = date_to_datetime(self.config.VARS.datehour)
111         date_hour = "%2s/%2s/%4s %2sh%2sm%2ss" % (dd, m, Y, H, M, S)
112         self.xmlFile.append_node_attrib("Site", attrib={"beginTime" : 
113                                                         date_hour})
114         # The application if any
115         if "APPLICATION" in self.config:
116             self.xmlFile.append_node_attrib("Site", 
117                         attrib={"application" : self.config.VARS.application})
118         # The initialization of the trace node
119         self.xmlFile.add_simple_node("Log",text="")
120         # The system commands logs
121         self.xmlFile.add_simple_node("OutLog",
122                                     text=os.path.join("OUT", self.txtFileName))
123         # The initialization of the node where 
124         # to put the links to the other sat commands that can be called by any
125         # command 
126         self.xmlFile.add_simple_node("Links")
127
128     def add_link(self,
129                  log_file_name,
130                  command_name,
131                  command_res,
132                  full_launched_command):
133         '''Add a link to another log file.
134         
135         :param log_file_name str: The file name of the link.
136         :param command_name str: The name of the command linked.
137         :param command_res str: The result of the command linked. "0" or "1"
138         :parma full_launched_command str: The full lanch command 
139                                           ("sat command ...")
140         '''
141         xmlLinks = self.xmlFile.xmlroot.find("Links")
142         src.xmlManager.add_simple_node(xmlLinks,
143                                        "link", 
144                                        text = log_file_name,
145                                        attrib = {"command" : command_name,
146                                                  "passed" : command_res,
147                                            "launchedCommand" : full_launched_command})
148
149     def write(self, message, level=None, screenOnly=False):
150         '''the function used in the commands 
151         that will print in the terminal and the log file.
152         
153         :param message str: The message to print.
154         :param level int: The output level corresponding 
155                           to the message 0 < level < 6.
156         :param screenOnly boolean: if True, do not write in log file.
157         '''
158         # do not write message starting with \r to log file
159         if not message.startswith("\r") and not screenOnly:
160             self.xmlFile.append_node_text("Log", 
161                                           printcolors.cleancolor(message))
162
163         # get user or option output level
164         current_output_verbose_level = self.config.USER.output_verbose_level
165         if not ('isatty' in dir(sys.stdout) and sys.stdout.isatty()):
166             # clean the message color if the terminal is redirected by user
167             # ex: sat compile appli > log.txt
168             message = printcolors.cleancolor(message)
169         
170         # Print message regarding the output level value
171         if level:
172             if level <= current_output_verbose_level and not self.silentSysStd:
173                 sys.stdout.write(message)
174         else:
175             if self.default_level <= current_output_verbose_level and not self.silentSysStd:
176                 sys.stdout.write(message)
177         self.flush()
178
179     def error(self, message):
180         '''Print an error.
181         
182         :param message str: The message to print.
183         '''
184         # Print in the log file
185         self.xmlFile.append_node_text("traces", _('ERROR:') + message)
186
187         # Print in the terminal and clean colors if the terminal 
188         # is redirected by user
189         if not ('isatty' in dir(sys.stderr) and sys.stderr.isatty()):
190             sys.stderr.write(printcolors.printcError(_('ERROR:') + message))
191         else:
192             sys.stderr.write(_('ERROR:') + message)
193
194     def flush(self):
195         '''Flush terminal
196         '''
197         sys.stdout.flush()
198         self.logTxtFile.flush()
199         
200     def end_write(self, attribute):
201         '''Method called just after command end : Put all fields 
202            corresponding to the command end context (time).
203            Write the log xml file on the hard drive.
204            And display the command to launch to get the log
205         
206         :param attribute dict: the attribute to add to the node "Site".
207         '''       
208         # Get current time (end of command) and format it
209         dt = datetime.datetime.now()
210         Y, m, dd, H, M, S = date_to_datetime(self.config.VARS.datehour)
211         t0 = datetime.datetime(int(Y), int(m), int(dd), int(H), int(M), int(S))
212         tf = dt
213         delta = tf - t0
214         total_time = timedelta_total_seconds(delta)
215         hours = int(total_time / 3600)
216         minutes = int((total_time - hours*3600) / 60)
217         seconds = total_time - hours*3600 - minutes*60
218         # Add the fields corresponding to the end time
219         # and the total time of command
220         endtime = dt.strftime('%Y/%m/%d %Hh%Mm%Ss')
221         self.xmlFile.append_node_attrib("Site", attrib={"endTime" : endtime})
222         self.xmlFile.append_node_attrib("Site", 
223                 attrib={"TotalTime" : "%ih%im%is" % (hours, minutes, seconds)})
224         
225         # Add the attribute passed to the method
226         self.xmlFile.append_node_attrib("Site", attrib=attribute)
227         
228         # Call the method to write the xml file on the hard drive
229         self.xmlFile.write_tree(stylesheet = "command.xsl")
230         
231         # Dump the config in a pyconf file in the log directory
232         logDir = self.config.USER.log_dir
233         dumpedPyconfFileName = (self.config.VARS.datehour 
234                                 + "_" 
235                                 + self.config.VARS.command 
236                                 + ".pyconf")
237         dumpedPyconfFilePath = os.path.join(logDir, 'OUT', dumpedPyconfFileName)
238         f = open(dumpedPyconfFilePath, 'w')
239         self.config.__save__(f)
240         f.close()
241         
242
243 def date_to_datetime(date):
244     '''Little method that gets year, mon, day, hour , 
245        minutes and seconds from a str in format YYYYMMDD_HHMMSS
246     
247     :param date str: The date in format YYYYMMDD_HHMMSS
248     :return: the same date and time in separate variables.
249     :rtype: (str,str,str,str,str,str)
250     '''
251     Y = date[:4]
252     m = date[4:6]
253     dd = date[6:8]
254     H = date[9:11]
255     M = date[11:13]
256     S = date[13:15]
257     return Y, m, dd, H, M, S
258
259 def timedelta_total_seconds(timedelta):
260     '''Little method to replace total_seconds from 
261        datetime module in order to be compatible with old python versions
262     
263     :param timedelta datetime.timedelta: The delta between two dates
264     :return: The number of seconds corresponding to timedelta.
265     :rtype: float
266     '''
267     return (
268         timedelta.microseconds + 0.0 +
269         (timedelta.seconds + timedelta.days * 24 * 3600) * 10 ** 6) / 10 ** 6
270         
271 def show_command_log(logFilePath, cmd, application, notShownCommands):
272     '''Used in updateHatXml. Determine if the log xml file logFilePath 
273        has to be shown or not in the hat log.
274     
275     :param logFilePath str: the path to the command xml log file
276     :param cmd str: the command of the log file
277     :param application str: the application passed as parameter 
278                             to the salomeTools command
279     :param notShownCommands list: the list of commands 
280                                   that are not shown by default
281     
282     :return: True if cmd is not in notShownCommands and the application 
283              in the log file corresponds to application
284     :rtype: boolean
285     '''
286     # When the command is not in notShownCommands, no need to go further :
287     # Do not show
288     if cmd in notShownCommands:
289         return False, None
290  
291     # Get the application of the log file
292     try:
293         logFileXml = src.xmlManager.ReadXmlFile(logFilePath)
294     except Exception as e:
295         msg = _("WARNING: the log file %s cannot be read:" % logFilePath)
296         sys.stdout.write(printcolors.printcWarning("%s\n%s\n" % (msg, e)))
297         return False, None
298
299     if 'application' in logFileXml.xmlroot.keys():
300         appliLog = logFileXml.xmlroot.get('application')
301         # if it corresponds, then the log has to be shown
302         if appliLog == application:
303             return True, appliLog
304         elif application != 'None':
305             return False, appliLog
306         
307         return True, appliLog
308     
309     if application == 'None':
310             return True, None    
311         
312     return False, None
313
314 def list_log_file(dirPath, expression):
315     '''Find all files corresponding to expression in dirPath
316     
317     :param dirPath str: the directory where to search the files
318     :param expression str: the regular expression of files to find
319     :return: the list of files path and informations about it
320     :rtype: list
321     '''
322     lRes = []
323     for fileName in os.listdir(dirPath):
324         # YYYYMMDD_HHMMSS_namecmd.xml
325         sExpr = expression
326         oExpr = re.compile(sExpr)
327         if oExpr.search(fileName):
328             # get date and hour and format it
329             date_hour_cmd_host = fileName.split('_')
330             date_not_formated = date_hour_cmd_host[0]
331             date = "%s/%s/%s" % (date_not_formated[6:8], 
332                                  date_not_formated[4:6], 
333                                  date_not_formated[0:4])
334             hour_not_formated = date_hour_cmd_host[1]
335             hour = "%s:%s:%s" % (hour_not_formated[0:2], 
336                                  hour_not_formated[2:4], 
337                                  hour_not_formated[4:6])
338             if len(date_hour_cmd_host) < 4:
339                 cmd = date_hour_cmd_host[2][:-len('.xml')]
340                 host = ""
341             else:
342                 cmd = date_hour_cmd_host[2]
343                 host = date_hour_cmd_host[3][:-len('.xml')]
344             lRes.append((os.path.join(dirPath, fileName), 
345                          date_not_formated,
346                          date,
347                          hour_not_formated,
348                          hour,
349                          cmd,
350                          host))
351     return lRes
352
353 def update_hat_xml(logDir, application=None, notShownCommands = []):
354     '''Create the xml file in logDir that contain all the xml file 
355        and have a name like YYYYMMDD_HHMMSS_namecmd.xml
356     
357     :param logDir str: the directory to parse
358     :param application str: the name of the application if there is any
359     '''
360     # Create an instance of XmlLogFile class to create hat.xml file
361     xmlHatFilePath = os.path.join(logDir, 'hat.xml')
362     xmlHat = src.xmlManager.XmlLogFile(xmlHatFilePath,
363                                     "LOGlist", {"application" : application})
364     # parse the log directory to find all the command logs, 
365     # then add it to the xml file
366     lLogFile = list_log_file(logDir, logCommandFileExpression)
367     for filePath, __, date, __, hour, cmd, __ in lLogFile:
368         showLog, cmdAppli = show_command_log(filePath, cmd,
369                                               application, notShownCommands)
370         #if cmd not in notShownCommands:
371         if showLog:
372             # add a node to the hat.xml file
373             xmlHat.add_simple_node("LogCommand", 
374                                    text=os.path.basename(filePath), 
375                                    attrib = {"date" : date, 
376                                              "hour" : hour, 
377                                              "cmd" : cmd, 
378                                              "application" : cmdAppli})
379     
380     # Write the file on the hard drive
381     xmlHat.write_tree('hat.xsl')