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