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