logging with colors

This commit is contained in:
debugtalk
2018-02-21 12:32:14 +08:00
parent 967cfeeb8f
commit 7c2b9a6eaa
10 changed files with 153 additions and 84 deletions

View File

@@ -1,18 +1,18 @@
import argparse
import logging
import multiprocessing
import os
import sys
import unittest
from collections import OrderedDict
from httprunner import __version__ as hrun_version
from httprunner.utils import create_scaffold, print_output, string_type
from pyunitreport import __version__ as pyu_version
from pyunitreport import HTMLTestRunner
from . import __version__ as hrun_version
from . import logger
from .exception import TestcaseNotFound
from .task import Result, TaskSuite
from .utils import create_scaffold, print_output, string_type
def run_suite_path(path, mapping=None, runner=None):
@@ -59,21 +59,13 @@ def main_hrun():
help="Specify new project name.")
args = parser.parse_args()
logger.setup_logger(args.log_level)
if args.version:
print("HttpRunner version: {}".format(hrun_version))
print("PyUnitReport version: {}".format(pyu_version))
logger.color_print("HttpRunner version: {}".format(hrun_version), "GREEN")
logger.color_print("PyUnitReport version: {}".format(pyu_version), "GREEN")
exit(0)
log_level = getattr(logging, args.log_level.upper(), None)
if not log_level:
raise ValueError('Invalid log level: %s' % args.log_level)
logging.basicConfig(level=log_level)
if log_level >= 20:
# hide traceback when log level is INFO/WARNING/ERROR/CRITICAL
sys.tracebacklimit = 0
project_name = args.startproject
if project_name:
project_path = os.path.join(os.getcwd(), project_name)
@@ -93,14 +85,14 @@ def main_hrun():
def main_locust():
""" Performance test with locust: parse command line options and run commands.
"""
logging.basicConfig(level="INFO")
logger.setup_logger("INFO")
try:
from httprunner import locusts
except ImportError:
msg = "Locust is not installed, install first and try again.\n"
msg += "install command: pip install locustio"
logging.info(msg)
logger.log_warning(msg)
exit(1)
sys.argv[0] = 'locust'
@@ -115,7 +107,7 @@ def main_locust():
testcase_index = sys.argv.index('-f') + 1
assert testcase_index < len(sys.argv)
except (ValueError, AssertionError):
logging.error("Testcase file is not specified, exit.")
logger.log_error("Testcase file is not specified, exit.")
sys.exit(1)
testcase_file_path = sys.argv[testcase_index]
@@ -125,7 +117,7 @@ def main_locust():
""" locusts -f locustfile.py --cpu-cores 4
"""
if "--no-web" in sys.argv:
logging.error("conflict parameter args: --cpu-cores & --no-web. \nexit.")
logger.log_error("conflict parameter args: --cpu-cores & --no-web. \nexit.")
sys.exit(1)
cpu_cores_index = sys.argv.index('--cpu-cores')
@@ -137,7 +129,7 @@ def main_locust():
locusts -f locustfile.py --cpu-cores
"""
cpu_cores_num_value = multiprocessing.cpu_count()
logging.warning("cpu cores number not specified, use {} by default.".format(cpu_cores_num_value))
logger.log_warning("cpu cores number not specified, use {} by default.".format(cpu_cores_num_value))
else:
try:
""" locusts -f locustfile.py --cpu-cores 4 """
@@ -146,7 +138,7 @@ def main_locust():
except ValueError:
""" locusts -f locustfile.py --cpu-cores -P 8888 """
cpu_cores_num_value = multiprocessing.cpu_count()
logging.warning("cpu cores number not specified, use {} by default.".format(cpu_cores_num_value))
logger.log_warning("cpu cores number not specified, use {} by default.".format(cpu_cores_num_value))
sys.argv.pop(cpu_cores_index)
locusts.run_locusts_on_cpu_cores(sys.argv, cpu_cores_num_value)

View File

@@ -1,15 +1,16 @@
import json
import logging
import re
import time
import requests
import urllib3
from httprunner.exception import ParamsError
from requests import Request, Response
from requests.exceptions import (InvalidSchema, InvalidURL, MissingSchema,
RequestException)
from . import logger
from .exception import ParamsError
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
absolute_http_url_regexp = re.compile(r"^https?://", re.I)
@@ -99,8 +100,8 @@ class HttpSession(requests.Session):
# prepend url with hostname unless it's already an absolute URL
url = self._build_url(url)
logging.info(" Start to {method} {url}".format(method=method, url=url))
logging.debug(" kwargs: {kwargs}".format(kwargs=kwargs))
logger.log_info("{method} {url}".format(method=method, url=url))
logger.log_debug("request kwargs: {kwargs}".format(kwargs=kwargs))
# store meta data that is used when reporting the request to locust's statistics
request_meta = {}
@@ -136,16 +137,17 @@ class HttpSession(requests.Session):
request_meta["response_headers"] = response.headers
request_meta["response_content"] = response.content
logging.debug(" response: {response}".format(response=request_meta))
logger.log_debug("response status_code: {}".format(response.status_code))
logger.log_debug("response headers: {}".format(response.headers))
logger.log_debug("response body: {}".format(response.text))
try:
response.raise_for_status()
except RequestException as e:
logging.error(u" Failed to {method} {url}! exception msg: {exception}".format(
method=method, url=url, exception=str(e)))
logger.log_error(u"{exception}".format(exception=str(e)))
else:
logging.info(
""" status_code: {}, response_time: {} ms, response_length: {} bytes"""\
logger.log_info(
"""status_code: {}, response_time: {} ms, response_length: {} bytes"""\
.format(request_meta["status_code"], request_meta["response_time"], \
request_meta["content_size"]))

View File

@@ -3,9 +3,11 @@ import multiprocessing
import os
import sys
from httprunner.testcase import load_test_file
from locust.main import main
from .logger import color_print
from .testcase import load_test_file
def parse_locustfile(file_path):
""" parse testcase file and return locustfile path.
@@ -13,7 +15,7 @@ def parse_locustfile(file_path):
if file_path is a YAML/JSON file, convert it to locustfile
"""
if not os.path.isfile(file_path):
print("file path invalid, exit.")
color_print("file path invalid, exit.", "RED")
sys.exit(1)
file_suffix = os.path.splitext(file_path)[1]
@@ -23,7 +25,7 @@ def parse_locustfile(file_path):
locustfile_path = gen_locustfile(file_path)
else:
# '' or other suffix
print("file type should be YAML/JSON/Python, exit.")
color_print("file type should be YAML/JSON/Python, exit.", "RED")
sys.exit(1)
return locustfile_path

63
httprunner/logger.py Normal file
View File

@@ -0,0 +1,63 @@
import logging
import sys
from colorama import Back, Fore, Style, init
from colorlog import ColoredFormatter
init(autoreset=True)
log_colors_config = {
'DEBUG': 'cyan',
'INFO': 'green',
'WARNING': 'yellow',
'ERROR': 'red',
'CRITICAL': 'red',
}
def setup_logger(log_level):
"""setup root logger with ColoredFormatter."""
level = getattr(logging, log_level.upper(), None)
if not level:
color_print("Invalid log level: %s" % log_level, "RED")
sys.exit(1)
# hide traceback when log level is INFO/WARNING/ERROR/CRITICAL
if level >= logging.INFO:
sys.tracebacklimit = 0
formatter = ColoredFormatter(
"%(log_color)s%(bg_white)s%(levelname)-8s%(reset)s %(message)s",
datefmt=None,
reset=True,
log_colors=log_colors_config
)
handler = logging.StreamHandler()
handler.setFormatter(formatter)
logging.root.addHandler(handler)
logging.root.setLevel(level)
def coloring(text, color="WHITE"):
fore_color = getattr(Fore, color.upper())
return fore_color + text
def color_print(msg, color="WHITE"):
fore_color = getattr(Fore, color.upper())
print(fore_color + msg)
def log_with_color(level):
""" log with color by different level
"""
def wrapper(text):
color = log_colors_config[level.upper()]
getattr(logging, level.lower())(coloring(text, color))
return wrapper
log_debug = log_with_color("debug")
log_info = log_with_color("info")
log_warning = log_with_color("warning")
log_error = log_with_color("error")
log_critical = log_with_color("critical")

View File

@@ -1,10 +1,10 @@
import logging
import re
from collections import OrderedDict
from httprunner import exception, utils, testcase
from requests.structures import CaseInsensitiveDict
from . import exception, logger, testcase, utils
text_extractor_regexp_compile = re.compile(r".*\(.*\).*")
@@ -42,10 +42,10 @@ class ResponseObject(object):
"""
matched = re.search(field, self.resp_text)
if not matched:
err_msg = u"Extractor error: failed to extract data with regex!\n"
err_msg = u"Failed to extract data with regex!\n"
err_msg += u"response body: {}\n".format(self.resp_text)
err_msg += u"regex: {}\n".format(field)
logging.error(err_msg)
logger.log_error(err_msg)
raise exception.ParamsError(err_msg)
return matched.group(1)
@@ -75,10 +75,10 @@ class ResponseObject(object):
if sub_query:
if not isinstance(top_query_content, (dict, CaseInsensitiveDict, list)):
err_msg = u"Extractor error: failed to extract data with regex!\n"
err_msg = u"Failed to extract data with regex!\n"
err_msg += u"response: {}\n".format(self.parsed_dict())
err_msg += u"regex: {}\n".format(field)
logging.error(err_msg)
logger.log_error(err_msg)
raise exception.ParamsError(err_msg)
# e.g. key: resp_headers_content_type, sub_query = "content-type"
@@ -91,7 +91,7 @@ class ResponseObject(object):
err_msg = u"Failed to extract value from response!\n"
err_msg += u"response: {}\n".format(self.parsed_dict())
err_msg += u"extract field: {}\n".format(field)
logging.error(err_msg)
logger.log_error(err_msg)
raise exception.ParamsError(err_msg)
def extract_field(self, field):

