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