1682 lines
56 KiB
Python
1682 lines
56 KiB
Python
# -*- coding:utf-8 -*-
|
|
#############################################################################
|
|
# Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd.
|
|
# Portions Copyright (c) 2007 Agendaless Consulting and Contributors.
|
|
#
|
|
# openGauss is licensed under Mulan PSL v2.
|
|
# You can use this software according to the terms
|
|
# and conditions of the Mulan PSL v2.
|
|
# You may obtain a copy of Mulan PSL v2 at:
|
|
#
|
|
# http://license.coscl.org.cn/MulanPSL2
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OF ANY KIND,
|
|
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
|
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
|
# See the Mulan PSL v2 for more details.
|
|
# ----------------------------------------------------------------------------
|
|
# Description : GaussLog.py is utility to handle the log
|
|
#############################################################################
|
|
import os
|
|
import sys
|
|
import datetime
|
|
import subprocess
|
|
import _thread as thread
|
|
import re
|
|
import logging
|
|
import logging.handlers as _handlers
|
|
import time
|
|
import io
|
|
import traceback
|
|
import codecs
|
|
|
|
sys.path.append(sys.path[0] + "/../../")
|
|
|
|
from gspylib.common.ErrorCode import ErrorCode
|
|
from gspylib.common.ErrorCode import OmError as _OmError
|
|
from base_utils.os.file_util import FileUtil
|
|
from base_utils.common.constantsbase import ConstantsBase
|
|
from base_utils.security.sensitive_mask import SensitiveMask
|
|
from domain_utils.domain_common.cluster_constants import ClusterConstants
|
|
|
|
# import typing for comment.
|
|
try:
|
|
from typing import Dict
|
|
from typing import List
|
|
except ImportError:
|
|
Dict = dict
|
|
List = list
|
|
|
|
# max log file size
|
|
# 16M
|
|
MAXLOGFILESIZE = 16 * 1024 * 1024
|
|
# The list of local action in preinstall
|
|
PREINSTALL_ACTION = ["prepare_path", "check_os_Version", "create_os_user",
|
|
"check_os_user", "create_cluster_paths",
|
|
"set_os_parameter", "set_finish_flag", "set_warning_env",
|
|
"prepare_user_cron_service", "prepare_user_sshd_service",
|
|
"set_library", "set_virtualIp",
|
|
"clean_virtualIp", "check_hostname_mapping",
|
|
"init_gausslog", "check_envfile", "check_dir_owner",
|
|
"set_user_env", "set_tool_env", "gs_preinstall"]
|
|
|
|
LOG_DEBUG = 1
|
|
LOG_INFO = 2
|
|
LOG_WARNING = 2.1
|
|
LOG_ERROR = 3
|
|
LOG_FATAL = 4
|
|
|
|
#
|
|
# _srcfile is used when walking the stack to check when we've got the first
|
|
# caller stack frame.
|
|
#
|
|
if hasattr(sys, 'frozen'): # support for py2exe
|
|
_srcfile = "logging%s__init__%s" % (os.sep, __file__[-4:])
|
|
elif __file__[-4:].lower() in ['.pyc', '.pyo']:
|
|
_srcfile = __file__[:-4] + '.py'
|
|
else:
|
|
_srcfile = __file__
|
|
_srcfile = os.path.normcase(_srcfile)
|
|
|
|
|
|
class GaussLog:
|
|
"""
|
|
Class to handle log file
|
|
"""
|
|
|
|
def __init__(self, logFile, module="", expectLevel=LOG_DEBUG):
|
|
"""
|
|
function: Constructor
|
|
input : NA
|
|
output: NA
|
|
"""
|
|
self.logFile = ""
|
|
self.expectLevel = expectLevel
|
|
self.moduleName = module
|
|
self.fp = None
|
|
self.size = 0
|
|
self.suffix = ""
|
|
self.prefix = ""
|
|
self.dir = ""
|
|
self.pid = os.getpid()
|
|
self.step = 0
|
|
self.lock = thread.allocate_lock()
|
|
self.tmpFile = None
|
|
self.ignoreErr = False
|
|
|
|
logFileList = ""
|
|
try:
|
|
dirName = os.path.dirname(logFile)
|
|
# check log path
|
|
if (not os.path.exists(dirName)):
|
|
try:
|
|
topDirPath = FileUtil.getTopPathNotExist(dirName)
|
|
self.tmpFile = '%s/topDirPath.dat' % dirName
|
|
if os.getuid() == 0:
|
|
self.tmpFile = ClusterConstants.TOP_DIR_FILE
|
|
if (not os.path.isdir(dirName)):
|
|
os.makedirs(dirName,
|
|
ConstantsBase.KEY_DIRECTORY_PERMISSION)
|
|
except Exception as e:
|
|
raise Exception(ErrorCode.GAUSS_502["GAUSS_50208"] %
|
|
dirName + " Error:\n%s" % str(e))
|
|
cmd = "echo %s > '%s' && chmod 600 '%s'" % (topDirPath, self.tmpFile, self.tmpFile)
|
|
(status, output) = subprocess.getstatusoutput(cmd)
|
|
if (status != 0):
|
|
raise Exception(ErrorCode.GAUSS_502["GAUSS_50206"]
|
|
% "the top path" + " Error:\n%s." % output
|
|
+ "The cmd is %s" % cmd)
|
|
self.dir = dirName
|
|
originalFileName = os.path.basename(logFile)
|
|
resList = originalFileName.split(".")
|
|
if (len(resList) > 2):
|
|
raise Exception(ErrorCode.GAUSS_502["GAUSS_50224"] +
|
|
" Error: The file name [%s] can not contain "
|
|
"more than one '.'." % logFile)
|
|
# check suffix
|
|
(self.prefix, self.suffix) = os.path.splitext(originalFileName)
|
|
if (self.suffix != ".log"):
|
|
raise Exception(
|
|
ErrorCode.GAUSS_502["GAUSS_50212"] % (logFile, ".log"))
|
|
|
|
# get log file list
|
|
logFileList = "%s/logFileList_%s.dat" % (self.dir, self.pid)
|
|
cmd = "ls %s | grep '^%s-' | grep '%s$' > %s" % (
|
|
self.dir, self.prefix, self.suffix, logFileList)
|
|
(status, output) = subprocess.getstatusoutput(cmd)
|
|
if status == 0:
|
|
with open(logFileList, "r") as fp:
|
|
filenameList = []
|
|
while True:
|
|
# get real file name
|
|
filename = (fp.readline()).strip()
|
|
if not filename:
|
|
break
|
|
existedResList = filename.split(".")
|
|
if len(existedResList) > 2:
|
|
continue
|
|
(existedPrefix, existedSuffix) = \
|
|
os.path.splitext(filename)
|
|
if existedSuffix != ".log":
|
|
continue
|
|
if len(originalFileName) + 18 != len(filename):
|
|
continue
|
|
timeStamp = filename[-21:-4]
|
|
# check log file name
|
|
if self.is_valid_date(timeStamp):
|
|
pass
|
|
else:
|
|
continue
|
|
filenameList.append(filename)
|
|
|
|
if len(filenameList):
|
|
fileName = max(filenameList)
|
|
self.logFile = self.dir + "/" + fileName.strip()
|
|
FileUtil.createFileInSafeMode(self.logFile)
|
|
self.check_link()
|
|
self.fp = open(self.logFile, "a")
|
|
FileUtil.cleanTmpFile(logFileList)
|
|
return
|
|
|
|
FileUtil.cleanTmpFile(logFileList)
|
|
# create new log file
|
|
self.__openLogFile()
|
|
except Exception as ex:
|
|
FileUtil.cleanTmpFile(logFileList)
|
|
print(str(ex))
|
|
sys.exit(1)
|
|
|
|
def __del__(self):
|
|
"""
|
|
function: Delete tmp file
|
|
input : NA
|
|
output: NA
|
|
"""
|
|
if (self.tmpFile is not None and os.path.isfile(self.tmpFile)):
|
|
if self.moduleName not in PREINSTALL_ACTION:
|
|
# If the moduleName is local action in preinstall,
|
|
# we do not delete the file, preinstall will use it to
|
|
# change path owner later.
|
|
if os.access(self.tmpFile, os.R_OK | os.W_OK):
|
|
os.remove(self.tmpFile)
|
|
|
|
def check_link(self):
|
|
"""
|
|
function: check log file is link
|
|
input : NA
|
|
output: list of
|
|
"""
|
|
if os.path.islink(self.logFile):
|
|
raise Exception(ErrorCode.GAUSS_502["GAUSS_50206"] % self.logFile)
|
|
|
|
def __openLogFile(self):
|
|
"""
|
|
function: open log file
|
|
input : NA
|
|
output: NA
|
|
"""
|
|
try:
|
|
# get current time
|
|
currentTime = time.strftime("%Y-%m-%d_%H%M%S")
|
|
# init log file
|
|
self.logFile = self.dir + "/" + self.prefix + "-" + currentTime \
|
|
+ self.suffix
|
|
# Re-create the log file to add a retry 3 times mechanism,
|
|
# in order to call concurrently between multiple processes
|
|
retryTimes = 3
|
|
count = 0
|
|
while (True):
|
|
(status, output) = self.__createLogFile()
|
|
if status == 0:
|
|
break
|
|
count = count + 1
|
|
time.sleep(1)
|
|
if (count > retryTimes):
|
|
raise Exception(output)
|
|
# open log file
|
|
self.check_link()
|
|
self.fp = open(self.logFile, "a")
|
|
except Exception as e:
|
|
raise Exception(ErrorCode.GAUSS_502["GAUSS_50206"]
|
|
% self.logFile + " Error:\n%s" % str(e))
|
|
|
|
def __createLogFile(self):
|
|
"""
|
|
function: create log file
|
|
input : NA
|
|
output: (status, output)
|
|
"""
|
|
try:
|
|
if (not os.path.exists(self.logFile)):
|
|
os.mknod(self.logFile, ConstantsBase.KEY_FILE_PERMISSION)
|
|
return (0, "")
|
|
except Exception as e:
|
|
return (1, str(e))
|
|
|
|
def is_valid_date(self, datastr):
|
|
"""
|
|
function: Judge if date valid
|
|
input : datastr
|
|
output: bool
|
|
"""
|
|
try:
|
|
time.strptime(datastr, "%Y-%m-%d_%H%M%S")
|
|
return True
|
|
except Exception as ex:
|
|
return False
|
|
|
|
def closeLog(self):
|
|
"""
|
|
function: Function to close log file
|
|
input : NA
|
|
output: NA
|
|
"""
|
|
try:
|
|
if (self.fp):
|
|
self.fp.flush()
|
|
self.fp.close()
|
|
self.fp = None
|
|
except Exception as ex:
|
|
if self.fp:
|
|
self.fp.close()
|
|
raise Exception(str(ex))
|
|
|
|
# print the flow message to console window and log file
|
|
# AddInfo: constant represent step constant, addStep represent step
|
|
# plus, None represent no step
|
|
def log(self, msg, stepFlag=""):
|
|
"""
|
|
function:print the flow message to console window and log file
|
|
input: msg,stepFlag
|
|
control: when stepFlag="", the OM background log does not display
|
|
step information.
|
|
when stepFlag="addStep", the OM background log step will
|
|
add 1.
|
|
when stepFlag="constant", the OM background log step
|
|
defaults to the current step.
|
|
output: NA
|
|
"""
|
|
if (LOG_INFO >= self.expectLevel):
|
|
sanitized_msg = SensitiveMask.mask_pwd(msg)
|
|
print(sanitized_msg)
|
|
self.__writeLog("LOG", sanitized_msg, stepFlag)
|
|
|
|
# print the flow message to log file only
|
|
def debug(self, msg, stepFlag=""):
|
|
"""
|
|
function:print the flow message to log file only
|
|
input: msg,stepFlag
|
|
control: when stepFlag="", the OM background log does not display
|
|
step information.
|
|
when stepFlag="addStep", the OM background log step will
|
|
add 1.
|
|
when stepFlag="constant", the OM background log step
|
|
defaults to the current step.
|
|
output: NA
|
|
"""
|
|
if (LOG_DEBUG >= self.expectLevel):
|
|
sanitized_msg = SensitiveMask.mask_pwd(msg)
|
|
self.__writeLog("DEBUG", sanitized_msg, stepFlag)
|
|
|
|
def warn(self, msg, stepFlag=""):
|
|
"""
|
|
function:print the flow message to log file only
|
|
input: msg,stepFlag
|
|
control: when stepFlag="", the OM background log does not display
|
|
step information.
|
|
when stepFlag="addStep", the OM background log step will
|
|
add 1.
|
|
when stepFlag="constant", the OM background log step
|
|
defaults to the current step.
|
|
output: NA
|
|
"""
|
|
if (LOG_WARNING >= self.expectLevel):
|
|
sanitized_msg = SensitiveMask.mask_pwd(msg)
|
|
print(sanitized_msg)
|
|
self.__writeLog("WARNING", sanitized_msg, stepFlag)
|
|
|
|
# print the error message to console window and log file
|
|
def error(self, msg):
|
|
"""
|
|
function: print the error message to console window and log file
|
|
input : msg
|
|
output: NA
|
|
"""
|
|
if (LOG_ERROR >= self.expectLevel):
|
|
sanitized_msg = SensitiveMask.mask_pwd(msg)
|
|
print(sanitized_msg)
|
|
self.__writeLog("ERROR", sanitized_msg)
|
|
|
|
# print the error message to console window and log file,then exit
|
|
def logExit(self, msg):
|
|
"""
|
|
function: print the error message to console window and log file,
|
|
then exit
|
|
input : msg
|
|
output: NA
|
|
"""
|
|
if (LOG_FATAL >= self.expectLevel):
|
|
sanitized_msg = SensitiveMask.mask_pwd(msg)
|
|
print(sanitized_msg)
|
|
try:
|
|
self.__writeLog("ERROR", sanitized_msg)
|
|
except Exception as ex:
|
|
print(str(ex))
|
|
self.closeLog()
|
|
sys.exit(1)
|
|
|
|
def Step(self, stepFlag):
|
|
"""
|
|
function: return Step number info
|
|
input: add
|
|
output: step number
|
|
"""
|
|
if (stepFlag == "constant"):
|
|
return self.step
|
|
else:
|
|
self.step = self.step + 1
|
|
return self.step
|
|
|
|
def __writeLog(self, level, msg, stepFlag=""):
|
|
"""
|
|
function: Write log to file
|
|
input: level, msg, stepFlag
|
|
output: NA
|
|
"""
|
|
if (self.fp is None):
|
|
return
|
|
|
|
try:
|
|
self.lock.acquire()
|
|
# if the log file does not exits, create it
|
|
if (not os.path.exists(self.logFile)):
|
|
self.__openLogFile()
|
|
else:
|
|
LogPer = oct(os.stat(self.logFile).st_mode)[-3:]
|
|
self.check_link()
|
|
if (not LogPer == "600"):
|
|
os.chmod(self.logFile, ConstantsBase.KEY_FILE_PERMISSION)
|
|
# check if need switch to an new log file
|
|
self.size = os.path.getsize(self.logFile)
|
|
if (self.size >= MAXLOGFILESIZE and os.getuid() != 0):
|
|
self.closeLog()
|
|
self.__openLogFile()
|
|
|
|
replace_reg = re.compile(r'-W[ ]*[^ ]*[ ]*')
|
|
msg = replace_reg.sub('-W *** ', str(msg))
|
|
|
|
if (msg.find("gs_redis") >= 0):
|
|
replace_reg = re.compile(r'-A[ ]*[^ ]*[ ]*')
|
|
msg = replace_reg.sub('-A *** ', str(msg))
|
|
|
|
strTime = datetime.datetime.now()
|
|
if (stepFlag == ""):
|
|
print("[%s][%d][%s][%s]:%s" % (
|
|
strTime, self.pid, self.moduleName, level, msg),
|
|
file=self.fp)
|
|
else:
|
|
stepnum = self.Step(stepFlag)
|
|
print("[%s][%d][%s][%s][Step%d]:%s" % (
|
|
strTime, self.pid, self.moduleName, level, stepnum, msg),
|
|
file=self.fp)
|
|
self.fp.flush()
|
|
self.lock.release()
|
|
except Exception as ex:
|
|
self.lock.release()
|
|
if self.ignoreErr:
|
|
return
|
|
raise Exception(ErrorCode.GAUSS_502["GAUSS_50205"]
|
|
% (("log file %s") % self.logFile) +
|
|
" Error:\n%s" % str(ex))
|
|
|
|
@staticmethod
|
|
def exitWithError(msg, status=1):
|
|
"""
|
|
function: Exit with error message
|
|
input: msg, status=1
|
|
output: NA
|
|
"""
|
|
sys.stderr.write("%s\n" % msg)
|
|
sys.exit(status)
|
|
|
|
@staticmethod
|
|
def printMessage(msg):
|
|
"""
|
|
function: Print the String message
|
|
input: msg
|
|
output: NA
|
|
"""
|
|
sys.stdout.write("%s\n" % msg)
|
|
|
|
|
|
class FormatColor(object):
|
|
"""
|
|
Formatting string for displaying colors on the screen.
|
|
"""
|
|
|
|
def __init__(self):
|
|
"""
|
|
Initialize the format color class.
|
|
"""
|
|
pass
|
|
|
|
@staticmethod
|
|
def withColor(_string, _foregroundColorID, _backgroundColorID=49):
|
|
"""
|
|
Given foreground/background ANSI color codes, return a string that,
|
|
when printed, will format the supplied
|
|
string using the supplied colors.
|
|
|
|
:param _string: The input string.
|
|
:param _foregroundColorID: The foreground color identify number.
|
|
:param _backgroundColorID: The background color identify number.
|
|
|
|
:type _string: str
|
|
:type _foregroundColorID: int
|
|
:type _backgroundColorID: int
|
|
|
|
:return: Return the string with color.
|
|
:rtype: str
|
|
"""
|
|
return "\x1b[%dm\x1b[%dm%s\x1b[39m\x1b[49m" % (
|
|
_foregroundColorID, _backgroundColorID, _string)
|
|
|
|
@staticmethod
|
|
def bold(_string):
|
|
"""
|
|
Returns a string that, when printed, will display the supplied
|
|
string in ANSI bold.
|
|
|
|
:param _string: The input string.
|
|
:type _string: str
|
|
|
|
:return: Return the bold string.
|
|
:rtype: str
|
|
"""
|
|
return "\x1b[1m%s\x1b[22m" % _string
|
|
|
|
@staticmethod
|
|
def default(_string):
|
|
"""
|
|
Get the string with default color.
|
|
|
|
:param _string: The input string.
|
|
:type _string: str
|
|
|
|
:return: Return the string with color.
|
|
:rtype: str
|
|
"""
|
|
return FormatColor.withColor(_string, 30)
|
|
|
|
@staticmethod
|
|
def defaultWithBold(_string):
|
|
"""
|
|
Get the string with default color.
|
|
|
|
:param _string: The input string.
|
|
:type _string: str
|
|
|
|
:return: Return the string with color.
|
|
:rtype: str
|
|
"""
|
|
return FormatColor.default(FormatColor.bold(_string))
|
|
|
|
@staticmethod
|
|
def red(_string):
|
|
"""
|
|
Get the string with red color.
|
|
|
|
:param _string: The input string.
|
|
:type _string: str
|
|
|
|
:return: Return the string with color.
|
|
:rtype: str
|
|
"""
|
|
return FormatColor.withColor(_string, 31)
|
|
|
|
@staticmethod
|
|
def redWithBold(_string):
|
|
"""
|
|
Get the string with red color.
|
|
|
|
:param _string: The input string.
|
|
:type _string: str
|
|
|
|
:return: Return the string with color.
|
|
:rtype: str
|
|
"""
|
|
return FormatColor.red(FormatColor.bold(_string))
|
|
|
|
@staticmethod
|
|
def green(_string):
|
|
"""
|
|
Get the string with green color.
|
|
|
|
:param _string: The input string.
|
|
:type _string: str
|
|
|
|
:return: Return the string with color.
|
|
:rtype: str
|
|
"""
|
|
return FormatColor.withColor(_string, 32)
|
|
|
|
@staticmethod
|
|
def greenWithBold(_string):
|
|
"""
|
|
Get the string with green color.
|
|
|
|
:param _string: The input string.
|
|
:type _string: str
|
|
|
|
:return: Return the string with color.
|
|
:rtype: str
|
|
"""
|
|
return FormatColor.green(FormatColor.bold(_string))
|
|
|
|
@staticmethod
|
|
def yellow(_string):
|
|
"""
|
|
Get the string with yellow color.
|
|
|
|
:param _string: The input string.
|
|
:type _string: str
|
|
|
|
:return: Return the string with color.
|
|
:rtype: str
|
|
"""
|
|
return FormatColor.withColor(_string, 33)
|
|
|
|
@staticmethod
|
|
def yellowWithBold(_string):
|
|
"""
|
|
Get the string with yellow color.
|
|
|
|
:param _string: The input string.
|
|
:type _string: str
|
|
|
|
:return: Return the string with color.
|
|
:rtype: str
|
|
"""
|
|
return FormatColor.yellow(FormatColor.bold(_string))
|
|
|
|
@staticmethod
|
|
def blue(_string):
|
|
"""
|
|
Get the string with blue color.
|
|
|
|
:param _string: The input string.
|
|
:type _string: str
|
|
|
|
:return: Return the string with color.
|
|
:rtype: str
|
|
"""
|
|
return FormatColor.withColor(_string, 34)
|
|
|
|
@staticmethod
|
|
def blueWithBold(_string):
|
|
"""
|
|
Get the string with blue color.
|
|
|
|
:param _string: The input string.
|
|
:type _string: str
|
|
|
|
:return: Return the string with color.
|
|
:rtype: str
|
|
"""
|
|
return FormatColor.blue(FormatColor.bold(_string))
|
|
|
|
@staticmethod
|
|
def magenta(_string):
|
|
"""
|
|
Get the string with magenta color.
|
|
|
|
:param _string: The input string.
|
|
:type _string: str
|
|
|
|
:return: Return the string with color.
|
|
:rtype: str
|
|
"""
|
|
return FormatColor.withColor(_string, 35)
|
|
|
|
@staticmethod
|
|
def magentaWithBold(_string):
|
|
"""
|
|
Get the string with magenta color.
|
|
|
|
:param _string: The input string.
|
|
:type _string: str
|
|
|
|
:return: Return the string with color.
|
|
:rtype: str
|
|
"""
|
|
return FormatColor.magenta(FormatColor.bold(_string))
|
|
|
|
@staticmethod
|
|
def cyan(_string):
|
|
"""
|
|
Get the string with cyan color.
|
|
|
|
:param _string: The input string.
|
|
:type _string: str
|
|
|
|
:return: Return the string with color.
|
|
:rtype: str
|
|
"""
|
|
return FormatColor.withColor(_string, 36)
|
|
|
|
@staticmethod
|
|
def cyanWithBold(_string):
|
|
"""
|
|
Get the string with cyan color.
|
|
|
|
:param _string: The input string.
|
|
:type _string: str
|
|
|
|
:return: Return the string with color.
|
|
:rtype: str
|
|
"""
|
|
return FormatColor.cyan(FormatColor.bold(_string))
|
|
|
|
@staticmethod
|
|
def white(_string):
|
|
"""
|
|
Get the string with white color.
|
|
|
|
:param _string: The input string.
|
|
:type _string: str
|
|
|
|
:return: Return the string with color.
|
|
:rtype: str
|
|
"""
|
|
return FormatColor.withColor(_string, 37)
|
|
|
|
@staticmethod
|
|
def whiteWithBold(_string):
|
|
"""
|
|
Get the string with white color.
|
|
|
|
:param _string: The input string.
|
|
:type _string: str
|
|
|
|
:return: Return the string with color.
|
|
:rtype: str
|
|
"""
|
|
return FormatColor.white(FormatColor.bold(_string))
|
|
|
|
@staticmethod
|
|
def hasColors(_stream):
|
|
"""
|
|
Returns boolean indicating whether or not the supplied stream
|
|
supports ANSI color.
|
|
|
|
:param _stream: The stream instance.
|
|
:type _stream: file | io.TextIOWrapper
|
|
|
|
:return: Return whether the supplied stream supports ANSI color.
|
|
:rtype: bool
|
|
"""
|
|
if not hasattr(_stream, "isatty") or not _stream.isatty():
|
|
return False
|
|
|
|
try:
|
|
# noinspection PyUnresolvedReferences
|
|
import curses as _curses
|
|
except ImportError:
|
|
pass
|
|
else:
|
|
try:
|
|
# Set the current terminal type to the value of the
|
|
# environment variable "TERM".
|
|
_curses.setupterm(None, _stream.fileno())
|
|
|
|
return _curses.tigetnum("colors") > 2
|
|
except _curses.error:
|
|
pass
|
|
|
|
retCode, output = subprocess.getstatusoutput("tput colors")
|
|
if retCode == 0:
|
|
try:
|
|
return int(output.strip()) > 2
|
|
except (NameError, TypeError, EOFError, SyntaxError):
|
|
return False
|
|
else:
|
|
return False
|
|
|
|
@staticmethod
|
|
def screenWidth(_steam):
|
|
"""
|
|
Get the command line interface width.
|
|
|
|
:param: The steam instance, such as sys.stdout.
|
|
:type: file
|
|
|
|
:return: Return the command line interface width.
|
|
:rtype: int
|
|
"""
|
|
try:
|
|
# noinspection PyUnresolvedReferences
|
|
import curses as _curses
|
|
except ImportError:
|
|
pass
|
|
else:
|
|
try:
|
|
_curses.setupterm(None, _steam.fileno())
|
|
|
|
return _curses.tigetnum("cols")
|
|
except _curses.error:
|
|
pass
|
|
|
|
# Ignore the standard error, sometimes it will cause the tput
|
|
# command to return a wrong value.
|
|
retCode, output = subprocess.getstatusoutput("tput cols")
|
|
if retCode == 0:
|
|
try:
|
|
return int(output.strip())
|
|
except (NameError, TypeError, EOFError, SyntaxError):
|
|
return -1
|
|
else:
|
|
return -1
|
|
|
|
|
|
#
|
|
# Progress logger config.
|
|
#
|
|
# Progress Handler Color.
|
|
#
|
|
# The color of the status information.
|
|
PROGRESS_COLOR_RED = "red"
|
|
PROGRESS_COLOR_GREEN = "green"
|
|
PROGRESS_COLOR_YELLOW = "yellow"
|
|
PROGRESS_COLOR_BLUE = "blue"
|
|
PROGRESS_COLOR_MAGENTA = "magenta"
|
|
PROGRESS_COLOR_CYAN = "cyan"
|
|
PROGRESS_COLOR_WHITE = "white"
|
|
PROGRESS_COLOR_DEFAULT = "default"
|
|
# The color with bold of the status information.
|
|
PROGRESS_COLOR_RED_BOLD = "redWithBold"
|
|
PROGRESS_COLOR_GREEN_BOLD = "greenWithBold"
|
|
PROGRESS_COLOR_YELLOW_BOLD = "yellowWithBold"
|
|
PROGRESS_COLOR_BLUE_BOLD = "blueWithBold"
|
|
PROGRESS_COLOR_MAGENTA_BOLD = "magentaWithBold"
|
|
PROGRESS_COLOR_CYAN_BOLD = "cyanWithBold"
|
|
PROGRESS_COLOR_WHITE_BOLD = "whiteWithBold"
|
|
PROGRESS_COLOR_DEFAULT_BOLD = "defaultWithBold"
|
|
PROGRESS_COLOR_MAP = {
|
|
PROGRESS_COLOR_RED: FormatColor.red,
|
|
PROGRESS_COLOR_GREEN: FormatColor.green,
|
|
PROGRESS_COLOR_YELLOW: FormatColor.yellow,
|
|
PROGRESS_COLOR_BLUE: FormatColor.blue,
|
|
PROGRESS_COLOR_MAGENTA: FormatColor.magenta,
|
|
PROGRESS_COLOR_CYAN: FormatColor.cyan,
|
|
PROGRESS_COLOR_WHITE: FormatColor.white,
|
|
PROGRESS_COLOR_DEFAULT: FormatColor.default,
|
|
PROGRESS_COLOR_RED_BOLD: FormatColor.redWithBold,
|
|
PROGRESS_COLOR_GREEN_BOLD: FormatColor.greenWithBold,
|
|
PROGRESS_COLOR_YELLOW_BOLD: FormatColor.yellowWithBold,
|
|
PROGRESS_COLOR_BLUE_BOLD: FormatColor.blueWithBold,
|
|
PROGRESS_COLOR_MAGENTA_BOLD: FormatColor.magentaWithBold,
|
|
PROGRESS_COLOR_CYAN_BOLD: FormatColor.cyanWithBold,
|
|
PROGRESS_COLOR_WHITE_BOLD: FormatColor.whiteWithBold,
|
|
PROGRESS_COLOR_DEFAULT_BOLD: FormatColor.defaultWithBold
|
|
}
|
|
# Default std format.
|
|
_LOGGER_DEFAULT_STD_FORMAT = "%(message)s"
|
|
|
|
|
|
class LogColorFormatter(logging.Formatter, object):
|
|
"""
|
|
Print colour log information in terminal interface.
|
|
|
|
It will only be used in the "StreamHandler" of the logger.
|
|
"""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
"""
|
|
Initialize the formatter class.
|
|
|
|
:param args: The additional for default Formatter class.
|
|
:param kwargs: The additional for default Formatter class.
|
|
|
|
:type _isColorful: bool
|
|
:type args: str | None
|
|
:type kwargs: str | None
|
|
"""
|
|
logging.Formatter.__init__(self, *args, **kwargs)
|
|
|
|
def format(self, _record):
|
|
"""
|
|
Format the specified record as text.
|
|
|
|
:param _record: The log record instance.
|
|
:type _record: _logging.LogRecord
|
|
|
|
:return: Return the formatted string.
|
|
:rtype: str
|
|
"""
|
|
try:
|
|
_record.message = _record.getMessage()
|
|
except Exception as e:
|
|
raise Exception(ErrorCode.GAUSS_500["GAUSS_50009"]
|
|
+ " Exception is %r, format string is %r",
|
|
e, _record.__dict__)
|
|
|
|
if FormatColor.hasColors(sys.stderr):
|
|
if _record.levelname == "DEBUG" or _record.levelno == \
|
|
logging.DEBUG:
|
|
_record.levelname = FormatColor.blue(_record.levelname)
|
|
elif _record.levelname == "INFO" or _record.levelno == \
|
|
logging.INFO:
|
|
_record.levelname = FormatColor.green(_record.levelname)
|
|
elif _record.levelname == "WARNING" or _record.levelno == \
|
|
logging.WARN:
|
|
_record.levelname = FormatColor.yellow(_record.levelname)
|
|
elif _record.levelname == "ERROR" or _record.levelno == \
|
|
logging.ERROR:
|
|
_record.levelname = FormatColor.red(_record.levelname)
|
|
elif _record.levelname == "CRITICAL" or _record.levelno == \
|
|
logging.CRITICAL:
|
|
_record.levelname = FormatColor.redWithBold(_record.levelname)
|
|
|
|
return logging.Formatter.format(self, _record)
|
|
|
|
|
|
class ProgressHandler(logging.StreamHandler, object):
|
|
"""
|
|
Print progress handler.
|
|
|
|
This class was used to print progress information on the screen.
|
|
Status flag can be displayed colorful.
|
|
|
|
For example:
|
|
In a process, the first step is to display it on the screen.
|
|
[STARTING] Starting the cluster.
|
|
Next, the transaction in the process is processed.
|
|
When processing succeed, refresh information on the same line.
|
|
[SUCCESS ] Starting the cluster.
|
|
When processing failure, refresh information on the same line.
|
|
[FAILURE ] Starting the cluster.
|
|
"""
|
|
|
|
def __init__(self):
|
|
"""
|
|
This class was used to print progress information on the screen.
|
|
"""
|
|
logging.StreamHandler.__init__(self, sys.stdout)
|
|
|
|
# The registered status.
|
|
self.__status_list = {} # type: Dict[str, str]
|
|
# Width of middle brackets.
|
|
self.__wide = 0
|
|
# The start flag.
|
|
self._isStart = False
|
|
# The storage flag.
|
|
self._storeMessage = []
|
|
|
|
def registerStatus(self, status, color):
|
|
"""
|
|
Register the status information and the color of the status
|
|
information.
|
|
|
|
Repeated addition of status strings does not refresh status string
|
|
color information
|
|
|
|
:param status: The status information.
|
|
:param color: the color of the status information.
|
|
|
|
:type status: str
|
|
:type color: str
|
|
"""
|
|
if color in PROGRESS_COLOR_MAP.keys():
|
|
handler = PROGRESS_COLOR_MAP.get(color)
|
|
self.__status_list.setdefault(status, handler(status))
|
|
if len(status) > self.__wide:
|
|
self.__wide = len(status)
|
|
|
|
# noinspection PyBroadException
|
|
def emit(self, record):
|
|
"""
|
|
Emit a record.
|
|
|
|
The following progress information can be printed:
|
|
Progress information of start/end type.
|
|
Start the progress: Logger.start(self, message, status =
|
|
None, *args, **kwargs)
|
|
End the progress: Logger.stop(self, message, status =
|
|
None, *args, **kwargs)
|
|
Progress information of current-step/total-step type.
|
|
Progress information of percentage type.
|
|
|
|
:param record: log record instance.
|
|
:type record: logging.LogRecord
|
|
"""
|
|
try:
|
|
# If the current step text does not exists, save it, otherwise
|
|
# delete the saved copy.
|
|
fs = self.format(record)
|
|
# Get the screen width, and clip the string to adapt the screen.
|
|
screenWidth = FormatColor.screenWidth(self.stream)
|
|
|
|
if hasattr(record, "progress_handler_status_start"):
|
|
status = record.progress_handler_status_start
|
|
length = self.__wide - len(status)
|
|
if status in self.__status_list:
|
|
status = self.__status_list[status]
|
|
fs = "[%s] %s" % (status.center(len(status) + length), fs)
|
|
if 0 < screenWidth <= len(fs):
|
|
fs = fs[:screenWidth - 4] + "..."
|
|
|
|
self.stream.write(fs)
|
|
self._isStart = True
|
|
elif hasattr(record, "progress_handler_status_stop"):
|
|
status = record.progress_handler_status_stop
|
|
length = self.__wide - len(status)
|
|
if status in self.__status_list:
|
|
status = self.__status_list[status]
|
|
fs = "\r[%s] %s\n" % (status.center(len(status) + length), fs)
|
|
if 0 < screenWidth <= len(fs):
|
|
fs = fs[:screenWidth - 4] + "...\n"
|
|
|
|
self.stream.write(fs)
|
|
self._isStart = False
|
|
elif self._isStart:
|
|
# Use the other formatter
|
|
formatter = LogColorFormatter(_LOGGER_DEFAULT_STD_FORMAT)
|
|
fs = formatter.format(record) + "\n"
|
|
# Store the message.
|
|
self._storeMessage.append(fs)
|
|
else:
|
|
# Use the other formatter
|
|
formatter = LogColorFormatter(_LOGGER_DEFAULT_STD_FORMAT)
|
|
fs = formatter.format(record) + "\n"
|
|
|
|
self.stream.write(fs)
|
|
|
|
if not self._isStart:
|
|
# Flush the stop message.
|
|
self.flush()
|
|
# Print the warning or error message.
|
|
for message in self._storeMessage:
|
|
self.stream.write(message)
|
|
# Clean the storage message.
|
|
del self._storeMessage[:]
|
|
|
|
self.flush()
|
|
except (KeyboardInterrupt, SystemExit):
|
|
raise
|
|
except BaseException:
|
|
self.handleError(record)
|
|
|
|
|
|
class RotatingFileHandler(_handlers.RotatingFileHandler, object):
|
|
"""
|
|
Handler for logging to a set of files, which switches from one file to
|
|
the next when the current file reaches
|
|
a certain size.
|
|
"""
|
|
|
|
def __init__(self, filename, mode='a', maxBytes=0, backupCount=0,
|
|
encoding=None, delay=False):
|
|
"""
|
|
Open the specified file and use it as the stream for logging.
|
|
|
|
By default, the file grows indefinitely. You can specify particular
|
|
values of maxBytes and backupCount to allow the file to rollover at
|
|
a predetermined size.
|
|
|
|
Rollover occurs whenever the current log file is nearly maxBytes in
|
|
length. If backupCount is >= 1, the system will successively create
|
|
new files with the same pathname as the base file, but with extensions
|
|
".1", ".2" etc. appended to it. For example, with a backupCount of 5
|
|
and a base file name of "app.log", you would get "app.log",
|
|
"app.log.1", "app.log.2", ... through to "app.log.5". The file being
|
|
written to is always "app.log" - when it gets filled up, it is closed
|
|
and renamed to "app.log.1", and if files "app.log.1", "app.log.2" etc.
|
|
exist, then they are renamed to "app.log.2", "app.log.3" etc.
|
|
respectively.
|
|
|
|
If maxBytes is zero, rollover never occurs.
|
|
|
|
:param filename: The base file name for the logger.
|
|
:param mode: The file open mode, default is "a".
|
|
:param maxBytes: The max size of the log file.
|
|
:param backupCount: The back up count of the log file.
|
|
:param encoding: The encoding type of the log file.
|
|
:param delay: Whether to delay to open the file.
|
|
|
|
:type filename: str
|
|
:type mode: str
|
|
:type maxBytes: int
|
|
:type backupCount: int
|
|
:type encoding: str | None
|
|
:type delay: bool
|
|
"""
|
|
# Store the base file name before the parent initialization.
|
|
self.baseFilename = filename.strip() if str else filename
|
|
# The real log file name is equal with the base log file name.
|
|
self._currentFileName = self.baseFilename
|
|
|
|
# Check the log file list, get the last log file.
|
|
self.__getNewestFile()
|
|
|
|
_handlers.RotatingFileHandler.__init__(self, filename, mode, maxBytes,
|
|
backupCount, encoding, delay)
|
|
|
|
def doRollover(self):
|
|
"""
|
|
Do a rollover, as described in __init__().
|
|
"""
|
|
# Close the output stream.
|
|
if self.stream:
|
|
self.stream.close()
|
|
self.stream = None
|
|
|
|
# Change the log file.
|
|
self.__getNewestFile()
|
|
|
|
# Open an new output stream.
|
|
if sys.version_info <= (2, 6) or not self.delay:
|
|
self.stream = self._open()
|
|
|
|
def _open(self):
|
|
"""
|
|
Open the file stream.
|
|
|
|
:return: Return the file descriptor.
|
|
:rtype: file
|
|
"""
|
|
# Get the log dir.
|
|
logDir = os.path.dirname(self.baseFilename)
|
|
# Check whether the log file directory exist.
|
|
try:
|
|
if not os.path.exists(logDir):
|
|
# Create the log dir.
|
|
os.makedirs(logDir, ConstantsBase.KEY_DIRECTORY_PERMISSION)
|
|
except OSError:
|
|
raise Exception(ErrorCode.GAUSS_502["GAUSS_50208"] % logDir)
|
|
|
|
# Open the file.
|
|
if self.encoding is None:
|
|
stream = open(self._currentFileName, self.mode)
|
|
else:
|
|
stream = codecs.open(self._currentFileName, self.mode,
|
|
self.encoding)
|
|
|
|
return stream
|
|
|
|
def __getNewestFile(self):
|
|
"""
|
|
Get the newest log file.
|
|
|
|
:rtype: None
|
|
"""
|
|
|
|
def getNewFileName(_filePath):
|
|
"""
|
|
Get the real file name from the base file name.
|
|
|
|
:param _filePath: The input base file path.
|
|
:type _filePath: str
|
|
|
|
:return: Return the real file name from the base file name.
|
|
:rtype: str
|
|
"""
|
|
# Get tht log directory.
|
|
_dirName = os.path.dirname(_filePath)
|
|
_fileName = os.path.basename(_filePath)
|
|
|
|
# Get the prefix and the suffix.
|
|
_prefix, _suffix = os.path.splitext(os.path.basename(_fileName))
|
|
|
|
# Get the new file name.
|
|
_newFileName = "%(_prefix)s_%(timeStamp)s%(suffix)s" \
|
|
% {"_prefix": _prefix.lower(),
|
|
"timeStamp": time.strftime("%Y-%m-%d_%H%M%S"),
|
|
"suffix": _suffix}
|
|
return os.path.join(_dirName, _newFileName)
|
|
|
|
# Initialize the file log handler.
|
|
if self._currentFileName != self.baseFilename:
|
|
self._currentFileName = getNewFileName(self.baseFilename)
|
|
return
|
|
|
|
dirName = os.path.dirname(self.baseFilename)
|
|
# If the log file path does not exist, create it, and generate the
|
|
# real log file name.
|
|
try:
|
|
# Check whether the log file directory exist.
|
|
if not os.path.exists(dirName):
|
|
# Create the log dir.
|
|
os.makedirs(dirName, ConstantsBase.KEY_DIRECTORY_PERMISSION)
|
|
# Save the real log file name.
|
|
self._currentFileName = getNewFileName(self.baseFilename)
|
|
return
|
|
except OSError:
|
|
raise _OmError(ErrorCode.GAUSS_502["GAUSS_50208"] % dirName)
|
|
|
|
# Get the prefix and the suffix.
|
|
prefix, suffix = os.path.splitext(os.path.basename(self.baseFilename))
|
|
# The file name list file.
|
|
fileList = "%(dirName)s/logFileList_%(pid)s.dat" % {"dirName": dirName,
|
|
"pid": os.getpid()}
|
|
try:
|
|
with open(fileList, "w") as fp:
|
|
try:
|
|
for filename in os.listdir(dirName):
|
|
if re.match(prefix + "-", filename) and \
|
|
re.search(suffix + "$", filename):
|
|
fp.write(filename + '\n')
|
|
except OSError:
|
|
raise Exception(ErrorCode.GAUSS_502["GAUSS_50208"],
|
|
dirName)
|
|
except Exception as e:
|
|
raise Exception("open %s in 'w' mode err." % fileList)
|
|
# Get the file list.
|
|
with open(fileList, "r") as fp:
|
|
lines = [line.strip() for line in fp.readlines() if
|
|
line and line.strip()]
|
|
|
|
# Remove the file list file.
|
|
if os.path.exists(fileList):
|
|
os.remove(fileList)
|
|
|
|
fileNameList = []
|
|
for fileName in lines:
|
|
# Get the matched file.
|
|
pattern = r"%(prefix)s-(%(timeStamp)s)%(suffix)s" \
|
|
% {"prefix": prefix,
|
|
"timeStamp": "\\d{4}-\\d{2}-\\d{2}_\\d{6}",
|
|
"suffix": suffix}
|
|
match = re.match(pattern, fileName)
|
|
if match:
|
|
timeStamp = match.groups()[0]
|
|
|
|
# Check whether the time stamp is valid.
|
|
# noinspection PyBroadException
|
|
try:
|
|
time.strptime(timeStamp, "%Y-%m-%d_%H%M%S")
|
|
except Exception:
|
|
print("Time stamp %s type error." % timeStamp)
|
|
continue
|
|
else:
|
|
continue
|
|
|
|
# Add to file list.
|
|
fileNameList.append(fileName)
|
|
|
|
# If the log directory does not contain the specified log file.
|
|
if not fileNameList:
|
|
self._currentFileName = getNewFileName(self.baseFilename)
|
|
return
|
|
|
|
# Get the newest log file.
|
|
fileName = os.path.join(dirName, max(fileNameList))
|
|
if os.path.getsize(fileName) >= MAXLOGFILESIZE:
|
|
self._currentFileName = getNewFileName(self.baseFilename)
|
|
else:
|
|
self._currentFileName = fileName
|
|
|
|
|
|
class _Logger(logging.Logger, object):
|
|
"""
|
|
The logger class, expected to replace GaussLog class.
|
|
|
|
Inheriting the "object" class for adapting Python 2.6.
|
|
"""
|
|
|
|
def __init__(self, name, level=logging.NOTSET):
|
|
"""
|
|
Init the logger class.
|
|
|
|
:param name: The logger name.
|
|
:param level: The default logger level.
|
|
|
|
:type name: str
|
|
:type level: int
|
|
"""
|
|
logging.Logger.__init__(self, name, level)
|
|
|
|
def start(self, message, status=None, *args, **kwargs):
|
|
"""
|
|
Start a new progress.
|
|
|
|
Logger does not check whether progress is over, It requires the user
|
|
to control the beginning and end of the
|
|
progress.
|
|
|
|
:param message: Progress messages that need to be recorded.
|
|
:param status: Progress status information.
|
|
:param args: A list of parameters for formatting into progress
|
|
messages.
|
|
:param kwargs: Include two parameters: "exc_info" and "extra".
|
|
exc_info: Exception information.
|
|
extra: Additional parameters will be used to initialize log
|
|
record instance.
|
|
|
|
:type message: str
|
|
:type status: str
|
|
:type args: *
|
|
:type kwargs: *
|
|
"""
|
|
kwargs.setdefault("extra", {})
|
|
kwargs["extra"].setdefault("progress_handler", True)
|
|
if status is not None and self.hasHandler(ProgressHandler):
|
|
kwargs["extra"].setdefault("progress_handler_status_start", status)
|
|
|
|
self.info(message, *args, **kwargs)
|
|
|
|
def stop(self, message, status=None, *args, **kwargs):
|
|
"""
|
|
End a progress.
|
|
|
|
Logger does not check whether progress is over, It requires the user
|
|
to control the beginning and end of the
|
|
progress.
|
|
|
|
:param message: Progress messages that need to be recorded.
|
|
:param status: Progress status information.
|
|
:param args: A list of parameters for formatting into progress
|
|
messages.
|
|
:param kwargs: Include two parameters: "exc_info" and "extra".
|
|
exc_info: Exception information.
|
|
extra: Additional parameters will be used to initialize log
|
|
record instance.
|
|
|
|
:type message: str
|
|
:type status: str
|
|
:type args: *
|
|
:type kwargs: *
|
|
"""
|
|
kwargs.setdefault("extra", {})
|
|
kwargs["extra"].setdefault("progress_handler", False)
|
|
if status is not None and self.hasHandler(ProgressHandler):
|
|
kwargs["extra"].setdefault("progress_handler_status_stop", status)
|
|
|
|
self.info(message, *args, **kwargs)
|
|
|
|
def hasHandler(self, handler_type):
|
|
"""
|
|
Check whether the list contains a handler instance of the specified
|
|
type.
|
|
|
|
:param handler_type: The type of handler.
|
|
:type handler_type: type
|
|
|
|
:return: If the list contains a handler instance of specified type.
|
|
"""
|
|
for handler in self.handlers:
|
|
if handler.__class__ == handler_type:
|
|
return True
|
|
|
|
return False
|
|
|
|
def addHandler(self, hdlr):
|
|
"""
|
|
Add a new log handler.
|
|
|
|
StreamHandler and ProgressHandler are conflict.
|
|
StreamHandler will be overwritten by ProgressHandler, but not the
|
|
reverse.
|
|
The instance of progressHandler is unique in a logger instance.
|
|
|
|
:param hdlr: log handler instance
|
|
:type hdlr: ProgressHandler | _logging.Handler
|
|
"""
|
|
for i in range(len(self.handlers) - 1, -1, -1):
|
|
handler = self.handlers[i]
|
|
|
|
# StreamHandler will be overwritten by ProgressHandler.
|
|
if handler.__class__ == logging.StreamHandler and hdlr.__class__ \
|
|
== ProgressHandler:
|
|
self.removeHandler(handler)
|
|
# If ProgressHandler instance is exist, StreamHandler instance
|
|
# will not be inserted into the list.
|
|
elif handler.__class__ == ProgressHandler and hdlr.__class__ == \
|
|
logging.StreamHandler:
|
|
return
|
|
|
|
# call the parent function.
|
|
logging.Logger.addHandler(self, hdlr)
|
|
|
|
def findCaller(self, stack_info=False):
|
|
"""
|
|
Find the stack frame of the caller so that we can note the source
|
|
file name, line number and function name.
|
|
|
|
:rtype: (str, int, str) | (str, int, str, str)
|
|
"""
|
|
f = logging.currentframe()
|
|
|
|
# On some versions of IronPython, currentframe() returns None if
|
|
# IronPython isn't run with -X:Frames.
|
|
if f is not None:
|
|
f = f.f_back
|
|
|
|
rv = "(unknown file)", 0, "(unknown function)"
|
|
while hasattr(f, "f_code"):
|
|
co = f.f_code
|
|
filename = os.path.normcase(co.co_filename)
|
|
# noinspection PyProtectedMember
|
|
if filename in [logging._srcfile, _srcfile]:
|
|
f = f.f_back
|
|
continue
|
|
|
|
if sys.version_info[:2] >= (3, 0):
|
|
sInfo = None
|
|
if stack_info:
|
|
sio = io.StringIO()
|
|
sio.write('Stack (most recent call last):\n')
|
|
traceback.print_stack(f, file=sio)
|
|
sInfo = sio.getvalue()
|
|
if sInfo[-1] == '\n':
|
|
sInfo = sInfo[:-1]
|
|
|
|
rv = (co.co_filename, f.f_lineno, co.co_name, sInfo)
|
|
else:
|
|
rv = (co.co_filename, f.f_lineno, co.co_name)
|
|
break
|
|
|
|
return rv
|
|
|
|
def callHandlers(self, record):
|
|
"""
|
|
Call the log processing routine to process log information.
|
|
|
|
:param record: Log record instance.
|
|
:type record: logging.LogRecord
|
|
"""
|
|
c = self
|
|
found = 0
|
|
while c:
|
|
for hdlr in c.handlers:
|
|
found += 1
|
|
if record.levelno >= hdlr.level and isinstance(
|
|
hdlr, ProgressHandler):
|
|
hdlr.handle(record)
|
|
elif record.levelno >= hdlr.level and not \
|
|
isinstance(hdlr, ProgressHandler) and \
|
|
(not hasattr(record, "progress_handler") or
|
|
getattr(record, "progress_handler") is True):
|
|
hdlr.handle(record)
|
|
if not c.propagate:
|
|
c = None
|
|
else:
|
|
c = c.parent
|
|
|
|
if found == 0:
|
|
raise Exception(ErrorCode.GAUSS_500["GAUSS_50015"]
|
|
% "You have to register at lease one "
|
|
"handler for the logger")
|
|
|
|
|
|
class Logger(object):
|
|
"""
|
|
Logger management class.
|
|
|
|
The main functions are as follows:
|
|
Initialize a unique logger.
|
|
addStreamHandler Add a command line interface recording routine.
|
|
addProgressHandler Add a progress information printed routine.
|
|
addFileHandler Add a file recording routine.
|
|
addFileErrorHandler Add a file error-recording routine.
|
|
addSyslogHandler Add a syslog recording routine.
|
|
getLogger Get the unique logger instance.
|
|
setLevel Set global log level.
|
|
"""
|
|
# Logger management instance.
|
|
__instance = None
|
|
|
|
# Logger level.
|
|
# Debug level.
|
|
DEBUG = logging.DEBUG
|
|
# Info level.
|
|
INFO = logging.INFO
|
|
# Warning level.
|
|
WARN = logging.WARN
|
|
WARNING = logging.WARNING
|
|
# Error level.
|
|
ERROR = logging.ERROR
|
|
# Fatal level.
|
|
FATAL = logging.FATAL
|
|
CRITICAL = logging.CRITICAL
|
|
# log level not set.
|
|
NOTSET = logging.NOTSET
|
|
# log level list.
|
|
# noinspection PyProtectedMember
|
|
LOG_LEVEL_LIST = [level for level in logging._nameToLevel.keys() if
|
|
isinstance(level, str)]
|
|
|
|
# The default logger format.
|
|
# %(name)s Name of the logger (logging channel)
|
|
# %(levelno)s Numeric logging level for the message (DEBUG,
|
|
# INFO, WARNING, ERROR, CRITICAL)
|
|
# %(levelname)s Text logging level for the message ("DEBUG",
|
|
# "INFO", "WARNING", "ERROR", "CRITICAL")
|
|
# %(pathname)s Full pathname of the source file where the
|
|
# logging call was issued (if available)
|
|
# %(filename)s Filename portion of pathname
|
|
# %(module)s Module (name portion of filename)
|
|
# %(lineno)d Source line number where the logging call was
|
|
# issued (if available)
|
|
# %(funcName)s Function name
|
|
# %(created)f Time when the LogRecord was created (time.time()
|
|
# return value)
|
|
# %(asctime)s Textual time when the LogRecord was created
|
|
# %(msecs)d Millisecond portion of the creation time
|
|
# %(relativeCreated)d Time in milliseconds when the LogRecord was
|
|
# created, relative to the time the logging module
|
|
# was loaded (typically at application startup time)
|
|
# %(thread)d Thread ID (if available)
|
|
# %(threadName)s Thread name (if available)
|
|
# %(process)d Process ID (if available)
|
|
# %(message)s The result of record.getMessage(), computed just
|
|
# as the record is emitted
|
|
LOGGER_DEFAULT_STD_FORMAT = _LOGGER_DEFAULT_STD_FORMAT
|
|
LOGGER_DEFAULT_FORMAT = "%(asctime)s %(module)s [%(levelname)s] %(" \
|
|
"message)s"
|
|
|
|
def __init__(self):
|
|
"""
|
|
Create logger management class.
|
|
|
|
Initialization is performed only when the logger management instance
|
|
is first initialized.
|
|
"""
|
|
if Logger.__instance is None:
|
|
# set the unique logger manager instance.
|
|
Logger.__instance = self
|
|
|
|
# Set default logger class to support ProgressHandler.
|
|
logging.setLoggerClass(_Logger)
|
|
|
|
# set the unique logger instance.
|
|
self._logger = logging.getLogger(os.path.basename(sys.argv[0]))
|
|
|
|
# set the default loglevel to info.
|
|
Logger.setLevel(Logger.INFO)
|
|
# add a default stream handler.
|
|
Logger.addStreamHandler()
|
|
|
|
def __new__(cls):
|
|
"""
|
|
Used to create a single instance class.
|
|
|
|
:return: Returns an uninitialized logger manager instance.
|
|
If the logger manager instance is exist, return it, then we will
|
|
not re-init it.
|
|
If the logger manager instance is not exist, return a new object
|
|
instance, then we will init it to a logger
|
|
manager instance.
|
|
:rtype: Logger
|
|
"""
|
|
if cls.__instance is None:
|
|
return object.__new__(cls)
|
|
else:
|
|
return cls.__instance
|
|
|
|
@staticmethod
|
|
def addStreamHandler(loglevel=INFO, fmt=None, date_fmt=None):
|
|
"""
|
|
Add a command line interface recording routine.
|
|
|
|
:param loglevel: Log level.
|
|
:param fmt: Log formatting type.
|
|
:param date_fmt: Date formatting type.
|
|
|
|
:type loglevel: int
|
|
:type fmt: basestring
|
|
:type date_fmt: basestring
|
|
"""
|
|
if fmt is None:
|
|
fmt = Logger.LOGGER_DEFAULT_STD_FORMAT
|
|
|
|
# create formatter.
|
|
formatter = LogColorFormatter(fmt, date_fmt)
|
|
|
|
# create stream handler
|
|
stream = logging.StreamHandler()
|
|
stream.setLevel(loglevel)
|
|
stream.setFormatter(formatter)
|
|
|
|
# add stream handler to logger instance.
|
|
logger = Logger.getLogger()
|
|
if not logger.hasHandler(logging.StreamHandler):
|
|
logger.addHandler(stream)
|
|
|
|
@staticmethod
|
|
def addProgressHandler():
|
|
"""
|
|
Add a progress information printed routine.
|
|
"""
|
|
# create formatter.
|
|
formatter = logging.Formatter("%(message)s")
|
|
|
|
# create progress handler. Default log level is info.
|
|
progress = ProgressHandler()
|
|
progress.setFormatter(formatter)
|
|
progress.setLevel(Logger.INFO)
|
|
|
|
# add progress handler to logger instance.
|
|
logger = Logger.getLogger()
|
|
# ProgressHandler instance is unique.
|
|
if not logger.hasHandler(ProgressHandler):
|
|
logger.addHandler(progress)
|
|
|
|
@staticmethod
|
|
def addFileHandler(filename, logLevel=DEBUG, fmt=LOGGER_DEFAULT_FORMAT,
|
|
date_fmt=None, mode='a',
|
|
maxBytes=MAXLOGFILESIZE, encoding=None, delay=False):
|
|
"""
|
|
Add a file recording routine.
|
|
|
|
:param filename: The log file path.
|
|
:param logLevel: Log level.
|
|
:param fmt: Log formatting type.
|
|
:param date_fmt: Date formatting type.
|
|
:param mode: Open mode of file.
|
|
:param maxBytes: The max size of the log file.
|
|
:param encoding: Encoding format of file.
|
|
:param delay: Whether to delay to open the file.
|
|
|
|
:type filename: str
|
|
:type logLevel: int | str
|
|
:type fmt: str | None
|
|
:type date_fmt: str | None
|
|
:type mode: str
|
|
:type maxBytes: int
|
|
:type encoding: str | None
|
|
:type delay: bool
|
|
"""
|
|
if fmt is None:
|
|
fmt = Logger.LOGGER_DEFAULT_FORMAT
|
|
|
|
# create a formatter.
|
|
formatter = logging.Formatter(fmt, date_fmt)
|
|
|
|
# create a file handler
|
|
file_handler = RotatingFileHandler(filename, mode, maxBytes,
|
|
encoding=encoding, delay=delay)
|
|
file_handler.setLevel(logLevel)
|
|
file_handler.setFormatter(formatter)
|
|
|
|
# add file handler to the logger instance.
|
|
logger = Logger.getLogger()
|
|
logger.addHandler(file_handler)
|
|
|
|
@staticmethod
|
|
def getLogger():
|
|
"""
|
|
Get the unique logger instance.
|
|
|
|
:return: return the logger instance.
|
|
:rtype: _Logger
|
|
"""
|
|
return Logger()._logger
|
|
|
|
@staticmethod
|
|
def setLevel(loglevel):
|
|
"""
|
|
Set global log level.
|
|
|
|
:param loglevel: Log level.
|
|
:type loglevel: int | str
|
|
|
|
:return: Return whether the log level is legal.
|
|
:rtype: bool
|
|
"""
|
|
logger = Logger.getLogger()
|
|
|
|
# noinspection PyProtectedMember
|
|
if isinstance(loglevel,
|
|
str) and loglevel.upper() in logging._levelToName:
|
|
logger.setLevel(loglevel)
|
|
return True
|
|
elif isinstance(loglevel, int) and loglevel in logging._levelToName:
|
|
logger.setLevel(loglevel)
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
@staticmethod
|
|
def setFormat(_fmt, _date_fmt=None):
|
|
"""
|
|
Setting the log format and the date format.
|
|
|
|
:param _fmt: Log formatting type.
|
|
:param _date_fmt: Date formatting type.
|
|
|
|
:type _fmt: str
|
|
:type _date_fmt: str | None
|
|
"""
|
|
logger = Logger.getLogger()
|
|
for handler in logger.handlers:
|
|
# Process handler can not accept the other type of the print
|
|
# format.
|
|
# create a formatter.
|
|
formatter = logging.Formatter(_fmt, _date_fmt)
|
|
# Set the formatter.
|
|
handler.setFormatter(formatter)
|
|
|
|
@staticmethod
|
|
def registerProgressStatus(status, color):
|
|
"""
|
|
Register the status information and the color of the status
|
|
information, which is used to progress handler.
|
|
|
|
Repeated addition of status strings does not refresh status string
|
|
color information.
|
|
|
|
:param status: The status information.
|
|
:param color: the color of the status information.
|
|
|
|
:type status: str
|
|
:type color: str
|
|
"""
|
|
logger = Logger.getLogger()
|
|
|
|
for handler in logger.handlers:
|
|
if isinstance(handler, ProgressHandler):
|
|
handler.registerStatus(status, color)
|
|
|
|
|