Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add configurable logger #83

Merged
merged 5 commits into from
Nov 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ import and configure the library with your Castle API secret.
# Base Castle API url
# configuration.base_url = "https://api.castle.io/v1"

# Logger (need to respond to info method) - logs Castle API requests and responses
# configuration.logger = logging.getLogger()

# Allowlisted and Denylisted headers are case insensitive
# and allow to use _ and - as a separator, http prefixes are removed
# By default all headers are passed, but some are automatically scrubbed.
Expand Down
51 changes: 30 additions & 21 deletions castle/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,29 @@ def __init__(self):
self.trusted_proxies = []
self.trust_proxy_chain = False
self.trusted_proxy_depth = None
self.logger = None

def isValid(self):
return self.api_secret and self.base_url.hostname

@property
def api_secret(self):
return self.__api_secret
def request_timeout(self):
return self.__request_timeout

@api_secret.setter
def api_secret(self, value):
self.__api_secret = value
@request_timeout.setter
def request_timeout(self, value):
self.__request_timeout = value

@property
def failover_strategy(self):
return self.__failover_strategy

@failover_strategy.setter
def failover_strategy(self, value):
if value in FAILOVER_STRATEGIES:
self.__failover_strategy = value
else:
raise ConfigurationError

@property
def base_url(self):
Expand Down Expand Up @@ -101,23 +113,12 @@ def denylisted(self, value):
self.__denylisted = []

@property
def request_timeout(self):
return self.__request_timeout

@request_timeout.setter
def request_timeout(self, value):
self.__request_timeout = value

@property
def failover_strategy(self):
return self.__failover_strategy
def api_secret(self):
return self.__api_secret

@failover_strategy.setter
def failover_strategy(self, value):
if value in FAILOVER_STRATEGIES:
self.__failover_strategy = value
else:
raise ConfigurationError
@api_secret.setter
def api_secret(self, value):
self.__api_secret = value

@property
def ip_headers(self):
Expand Down Expand Up @@ -163,6 +164,14 @@ def trusted_proxy_depth(self, value):
else:
raise ConfigurationError

@property
def logger(self):
return self.__logger

@logger.setter
def logger(self, value):
self.__logger = value


# pylint: disable=invalid-name
configuration = Configuration()
20 changes: 14 additions & 6 deletions castle/core/send_request.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
from castle.configuration import configuration
from castle.logger import Logger
from castle.session import Session

HTTPS_SCHEME = 'https'
Expand All @@ -12,14 +13,21 @@ def __init__(self, headers=None):
self.session = Session()

def build_query(self, method, path, params):
url = self.build_url(path)
request_data = {
"auth": ('', configuration.api_secret),
"timeout": configuration.request_timeout / 1000.0,
"headers": self.headers,
"verify": CoreSendRequest.verify(),
"data": None if params is None else json.dumps(params)
}

Logger.call("{}:".format(url), request_data.get("data"))

return self.session.get().request(
method,
self.build_url(path),
auth=('', configuration.api_secret),
timeout=configuration.request_timeout / 1000.0,
headers=self.headers,
verify=CoreSendRequest.verify(),
data=None if params is None else json.dumps(params)
url,
**request_data
)

def build_url(self, path):
Expand Down
20 changes: 20 additions & 0 deletions castle/logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from castle.configuration import configuration


class Logger(object):

@staticmethod
def call(message, data=""):
"""
Log the message with optionally data using preconfigured logger

:param message: The base logger message.
:param data: Additional data passed optionally.
"""
logger = configuration.logger

if not logger:
return None

msg = "[CASTLE] {} {}".format(message, data).strip()
return logger.info(msg)
1 change: 1 addition & 0 deletions castle/test/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
'castle.test.headers.filter_test',
'castle.test.headers.format_test',
'castle.test.ip.extract_test',
'castle.test.logger_test',
'castle.test.secure_mode_test',
'castle.test.session_test',
'castle.test.utils.clone_test',
Expand Down
21 changes: 21 additions & 0 deletions castle/test/logger_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from castle.test import unittest
from castle.logger import Logger
from castle.configuration import configuration


class TmpLogger(object):

@staticmethod
def info(message):
return message


class LoggerTestCase(unittest.TestCase):

def test_without_logger(self):
configuration.logger = None
self.assertEqual(Logger.call("Test"), None)

def test_with_logger(self):
configuration.logger = TmpLogger
self.assertEqual(Logger.call("Test"), "[CASTLE] Test")
6 changes: 3 additions & 3 deletions castle/utils/merge.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
class UtilsMerge(object):

@staticmethod
def call(base, extra):
@classmethod
def call(cls, base, extra):
"""
Deeply merge two dictionaries, overriding existing keys in the base.

Expand All @@ -18,7 +18,7 @@ def call(base, extra):
del base[key]
# If the key represents a dict on both given dicts, merge the sub-dicts
elif isinstance(base.get(key), dict) and isinstance(value, dict):
__class__.call(base[key], value)
cls.call(base[key], value)
else:
# Otherwise, set the key on the base to be the value of the extra.
base[key] = value