1## @file
2# This file implements the log mechanism for Python tools.
3#
4# Copyright (c) 2007 - 2018, Intel Corporation. All rights reserved.<BR>
5# SPDX-License-Identifier: BSD-2-Clause-Patent
6#
7
8# Copyright 2001-2016 by Vinay Sajip. All Rights Reserved.
9#
10# Permission to use, copy, modify, and distribute this software and its
11# documentation for any purpose and without fee is hereby granted,
12# provided that the above copyright notice appear in all copies and that
13# both that copyright notice and this permission notice appear in
14# supporting documentation, and that the name of Vinay Sajip
15# not be used in advertising or publicity pertaining to distribution
16# of the software without specific, written prior permission.
17# VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
18# ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
19# VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
20# ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
21# IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
22# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23# This copyright is for QueueHandler.
24
25## Import modules
26from __future__ import absolute_import
27import Common.LongFilePathOs as os, sys, logging
28import traceback
29from  .BuildToolError import *
30try:
31    from logging.handlers import QueueHandler
32except:
33    class QueueHandler(logging.Handler):
34        """
35        This handler sends events to a queue. Typically, it would be used together
36        with a multiprocessing Queue to centralise logging to file in one process
37        (in a multi-process application), so as to avoid file write contention
38        between processes.
39
40        This code is new in Python 3.2, but this class can be copy pasted into
41        user code for use with earlier Python versions.
42        """
43
44        def __init__(self, queue):
45            """
46            Initialise an instance, using the passed queue.
47            """
48            logging.Handler.__init__(self)
49            self.queue = queue
50
51        def enqueue(self, record):
52            """
53            Enqueue a record.
54
55            The base implementation uses put_nowait. You may want to override
56            this method if you want to use blocking, timeouts or custom queue
57            implementations.
58            """
59            self.queue.put_nowait(record)
60
61        def prepare(self, record):
62            """
63            Prepares a record for queuing. The object returned by this method is
64            enqueued.
65
66            The base implementation formats the record to merge the message
67            and arguments, and removes unpickleable items from the record
68            in-place.
69
70            You might want to override this method if you want to convert
71            the record to a dict or JSON string, or send a modified copy
72            of the record while leaving the original intact.
73            """
74            # The format operation gets traceback text into record.exc_text
75            # (if there's exception data), and also returns the formatted
76            # message. We can then use this to replace the original
77            # msg + args, as these might be unpickleable. We also zap the
78            # exc_info and exc_text attributes, as they are no longer
79            # needed and, if not None, will typically not be pickleable.
80            msg = self.format(record)
81            record.message = msg
82            record.msg = msg
83            record.args = None
84            record.exc_info = None
85            record.exc_text = None
86            return record
87
88        def emit(self, record):
89            """
90            Emit a record.
91
92            Writes the LogRecord to the queue, preparing it for pickling first.
93            """
94            try:
95                self.enqueue(self.prepare(record))
96            except Exception:
97                self.handleError(record)
98class BlockQueueHandler(QueueHandler):
99    def enqueue(self, record):
100        self.queue.put(record,True)
101## Log level constants
102DEBUG_0 = 1
103DEBUG_1 = 2
104DEBUG_2 = 3
105DEBUG_3 = 4
106DEBUG_4 = 5
107DEBUG_5 = 6
108DEBUG_6 = 7
109DEBUG_7 = 8
110DEBUG_8 = 9
111DEBUG_9 = 10
112VERBOSE = 15
113INFO    = 20
114WARN    = 30
115QUIET   = 40
116ERROR   = 50
117SILENT  = 99
118
119IsRaiseError = True
120
121# Tool name
122_ToolName = os.path.basename(sys.argv[0])
123
124# For validation purpose
125_LogLevels = [DEBUG_0, DEBUG_1, DEBUG_2, DEBUG_3, DEBUG_4, DEBUG_5,
126              DEBUG_6, DEBUG_7, DEBUG_8, DEBUG_9, VERBOSE, WARN, INFO,
127              ERROR, QUIET, SILENT]
128
129# For DEBUG level (All DEBUG_0~9 are applicable)
130_DebugLogger = logging.getLogger("tool_debug")
131_DebugFormatter = logging.Formatter("[%(asctime)s.%(msecs)d]: %(message)s", datefmt="%H:%M:%S")
132
133# For VERBOSE, INFO, WARN level
134_InfoLogger = logging.getLogger("tool_info")
135_InfoFormatter = logging.Formatter("%(message)s")
136
137# For ERROR level
138_ErrorLogger = logging.getLogger("tool_error")
139_ErrorFormatter = logging.Formatter("%(message)s")
140
141# String templates for ERROR/WARN/DEBUG log message
142_ErrorMessageTemplate = '\n\n%(tool)s...\n%(file)s(%(line)s): error %(errorcode)04X: %(msg)s\n\t%(extra)s'
143_ErrorMessageTemplateWithoutFile = '\n\n%(tool)s...\n : error %(errorcode)04X: %(msg)s\n\t%(extra)s'
144_WarningMessageTemplate = '%(tool)s...\n%(file)s(%(line)s): warning: %(msg)s'
145_WarningMessageTemplateWithoutFile = '%(tool)s: : warning: %(msg)s'
146_DebugMessageTemplate = '%(file)s(%(line)s): debug: \n    %(msg)s'
147
148#
149# Flag used to take WARN as ERROR.
150# By default, only ERROR message will break the tools execution.
151#
152_WarningAsError = False
153
154## Log debug message
155#
156#   @param  Level       DEBUG level (DEBUG0~9)
157#   @param  Message     Debug information
158#   @param  ExtraData   More information associated with "Message"
159#
160def debug(Level, Message, ExtraData=None):
161    if _DebugLogger.level > Level:
162        return
163    if Level > DEBUG_9:
164        return
165
166    # Find out the caller method information
167    CallerStack = traceback.extract_stack()[-2]
168    TemplateDict = {
169        "file"      : CallerStack[0],
170        "line"      : CallerStack[1],
171        "msg"       : Message,
172    }
173
174    if ExtraData is not None:
175        LogText = _DebugMessageTemplate % TemplateDict + "\n    %s" % ExtraData
176    else:
177        LogText = _DebugMessageTemplate % TemplateDict
178
179    _DebugLogger.log(Level, LogText)
180
181## Log verbose message
182#
183#   @param  Message     Verbose information
184#
185def verbose(Message):
186    return _InfoLogger.log(VERBOSE, Message)
187
188## Log warning message
189#
190#   Warning messages are those which might be wrong but won't fail the tool.
191#
192#   @param  ToolName    The name of the tool. If not given, the name of caller
193#                       method will be used.
194#   @param  Message     Warning information
195#   @param  File        The name of file which caused the warning.
196#   @param  Line        The line number in the "File" which caused the warning.
197#   @param  ExtraData   More information associated with "Message"
198#
199def warn(ToolName, Message, File=None, Line=None, ExtraData=None):
200    if _InfoLogger.level > WARN:
201        return
202
203    # if no tool name given, use caller's source file name as tool name
204    if ToolName is None or ToolName == "":
205        ToolName = os.path.basename(traceback.extract_stack()[-2][0])
206
207    if Line is None:
208        Line = "..."
209    else:
210        Line = "%d" % Line
211
212    TemplateDict = {
213        "tool"      : ToolName,
214        "file"      : File,
215        "line"      : Line,
216        "msg"       : Message,
217    }
218
219    if File is not None:
220        LogText = _WarningMessageTemplate % TemplateDict
221    else:
222        LogText = _WarningMessageTemplateWithoutFile % TemplateDict
223
224    if ExtraData is not None:
225        LogText += "\n    %s" % ExtraData
226
227    _InfoLogger.log(WARN, LogText)
228
229    # Raise an exception if indicated
230    if _WarningAsError == True:
231        raise FatalError(WARNING_AS_ERROR)
232
233## Log INFO message
234info    = _InfoLogger.info
235
236## Log ERROR message
237#
238#   Once an error messages is logged, the tool's execution will be broken by raising
239# an exception. If you don't want to break the execution later, you can give
240# "RaiseError" with "False" value.
241#
242#   @param  ToolName    The name of the tool. If not given, the name of caller
243#                       method will be used.
244#   @param  ErrorCode   The error code
245#   @param  Message     Warning information
246#   @param  File        The name of file which caused the error.
247#   @param  Line        The line number in the "File" which caused the warning.
248#   @param  ExtraData   More information associated with "Message"
249#   @param  RaiseError  Raise an exception to break the tool's execution if
250#                       it's True. This is the default behavior.
251#
252def error(ToolName, ErrorCode, Message=None, File=None, Line=None, ExtraData=None, RaiseError=IsRaiseError):
253    if Line is None:
254        Line = "..."
255    else:
256        Line = "%d" % Line
257
258    if Message is None:
259        if ErrorCode in gErrorMessage:
260            Message = gErrorMessage[ErrorCode]
261        else:
262            Message = gErrorMessage[UNKNOWN_ERROR]
263
264    if ExtraData is None:
265        ExtraData = ""
266
267    TemplateDict = {
268        "tool"      : _ToolName,
269        "file"      : File,
270        "line"      : Line,
271        "errorcode" : ErrorCode,
272        "msg"       : Message,
273        "extra"     : ExtraData
274    }
275
276    if File is not None:
277        LogText =  _ErrorMessageTemplate % TemplateDict
278    else:
279        LogText = _ErrorMessageTemplateWithoutFile % TemplateDict
280
281    _ErrorLogger.log(ERROR, LogText)
282
283    if RaiseError and IsRaiseError:
284        raise FatalError(ErrorCode)
285
286# Log information which should be always put out
287quiet   = _ErrorLogger.error
288
289## Initialize log system
290def LogClientInitialize(log_q):
291    #
292    # Since we use different format to log different levels of message into different
293    # place (stdout or stderr), we have to use different "Logger" objects to do this.
294    #
295    # For DEBUG level (All DEBUG_0~9 are applicable)
296    _DebugLogger.setLevel(INFO)
297    _DebugChannel = BlockQueueHandler(log_q)
298    _DebugChannel.setFormatter(_DebugFormatter)
299    _DebugLogger.addHandler(_DebugChannel)
300
301    # For VERBOSE, INFO, WARN level
302    _InfoLogger.setLevel(INFO)
303    _InfoChannel = BlockQueueHandler(log_q)
304    _InfoChannel.setFormatter(_InfoFormatter)
305    _InfoLogger.addHandler(_InfoChannel)
306
307    # For ERROR level
308    _ErrorLogger.setLevel(INFO)
309    _ErrorCh = BlockQueueHandler(log_q)
310    _ErrorCh.setFormatter(_ErrorFormatter)
311    _ErrorLogger.addHandler(_ErrorCh)
312
313## Set log level
314#
315#   @param  Level   One of log level in _LogLevel
316def SetLevel(Level):
317    if Level not in _LogLevels:
318        info("Not supported log level (%d). Use default level instead." % Level)
319        Level = INFO
320    _DebugLogger.setLevel(Level)
321    _InfoLogger.setLevel(Level)
322    _ErrorLogger.setLevel(Level)
323
324## Initialize log system
325def Initialize():
326    #
327    # Since we use different format to log different levels of message into different
328    # place (stdout or stderr), we have to use different "Logger" objects to do this.
329    #
330    # For DEBUG level (All DEBUG_0~9 are applicable)
331    _DebugLogger.setLevel(INFO)
332    _DebugChannel = logging.StreamHandler(sys.stdout)
333    _DebugChannel.setFormatter(_DebugFormatter)
334    _DebugLogger.addHandler(_DebugChannel)
335
336    # For VERBOSE, INFO, WARN level
337    _InfoLogger.setLevel(INFO)
338    _InfoChannel = logging.StreamHandler(sys.stdout)
339    _InfoChannel.setFormatter(_InfoFormatter)
340    _InfoLogger.addHandler(_InfoChannel)
341
342    # For ERROR level
343    _ErrorLogger.setLevel(INFO)
344    _ErrorCh = logging.StreamHandler(sys.stderr)
345    _ErrorCh.setFormatter(_ErrorFormatter)
346    _ErrorLogger.addHandler(_ErrorCh)
347
348def InitializeForUnitTest():
349    Initialize()
350    SetLevel(SILENT)
351
352## Get current log level
353def GetLevel():
354    return _InfoLogger.getEffectiveLevel()
355
356## Raise up warning as error
357def SetWarningAsError():
358    global _WarningAsError
359    _WarningAsError = True
360
361## Specify a file to store the log message as well as put on console
362#
363#   @param  LogFile     The file path used to store the log message
364#
365def SetLogFile(LogFile):
366    if os.path.exists(LogFile):
367        os.remove(LogFile)
368
369    _Ch = logging.FileHandler(LogFile)
370    _Ch.setFormatter(_DebugFormatter)
371    _DebugLogger.addHandler(_Ch)
372
373    _Ch= logging.FileHandler(LogFile)
374    _Ch.setFormatter(_InfoFormatter)
375    _InfoLogger.addHandler(_Ch)
376
377    _Ch = logging.FileHandler(LogFile)
378    _Ch.setFormatter(_ErrorFormatter)
379    _ErrorLogger.addHandler(_Ch)
380
381if __name__ == '__main__':
382    pass
383
384