1## @file
2# This file implements the log mechanism for Python tools.
3#
4# Copyright (c) 2011, Intel Corporation. All rights reserved.<BR>
5#
6# This program and the accompanying materials are licensed and made available
7# under the terms and conditions of the BSD License which accompanies this
8# distribution. The full text of the license may be found at
9# http://opensource.org/licenses/bsd-license.php
10#
11# THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
12# WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
13#
14
15'''
16Logger
17'''
18
19## Import modules
20from sys import argv
21from sys import stdout
22from sys import stderr
23import os.path
24from os import remove
25from logging import getLogger
26from logging import Formatter
27from logging import StreamHandler
28from logging import FileHandler
29from traceback import extract_stack
30
31from Logger.ToolError import FatalError
32from Logger.ToolError import WARNING_AS_ERROR
33from Logger.ToolError import gERROR_MESSAGE
34from Logger.ToolError import UNKNOWN_ERROR
35from Library import GlobalData
36
37#
38# Log level constants
39#
40DEBUG_0 = 1
41DEBUG_1 = 2
42DEBUG_2 = 3
43DEBUG_3 = 4
44DEBUG_4 = 5
45DEBUG_5 = 6
46DEBUG_6 = 7
47DEBUG_7 = 8
48DEBUG_8 = 9
49DEBUG_9 = 10
50VERBOSE = 15
51INFO    = 20
52WARN    = 30
53QUIET   = 40
54QUIET_1 = 41
55ERROR   = 50
56SILENT  = 60
57
58IS_RAISE_ERROR = True
59SUPRESS_ERROR = False
60
61#
62# Tool name
63#
64_TOOL_NAME = os.path.basename(argv[0])
65#
66# For validation purpose
67#
68_LOG_LEVELS = [DEBUG_0, DEBUG_1, DEBUG_2, DEBUG_3, DEBUG_4, DEBUG_5, DEBUG_6, \
69              DEBUG_7, DEBUG_8, DEBUG_9, VERBOSE, WARN, INFO, ERROR, QUIET, \
70              QUIET_1, SILENT]
71#
72# For DEBUG level (All DEBUG_0~9 are applicable)
73#
74_DEBUG_LOGGER = getLogger("tool_debug")
75_DEBUG_FORMATTER = Formatter("[%(asctime)s.%(msecs)d]: %(message)s", \
76                            datefmt="%H:%M:%S")
77#
78# For VERBOSE, INFO, WARN level
79#
80_INFO_LOGGER = getLogger("tool_info")
81_INFO_FORMATTER = Formatter("%(message)s")
82#
83# For ERROR level
84#
85_ERROR_LOGGER = getLogger("tool_error")
86_ERROR_FORMATTER = Formatter("%(message)s")
87
88#
89# String templates for ERROR/WARN/DEBUG log message
90#
91_ERROR_MESSAGE_TEMPLATE = \
92('\n\n%(tool)s...\n%(file)s(%(line)s): error %(errorcode)04X: %(msg)s\n\t%(extra)s')
93
94__ERROR_MESSAGE_TEMPLATE_WITHOUT_FILE = \
95'\n\n%(tool)s...\n : error %(errorcode)04X: %(msg)s\n\t%(extra)s'
96
97_WARNING_MESSAGE_TEMPLATE = '%(tool)s...\n%(file)s(%(line)s): warning: %(msg)s'
98_WARNING_MESSAGE_TEMPLATE_WITHOUT_FILE = '%(tool)s: : warning: %(msg)s'
99_DEBUG_MESSAGE_TEMPLATE = '%(file)s(%(line)s): debug: \n    %(msg)s'
100
101
102#
103# Log INFO message
104#
105#Info    = _INFO_LOGGER.info
106
107def Info(msg, *args, **kwargs):
108    _INFO_LOGGER.info(msg, *args, **kwargs)
109
110#
111# Log information which should be always put out
112#
113def Quiet(msg, *args, **kwargs):
114    _ERROR_LOGGER.error(msg, *args, **kwargs)
115
116## Log debug message
117#
118#   @param  Level       DEBUG level (DEBUG0~9)
119#   @param  Message     Debug information
120#   @param  ExtraData   More information associated with "Message"
121#
122def Debug(Level, Message, ExtraData=None):
123    if _DEBUG_LOGGER.level > Level:
124        return
125    if Level > DEBUG_9:
126        return
127    #
128    # Find out the caller method information
129    #
130    CallerStack = extract_stack()[-2]
131    TemplateDict = {
132        "file"      : CallerStack[0],
133        "line"      : CallerStack[1],
134        "msg"       : Message,
135    }
136
137    if ExtraData != None:
138        LogText = _DEBUG_MESSAGE_TEMPLATE % TemplateDict + "\n    %s" % ExtraData
139    else:
140        LogText = _DEBUG_MESSAGE_TEMPLATE % TemplateDict
141
142    _DEBUG_LOGGER.log(Level, LogText)
143
144## Log verbose message
145#
146#   @param  Message     Verbose information
147#
148def Verbose(Message):
149    return _INFO_LOGGER.log(VERBOSE, Message)
150
151## Log warning message
152#
153#   Warning messages are those which might be wrong but won't fail the tool.
154#
155#   @param  ToolName    The name of the tool. If not given, the name of caller
156#                       method will be used.
157#   @param  Message     Warning information
158#   @param  File        The name of file which caused the warning.
159#   @param  Line        The line number in the "File" which caused the warning.
160#   @param  ExtraData   More information associated with "Message"
161#
162def Warn(ToolName, Message, File=None, Line=None, ExtraData=None):
163    if _INFO_LOGGER.level > WARN:
164        return
165    #
166    # if no tool name given, use caller's source file name as tool name
167    #
168    if ToolName == None or ToolName == "":
169        ToolName = os.path.basename(extract_stack()[-2][0])
170
171    if Line == None:
172        Line = "..."
173    else:
174        Line = "%d" % Line
175
176    TemplateDict = {
177        "tool"      : ToolName,
178        "file"      : File,
179        "line"      : Line,
180        "msg"       : Message,
181    }
182
183    if File != None:
184        LogText = _WARNING_MESSAGE_TEMPLATE % TemplateDict
185    else:
186        LogText = _WARNING_MESSAGE_TEMPLATE_WITHOUT_FILE % TemplateDict
187
188    if ExtraData != None:
189        LogText += "\n    %s" % ExtraData
190
191    _INFO_LOGGER.log(WARN, LogText)
192    #
193    # Raise an execption if indicated
194    #
195    if GlobalData.gWARNING_AS_ERROR == True:
196        raise FatalError(WARNING_AS_ERROR)
197
198## Log ERROR message
199#
200# Once an error messages is logged, the tool's execution will be broken by
201# raising an execption. If you don't want to break the execution later, you
202# can give "RaiseError" with "False" value.
203#
204#   @param  ToolName    The name of the tool. If not given, the name of caller
205#                       method will be used.
206#   @param  ErrorCode   The error code
207#   @param  Message     Warning information
208#   @param  File        The name of file which caused the error.
209#   @param  Line        The line number in the "File" which caused the warning.
210#   @param  ExtraData   More information associated with "Message"
211#   @param  RaiseError  Raise an exception to break the tool's executuion if
212#                       it's True. This is the default behavior.
213#
214def Error(ToolName, ErrorCode, Message=None, File=None, Line=None, \
215          ExtraData=None, RaiseError=IS_RAISE_ERROR):
216    if ToolName:
217        pass
218    if Line == None:
219        Line = "..."
220    else:
221        Line = "%d" % Line
222
223    if Message == None:
224        if ErrorCode in gERROR_MESSAGE:
225            Message = gERROR_MESSAGE[ErrorCode]
226        else:
227            Message = gERROR_MESSAGE[UNKNOWN_ERROR]
228
229    if ExtraData == None:
230        ExtraData = ""
231
232    TemplateDict = {
233        "tool"      : _TOOL_NAME,
234        "file"      : File,
235        "line"      : Line,
236        "errorcode" : ErrorCode,
237        "msg"       : Message,
238        "extra"     : ExtraData
239    }
240
241    if File != None:
242        LogText =  _ERROR_MESSAGE_TEMPLATE % TemplateDict
243    else:
244        LogText = __ERROR_MESSAGE_TEMPLATE_WITHOUT_FILE % TemplateDict
245
246    if not SUPRESS_ERROR:
247        _ERROR_LOGGER.log(ERROR, LogText)
248    if RaiseError:
249        raise FatalError(ErrorCode)
250
251
252## Initialize log system
253#
254def Initialize():
255    #
256    # Since we use different format to log different levels of message into
257    # different place (stdout or stderr), we have to use different "Logger"
258    # objects to do this.
259    #
260    # For DEBUG level (All DEBUG_0~9 are applicable)
261    _DEBUG_LOGGER.setLevel(INFO)
262    _DebugChannel = StreamHandler(stdout)
263    _DebugChannel.setFormatter(_DEBUG_FORMATTER)
264    _DEBUG_LOGGER.addHandler(_DebugChannel)
265    #
266    # For VERBOSE, INFO, WARN level
267    #
268    _INFO_LOGGER.setLevel(INFO)
269    _InfoChannel = StreamHandler(stdout)
270    _InfoChannel.setFormatter(_INFO_FORMATTER)
271    _INFO_LOGGER.addHandler(_InfoChannel)
272    #
273    # For ERROR level
274    #
275    _ERROR_LOGGER.setLevel(INFO)
276    _ErrorCh = StreamHandler(stderr)
277    _ErrorCh.setFormatter(_ERROR_FORMATTER)
278    _ERROR_LOGGER.addHandler(_ErrorCh)
279
280
281## Set log level
282#
283#   @param  Level   One of log level in _LogLevel
284#
285def SetLevel(Level):
286    if Level not in _LOG_LEVELS:
287        Info("Not supported log level (%d). Use default level instead." % \
288             Level)
289        Level = INFO
290    _DEBUG_LOGGER.setLevel(Level)
291    _INFO_LOGGER.setLevel(Level)
292    _ERROR_LOGGER.setLevel(Level)
293
294## Get current log level
295#
296def GetLevel():
297    return _INFO_LOGGER.getEffectiveLevel()
298
299## Raise up warning as error
300#
301def SetWarningAsError():
302    GlobalData.gWARNING_AS_ERROR = True
303
304## Specify a file to store the log message as well as put on console
305#
306#   @param  LogFile     The file path used to store the log message
307#
308def SetLogFile(LogFile):
309    if os.path.exists(LogFile):
310        remove(LogFile)
311
312    _Ch = FileHandler(LogFile)
313    _Ch.setFormatter(_DEBUG_FORMATTER)
314    _DEBUG_LOGGER.addHandler(_Ch)
315
316    _Ch = FileHandler(LogFile)
317    _Ch.setFormatter(_INFO_FORMATTER)
318    _INFO_LOGGER.addHandler(_Ch)
319
320    _Ch = FileHandler(LogFile)
321    _Ch.setFormatter(_ERROR_FORMATTER)
322    _ERROR_LOGGER.addHandler(_Ch)
323
324
325
326