Salome HOME
compatibility python 2.6 and bug fix in sat log -t
[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
23 import src
24 from . import printcolors
25 from . import xmlManager
26
27 class Logger(object):
28     '''Class to handle log mechanism
29     '''
30     def __init__(self, config, silent_sysstd=False):
31         '''Initialization
32         
33         :param config pyconf.Config: The global configuration.
34         :param silent_sysstd boolean: if True, do not write anything in terminal.
35         '''
36         self.config = config
37         self.default_level = 3
38         self.silentSysStd = silent_sysstd
39         
40         # Construct log file location. There are two cases. With an application an without any application.
41         logFileName = config.VARS.datehour + "_" + config.VARS.command + ".xml"
42         if 'APPLICATION' in config:
43             logFilePath = os.path.join(config.APPLICATION.out_dir, 'LOGS', logFileName)
44         else:
45             logFilePath = os.path.join(config.VARS.personalDir, 'LOGS', logFileName)
46         src.ensure_path_exists(os.path.dirname(logFilePath))
47         
48         self.logFileName = logFileName
49         self.logFilePath = logFilePath   
50         self.xmlFile = xmlManager.xmlLogFile(logFilePath, "SATcommand", attrib = {"command" : config.VARS.command})
51         self.putInitialXMLFields()
52         
53     def putInitialXMLFields(self):
54         '''Method called at class initialization : Put all fields corresponding to the command context (user, time, ...)
55         '''
56         # command name
57         self.xmlFile.add_simple_node("Site", attrib={"command" : self.config.VARS.command})
58         # version of salomeTools
59         self.xmlFile.append_node_attrib("Site", attrib={"satversion" : self.config.INTERNAL.sat_version})
60         # machine name on which the command has been launched
61         self.xmlFile.append_node_attrib("Site", attrib={"hostname" : self.config.VARS.hostname})
62         # Distribution of the machine
63         self.xmlFile.append_node_attrib("Site", attrib={"OS" : self.config.VARS.dist})
64         # The user that have launched the command
65         self.xmlFile.append_node_attrib("Site", attrib={"user" : self.config.VARS.user})
66         # The time when command was launched
67         Y, m, dd, H, M, S = date_to_datetime(self.config.VARS.datehour)
68         date_hour = "%2s/%2s/%4s %2sh%2sm%2ss" % (dd, m, Y, H, M, S)
69         self.xmlFile.append_node_attrib("Site", attrib={"beginTime" : date_hour})
70         # The initialization of the trace node
71         self.xmlFile.add_simple_node("Log",text="")
72
73     def write(self, message, level=None, screenOnly=False):
74         '''the function used in the commands that will print in the terminal and the log file.
75         
76         :param message str: The message to print.
77         :param level int: The output level corresponding to the message 0 < level < 6.
78         :param screenOnly boolean: if True, do not write in log file.
79         '''
80         # do not write message starting with \r to log file
81         if not message.startswith("\r") and not screenOnly:
82             self.xmlFile.append_node_text("Log", printcolors.cleancolor(message))
83
84         # get user or option output level
85         current_output_level = self.config.USER.output_level
86         if not ('isatty' in dir(sys.stdout) and sys.stdout.isatty()):
87             # clean the message color if the terminal is redirected by user
88             # ex: sat compile appli > log.txt
89             message = printcolors.cleancolor(message)
90         
91         # Print message regarding the output level value
92         if level:
93             if level <= current_output_level and not self.silentSysStd:
94                 sys.stdout.write(message)
95         else:
96             if self.default_level <= current_output_level and not self.silentSysStd:
97                 sys.stdout.write(message)
98
99     def error(self, message):
100         '''Print an error.
101         
102         :param message str: The message to print.
103         '''
104         # Print in the log file
105         self.xmlFile.append_node_text("traces", _('ERROR:') + message)
106
107         # Print in the terminal and clean colors if the terminal is redirected by user
108         if not ('isatty' in dir(sys.stderr) and sys.stderr.isatty()):
109             sys.stderr.write(printcolors.printcError(_('ERROR:') + message))
110         else:
111             sys.stderr.write(_('ERROR:') + message)
112
113     def flush(self):
114         '''Flush terminal
115         '''
116         sys.stdout.flush()
117         
118     def endWrite(self):
119         '''Method called just after command end : Put all fields corresponding to the command end context (time).
120         Write the log xml file on the hard drive.
121         And display the command to launch to get the log
122         '''
123         # Print the command to launch to get the log, regarding the fact that there an application or not
124         self.write(_('\nTap the following command to get the log :\n'), screenOnly=True)
125         if 'APPLICATION' in self.config:
126             self.write('%s/sat log %s\n' % (self.config.VARS.salometoolsway, self.config.VARS.application), screenOnly=True)
127         else:
128             self.write('%s/sat log\n' % self.config.VARS.salometoolsway, screenOnly=True)
129         
130         # Get current time (end of command) and format it
131         dt = datetime.datetime.now()
132         Y, m, dd, H, M, S = date_to_datetime(self.config.VARS.datehour)
133         t0 = datetime.datetime(int(Y), int(m), int(dd), int(H), int(M), int(S))
134         tf = dt
135         delta = tf - t0
136         total_time = timedelta_total_seconds(delta)
137         hours = int(total_time / 3600)
138         minutes = int((total_time - hours*3600) / 60)
139         seconds = total_time - hours*3600 - minutes*60
140         # Add the fields corresponding to the end time and the total time of command
141         endtime = dt.strftime('%d/%Y/%m %Hh%Mm%Ss')
142         self.xmlFile.append_node_attrib("Site", attrib={"endTime" : endtime})
143         self.xmlFile.append_node_attrib("Site", attrib={"TotalTime" : "%ih%im%is" % (hours, minutes, seconds)})
144         
145         # Call the method to write the xml file on the hard drive
146         self.xmlFile.write_tree(stylesheet = "command.xsl")
147         
148         # Update the hat xml (that shows all logs) in order to make the new log visible on the main log page)
149         if 'APPLICATION' in self.config:
150             src.xmlManager.update_hat_xml(self.config.VARS.logDir, self.config.VARS.application)
151         else:
152             src.xmlManager.update_hat_xml(self.config.VARS.logDir)
153
154 def date_to_datetime(date):
155     '''Little method that gets year, mon, day, hour , minutes and seconds from a str in format YYYYMMDD_HHMMSS
156     
157     :param date str: The date in format YYYYMMDD_HHMMSS
158     :return: the same date and time in separate variables.
159     :rtype: (str,str,str,str,str,str)
160     '''
161     Y = date[:4]
162     m = date[4:6]
163     dd = date[6:8]
164     H = date[9:11]
165     M = date[11:13]
166     S = date[13:15]
167     return Y, m, dd, H, M, S
168
169 def timedelta_total_seconds(timedelta):
170     '''Little method to replace total_seconds from datetime module in order to be compatible with old python versions
171     
172     :param timedelta datetime.timedelta: The delta between two dates
173     :return: The number of seconds corresponding to timedelta.
174     :rtype: float
175     '''
176     return (
177         timedelta.microseconds + 0.0 +
178         (timedelta.seconds + timedelta.days * 24 * 3600) * 10 ** 6) / 10 ** 6