View File

@@ -1,9 +1,8 @@
import logging
from unittest.case import SkipTest
from httprunner import exception, response, testcase, utils
from httprunner.client import HttpSession
from httprunner.context import Context
from . import exception, logger, response, testcase, utils
from .client import HttpSession
from .context import Context
class Runner(object):
@@ -156,12 +155,20 @@ class Runner(object):
try:
self.context.validate(validators, resp_obj)
except (exception.ParamsError, exception.ResponseError, exception.ValidationError):
err_msg = u"Exception occured.\n"
err_msg += u"HTTP request url: {}\n".format(url)
err_msg += u"HTTP request kwargs: {}\n".format(parsed_request)
err_msg += u"HTTP response status_code: {}\n".format(resp.status_code)
err_msg += u"HTTP response content: \n{}".format(resp.text)
logging.error(err_msg)
# log request
err_req_msg = "request: \n"
err_req_msg += "headers: {}\n".format(parsed_request.pop("headers", {}))
for k, v in parsed_request.items():
err_req_msg += "{}: {}\n".format(k, v)
logger.log_error(err_req_msg)
# log response
err_resp_msg = "response: \n"
err_resp_msg += "status_code: {}\n".format(resp.status_code)
err_resp_msg += "headers: {}\n".format(resp.headers)
err_resp_msg += "body: {}\n".format(resp.text)
logger.log_error(err_resp_msg)
raise
finally:
setup_teardown(teardown_actions)
@@ -174,7 +181,7 @@ class Runner(object):
output = {}
for variable in output_variables_list:
if variable not in variables_mapping:
logging.warning(
logger.log_warning(
"variable '{}' can not be found in variables mapping, failed to ouput!"\
.format(variable)
)

View File

@@ -1,7 +1,6 @@
import logging
import unittest
from httprunner import exception, runner, testcase, utils
from . import exception, logger, runner, testcase, utils
class ApiTestCase(unittest.TestCase):
@@ -73,10 +72,11 @@ class ApiTestSuite(unittest.TestSuite):
def _add_tests_to_suite(self, testcases):
for testcase_dict in testcases:
testcase_name = self.test_runner.context.eval_content(testcase_dict["name"])
testcase_name_with_color = logger.coloring(testcase_name, "yellow")
if utils.PYTHON_VERSION == 3:
ApiTestCase.runTest.__doc__ = testcase_name
ApiTestCase.runTest.__doc__ = testcase_name_with_color
else:
ApiTestCase.runTest.__func__.__doc__ = testcase_name
ApiTestCase.runTest.__func__.__doc__ = testcase_name_with_color
test = ApiTestCase(self.test_runner, testcase_dict)
[self.addTest(test) for _ in range(int(testcase_dict.get("times", 1)))]
@@ -165,14 +165,10 @@ class LocustTask(object):
try:
test.runTest()
except exception.MyBaseError as ex:
try:
from locust.events import request_failure
request_failure.fire(
request_type=test.testcase_dict.get("request", {}).get("method"),
name=test.testcase_dict.get("request", {}).get("url"),
response_time=0,
exception=ex
)
except ImportError:
logging.exception(
"Exception occured in testcase: {}".format(test.testcase_dict.get("name")))
from locust.events import request_failure
request_failure.fire(
request_type=test.testcase_dict.get("request", {}).get("method"),
name=test.testcase_dict.get("request", {}).get("url"),
response_time=0,
exception=ex
)

View File

@@ -2,14 +2,14 @@ import ast
import io
import itertools
import json
import logging
import os
import random
import re
from collections import OrderedDict
import yaml
from httprunner import exception, utils
from . import exception, logger, utils
variable_regexp = r"\$([\w_]+)"
function_regexp = r"\$\{([\w_]+\([\$\w_ =,]*\))\}"
@@ -38,7 +38,7 @@ def _load_json_file(json_file):
json_content = json.load(data_file)
except exception.JSONDecodeError:
err_msg = u"JSONDecodeError: JSON file format error: {}".format(json_file)
logging.error(err_msg)
logger.log_error(err_msg)
raise exception.FileFormatError(err_msg)
check_format(json_file, json_content)
@@ -110,8 +110,8 @@ def load_file(file_path):
return _load_csv_file(file_path)
else:
# '' or other suffix
err_msg = u"file is not in YAML/JSON format: {}".format(file_path)
logging.warning(err_msg)
err_msg = u"Unsupported file format: {}".format(file_path)
logger.log_warning(err_msg)
return []
def extract_variables(content):
@@ -267,7 +267,7 @@ def load_testcases_by_path(path):
testcases_list = []
else:
logging.error(u"file not found: {}".format(path))
logger.log_error(u"file not found: {}".format(path))
testcases_list = []
testcases_cache_mapping[path] = testcases_list
@@ -380,7 +380,7 @@ def merge_extractor(api_extrators, test_extracors):
extractor_dict = OrderedDict()
for api_extrator in api_extrators:
if len(api_extrator) != 1:
logging.warning("incorrect extractor: {}".format(api_extrator))
logger.log_warning("incorrect extractor: {}".format(api_extrator))
continue
var_name = list(api_extrator.keys())[0]
@@ -388,7 +388,7 @@ def merge_extractor(api_extrators, test_extracors):
for test_extrator in test_extracors:
if len(test_extrator) != 1:
logging.warning("incorrect extractor: {}".format(test_extrator))
logger.log_warning("incorrect extractor: {}".format(test_extrator))
continue
var_name = list(test_extrator.keys())[0]
@@ -601,13 +601,13 @@ def check_format(file_path, content):
if not content:
# testcase file content is empty
err_msg = u"Testcase file content is empty: {}".format(file_path)
logging.error(err_msg)
logger.log_error(err_msg)
raise exception.FileFormatError(err_msg)
elif not isinstance(content, (list, dict)):
# testcase file content does not match testcase format
err_msg = u"Testcase file content format invalid: {}".format(file_path)
logging.error(err_msg)
logger.log_error(err_msg)
raise exception.FileFormatError(err_msg)
def gen_cartesian_product(*args):

View File

@@ -2,7 +2,6 @@ import hashlib
import hmac
import imp
import importlib
import logging
import os.path
import random
import re
@@ -10,9 +9,10 @@ import string
import types
from collections import OrderedDict
from httprunner import exception
from requests.structures import CaseInsensitiveDict
from . import exception, logger
try:
string_type = basestring
long_type = long
@@ -351,23 +351,23 @@ def print_output(output):
content += "============================================\n"
logging.debug(content)
logger.log_debug(content)
def create_scaffold(project_path):
logging.info(" Start to create new project: {}".format(project_path))
if os.path.isdir(project_path):
folder_name = os.path.basename(project_path)
logging.warning(u" Folder {} exists, please specify a new folder name.".format(folder_name))
logger.log_warning(u"Folder {} exists, please specify a new folder name.".format(folder_name))
return
logger.color_print("Start to create new project: {}\n".format(project_path), "GREEN")
def create_path(path, ptype):
if ptype == "folder":
os.makedirs(path)
elif ptype == "file":
open(path, 'w').close()
logging.info("\tcreated {}: {}".format(ptype, path))
return "created {}: {}\n".format(ptype, path)
path_list = [
(project_path, "folder"),
@@ -377,4 +377,9 @@ def create_scaffold(project_path):
(os.path.join(project_path, "tests", "testcases"), "folder"),
(os.path.join(project_path, "tests", "debugtalk.py"), "file")
]
[create_path(p[0], p[1]) for p in path_list]
msg = ""
for p in path_list:
msg += create_path(p[0], p[1])
logger.color_print(msg, "BLUE")

View File

@@ -2,4 +2,6 @@ requests[security]
flask
PyYAML
PyUnitReport
har2case
har2case
colorama
colorlog