Skip to content

Commit

Permalink
Merge 8d30809 into cad0b42
Browse files Browse the repository at this point in the history
  • Loading branch information
Anthony Poddubny committed Jul 10, 2019
2 parents cad0b42 + 8d30809 commit 79211d0
Show file tree
Hide file tree
Showing 11 changed files with 143 additions and 49 deletions.
3 changes: 2 additions & 1 deletion .travis.yml
@@ -1,6 +1,7 @@
language: python
python:
- '2.7'
- '3.6'

before_install:
- pip install -r test_requirements.txt -i https://testpypi.python.org/pypi
Expand All @@ -18,4 +19,4 @@ script:
- pip install cloudshell-core -f dist

after_success:
- coveralls
- coveralls
1 change: 0 additions & 1 deletion README.md
Expand Up @@ -2,7 +2,6 @@
[![Coverage Status](https://coveralls.io/repos/github/QualiSystems/cloudshell-core/badge.svg?branch=dev)](https://coveralls.io/github/QualiSystems/cloudshell-core?branch=dev)
[![PyPI version](https://badge.fury.io/py/cloudshell-core.svg)](https://badge.fury.io/py/cloudshell-core)
[![Dependency Status](https://dependencyci.com/github/QualiSystems/cloudshell-core/badge)](https://dependencyci.com/github/QualiSystems/cloudshell-core)
[![Stories in Ready](https://badge.waffle.io/QualiSystems/cloudshell-core.svg?label=ready&title=Ready)](http://waffle.io/QualiSystems/cloudshell-core)

<p align="center">
<img src="https://github.com/QualiSystems/devguide_source/raw/master/logo.png"></img>
Expand Down
7 changes: 4 additions & 3 deletions cloudshell/core/logger/qs_config.ini
@@ -1,6 +1,7 @@
[Logging]
LOG_LEVEL='DEBUG'
LOG_LEVEL='INFO'
LOG_FORMAT= '%(asctime)s [%(levelname)s]: %(name)s %(module)s - %(funcName)-20s %(message)s'
TIME_FORMAT= '%d-%b-%Y--%H-%M-%S'
LOG_PATH='../../Logs'
#LOG_PATH = 'x:/Logs/'
WINDOWS_LOG_PATH='{ALLUSERSPROFILE}\QualiSystems\logs'
UNIX_LOG_PATH='/var/log/qualisystems'
DEFAULT_LOG_PATH='../../Logs'
7 changes: 6 additions & 1 deletion cloudshell/core/logger/qs_config_parser.py
@@ -1,11 +1,16 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-

import ConfigParser
import os
import sys

DEFAULT_CONFIG_PATH = 'qs_config.ini'

if (sys.version_info >= (3,0)):
import configparser as ConfigParser
else:
import ConfigParser


class QSConfigParser:
_configDict = None
Expand Down
102 changes: 70 additions & 32 deletions cloudshell/core/logger/qs_logger.py
Expand Up @@ -24,9 +24,10 @@
# default settings
DEFAULT_FORMAT = '%(asctime)s [%(levelname)s]: %(name)s %(module)s - %(funcName)-20s %(message)s'
DEFAULT_TIME_FORMAT = '%Y%m%d%H%M%S'
DEFAULT_LEVEL = 'DEBUG'
DEFAULT_LEVEL = 'INFO'
# DEFAULT_LOG_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../', 'Logs')
LOG_SECTION = 'Logging'
WINDOWS_OS_FAMILY = "nt"

_LOGGER_CONTAINER = {}
_LOGGER_LOCK = threading.Lock()
Expand All @@ -47,9 +48,14 @@ def get_settings():
log_format = QSConfigParser.get_setting(LOG_SECTION, 'LOG_FORMAT') or DEFAULT_FORMAT
config['FORMAT'] = log_format

# log_path
log_path = QSConfigParser.get_setting(LOG_SECTION, 'LOG_PATH')
config['LOG_PATH'] = log_path
# UNIX log path
config['UNIX_LOG_PATH'] = QSConfigParser.get_setting(LOG_SECTION, 'UNIX_LOG_PATH')

# Windows log path
config['WINDOWS_LOG_PATH'] = QSConfigParser.get_setting(LOG_SECTION, 'WINDOWS_LOG_PATH')

# Default log path for all systems
config['DEFAULT_LOG_PATH'] = QSConfigParser.get_setting(LOG_SECTION, 'DEFAULT_LOG_PATH')

# Time format
time_format = QSConfigParser.get_setting(LOG_SECTION, 'TIME_FORMAT') or DEFAULT_TIME_FORMAT
Expand All @@ -58,47 +64,79 @@ def get_settings():
return config


# return accessable log path or None
def get_accessible_log_path(reservation_id='Autoload', handler='default'):
"""Generate log path for the logger and verify that it's accessible using LOG_PATH/reservation_id/handler-%timestamp%.log
def _get_log_path_config(config):
"""Get log path based on the environment variable or Windows/Unix config setting
:param reservation_id: part of log path
:param handler: handler name for logger
:return: generated log path
:param dict[str] config:
:rtype: str
"""

accessible_log_path = None
config = get_settings()

if 'LOG_PATH' in os.environ:
log_path = os.environ['LOG_PATH']
elif 'LOG_PATH' in config and config['LOG_PATH']:
log_path = config['LOG_PATH']
return os.environ['LOG_PATH']

if os.name == WINDOWS_OS_FAMILY:
tpl = config.get('WINDOWS_LOG_PATH')
if tpl:
try:
return tpl.format(**os.environ)
except KeyError:
print ("Environment variable is not defined in the template {}".format(tpl))
else:
return None
return config.get('UNIX_LOG_PATH')

if log_path.startswith('..'):
log_path = os.path.join(os.path.dirname(__file__), log_path)

time_format = config['TIME_FORMAT'] or DEFAULT_TIME_FORMAT
def _prepare_log_path(log_path, log_file_name):
"""Create logs directory if needed and return full path to the log file
log_file_name = '{0}--{1}.log'.format(handler, datetime.now().strftime(time_format))
log_path = os.path.join(log_path, reservation_id)
:param str log_path:
:param str log_file_name:
:rtype: str
"""
if log_path.startswith('..'):
log_path = os.path.join(os.path.dirname(__file__), log_path)

log_file = os.path.join(log_path, log_file_name)
# print(log_file)

if os.path.isdir(log_path):
if os.access(log_path, os.W_OK):
accessible_log_path = log_file
return log_file
else:
try:
os.makedirs(log_path)
accessible_log_path = log_file
return log_file
except:
pass

return accessible_log_path

# return accessable log path or None
def get_accessible_log_path(reservation_id='Autoload', handler='default'):
"""Generate log path for the logger and verify that it's accessible using LOG_PATH/reservation_id/handler-%timestamp%.log
:param reservation_id: part of log path
:param handler: handler name for logger
:return: generated log path
"""
config = get_settings()
time_format = config['TIME_FORMAT'] or DEFAULT_TIME_FORMAT
log_file_name = '{0}--{1}.log'.format(handler, datetime.now().strftime(time_format))

log_path = _get_log_path_config(config)

if log_path:
env_folder = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", "..", "..", "..")
shell_name = os.path.basename(os.path.abspath(env_folder))
log_path = os.path.join(log_path, reservation_id, shell_name)
path = _prepare_log_path(log_path=log_path,
log_file_name=log_file_name)
if path:
return path

default_log_path = config.get('DEFAULT_LOG_PATH')

if default_log_path:
default_log_path = os.path.join(default_log_path, reservation_id)
return _prepare_log_path(log_path=default_log_path,
log_file_name=log_file_name)


def log_execution_info(logger_hdlr, exec_info):
Expand All @@ -113,7 +151,7 @@ def log_execution_info(logger_hdlr, exec_info):
logger_hdlr.info('-----------------------------------------------------------\n')


def get_qs_logger(log_group='Ungrouped', log_category ='QS', log_file_prefix='QS'):
def get_qs_logger(log_group='Ungrouped', log_category='QS', log_file_prefix='QS'):
"""Create cloudshell specific singleton logger
:param log_group: This folder will be grouped under this name. The default implementation of the group is a folder
Expand Down Expand Up @@ -241,7 +279,7 @@ def normalize_buffer(input_buffer):

result_buffer = ''

if not isinstance(input_buffer, basestring):
if not isinstance(input_buffer, str):
input_buffer = str(input_buffer)

match_iter = color_pattern.finditer(input_buffer)
Expand Down Expand Up @@ -282,10 +320,10 @@ def format(self, record):
s = logging.Formatter.format(self, record)
header, footer = s.rsplit(record.message, self.MAX_SPLIT)
s = s.replace('\n', '\n' + header)
except Exception, e:
print traceback.format_exc()
print 'logger.format: Unexpected error: ' + str(e)
print 'record = {}<<<'.format(traceback.format_exc())
except Exception as e:
print (traceback.format_exc())
print ('logger.format: Unexpected error: ' + str(e))
print ('record = {}<<<'.format(traceback.format_exc()))
return s


Expand Down
3 changes: 3 additions & 0 deletions setup.py
@@ -1,3 +1,5 @@
import sys

from setuptools import setup, find_packages
import os

Expand Down Expand Up @@ -27,6 +29,7 @@
classifiers=[
"Development Status :: 5 - Production/Stable",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3.0",
"Topic :: Software Development :: Libraries",
"License :: OSI Approved :: Apache Software License",
]
Expand Down
1 change: 1 addition & 0 deletions test_requirements.txt
@@ -0,0 +1 @@
mock; python_version <= '2.7'
4 changes: 3 additions & 1 deletion tests/core/logger/config/test_qs_config.ini
Expand Up @@ -2,4 +2,6 @@
LOG_LEVEL='INFO'
LOG_FORMAT= '%(asctime)s [%(levelname)s]: %(name)s %(module)s - %(funcName)-20s %(message)s'
TIME_FORMAT= '%d-%b-%Y--%H-%M-%S'
LOG_PATH='../../Logs'
WINDOWS_LOG_PATH='{ALLUSERSPROFILE}\QualiSystems\logs'
UNIX_LOG_PATH='/var/log/qualisystems'
DEFAULT_LOG_PATH='../../Logs'
4 changes: 3 additions & 1 deletion tests/core/logger/test_qs_config_parser.py
Expand Up @@ -14,7 +14,9 @@
class TestQSConfigParser(TestCase):
exp_response = {'Logging':
{'time_format': '%d-%b-%Y--%H-%M-%S',
'log_path': '../../Logs',
'windows_log_path': r'{ALLUSERSPROFILE}\QualiSystems\logs',
'unix_log_path': '/var/log/qualisystems',
'default_log_path': '../../Logs',
'log_format': '%(asctime)s [%(levelname)s]: %(name)s %(module)s - %(funcName)-20s %(message)s',
'log_level': 'INFO'}}

Expand Down
58 changes: 50 additions & 8 deletions tests/core/logger/test_qs_logger.py
Expand Up @@ -8,9 +8,16 @@
import os
import re
import shutil
import sys

if (sys.version_info >= (3,0)):
from unittest.mock import MagicMock
from unittest import TestCase, mock
else:
from mock import MagicMock
import mock
from unittest import TestCase

from mock import MagicMock
from unittest import TestCase

from cloudshell.core.logger import qs_logger
from cloudshell.core.logger.interprocess_logger import MultiProcessingLog
Expand Down Expand Up @@ -66,18 +73,53 @@ def tearDown(self):

def test_get_settings(self):
""" Test suite for get_settings method """
exp_response = {'LOG_PATH': '../../Logs',
exp_response = {'WINDOWS_LOG_PATH': r'{ALLUSERSPROFILE}\QualiSystems\logs',
'UNIX_LOG_PATH': '/var/log/qualisystems',
'DEFAULT_LOG_PATH': '../../Logs',
'TIME_FORMAT': '%d-%b-%Y--%H-%M-%S',
'LOG_LEVEL': 'INFO',
'FORMAT': '%(asctime)s [%(levelname)s]: %(name)s %(module)s - %(funcName)-20s %(message)s'}

self.assertEqual(qs_logger.get_settings(), exp_response)

@mock.patch("cloudshell.core.logger.qs_logger.os")
def test_get_log_path_config_from_environment_variable(self, os):
"""Check that method will primarily return log path from the environment variable if such exists"""
config = {}
expected_path = MagicMock()
os.environ = {'LOG_PATH': expected_path}
# act
result = qs_logger._get_log_path_config(config=config)
# verify
self.assertEqual(result, expected_path)

@mock.patch("cloudshell.core.logger.qs_logger.os")
def test_get_log_path_config_for_windows_os(self, os):
"""Check that method will return windows log path setting with substituted environment variables"""
os.name = qs_logger.WINDOWS_OS_FAMILY
os.environ = {"SOME_EN_VARIABLE": "C:\\some_path"}
expected_path = "{SOME_EN_VARIABLE}\\qualisystems\\logs\\commands"
config = {"WINDOWS_LOG_PATH": expected_path}
# act
result = qs_logger._get_log_path_config(config=config)
# verify
self.assertEqual(result, "C:\\some_path\\qualisystems\\logs\\commands")

@mock.patch("cloudshell.core.logger.qs_logger.os")
def test_get_log_path_config_for_unix_os(self, os):
"""Check that method will return unix log path setting for posix OS"""
os.name = "posix"
expected_path = MagicMock()
config = {"UNIX_LOG_PATH": expected_path}
# act
result = qs_logger._get_log_path_config(config=config)
# verify
self.assertEqual(result, expected_path)

def test_get_accessible_log_path_default_params(self):
""" Test suite for get_accessible_log_path method """

path = qs_logger.get_accessible_log_path()
self.assertTrue(re.search(r"Logs[\\/]Autoload[\\/]default--\d{2}-\w+-\d{4}--\d{2}-\d{2}-\d{2}\.log", path))
self.assertRegexpMatches(path, r"Logs[\\/]Autoload[\\/](.*[\\/])?default--\d{2}-\w+-\d{4}--\d{2}-\d{2}-\d{2}\.log")
self.assertTrue(os.path.dirname(path))

def test_get_accessible_log_path_path_creation(self):
Expand All @@ -88,9 +130,9 @@ def test_get_accessible_log_path_path_creation(self):

def test_get_accessible_log_path(self):
""" Test suite for get_accessible_log_path method """

path = qs_logger.get_accessible_log_path(qs_logger.get_accessible_log_path("reservation_id", "handler_name"))
self.assertTrue(re.search(r"Logs[\\/]reservation_id[\\/]handler_name--\d{2}-\w+-\d{4}--\d{2}-\d{2}-\d{2}\.log", path))
path = qs_logger.get_accessible_log_path("reservation_id", "handler_name")
self.assertRegexpMatches(path, r"Logs[\\/]reservation_id[\\/](.*[\\/])?"
r"handler_name--\d{2}-\w+-\d{4}--\d{2}-\d{2}-\d{2}\.log")

def test_get_accessible_log_path_log_path_setting_missing(self):
""" Test suite for get_accessible_log_path method """
Expand Down
2 changes: 1 addition & 1 deletion version.txt
@@ -1 +1 @@
1.0.1
3.0.001

0 comments on commit 79211d0

Please sign in to comment.