Skip to content

Commit e352edc

Browse files
author
Maria Korlotian
authored
Add configurable logger (#83)
* Add a configurable logger to the configuration
1 parent 356c872 commit e352edc

7 files changed

Lines changed: 92 additions & 30 deletions

File tree

README.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ import and configure the library with your Castle API secret.
3535
# Base Castle API url
3636
# configuration.base_url = "https://api.castle.io/v1"
3737
38+
# Logger (need to respond to info method) - logs Castle API requests and responses
39+
# configuration.logger = logging.getLogger()
40+
3841
# Allowlisted and Denylisted headers are case insensitive
3942
# and allow to use _ and - as a separator, http prefixes are removed
4043
# By default all headers are passed, but some are automatically scrubbed.

castle/configuration.py

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -55,17 +55,29 @@ def __init__(self):
5555
self.trusted_proxies = []
5656
self.trust_proxy_chain = False
5757
self.trusted_proxy_depth = None
58+
self.logger = None
5859

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

6263
@property
63-
def api_secret(self):
64-
return self.__api_secret
64+
def request_timeout(self):
65+
return self.__request_timeout
6566

66-
@api_secret.setter
67-
def api_secret(self, value):
68-
self.__api_secret = value
67+
@request_timeout.setter
68+
def request_timeout(self, value):
69+
self.__request_timeout = value
70+
71+
@property
72+
def failover_strategy(self):
73+
return self.__failover_strategy
74+
75+
@failover_strategy.setter
76+
def failover_strategy(self, value):
77+
if value in FAILOVER_STRATEGIES:
78+
self.__failover_strategy = value
79+
else:
80+
raise ConfigurationError
6981

7082
@property
7183
def base_url(self):
@@ -101,23 +113,12 @@ def denylisted(self, value):
101113
self.__denylisted = []
102114

103115
@property
104-
def request_timeout(self):
105-
return self.__request_timeout
106-
107-
@request_timeout.setter
108-
def request_timeout(self, value):
109-
self.__request_timeout = value
110-
111-
@property
112-
def failover_strategy(self):
113-
return self.__failover_strategy
116+
def api_secret(self):
117+
return self.__api_secret
114118

115-
@failover_strategy.setter
116-
def failover_strategy(self, value):
117-
if value in FAILOVER_STRATEGIES:
118-
self.__failover_strategy = value
119-
else:
120-
raise ConfigurationError
119+
@api_secret.setter
120+
def api_secret(self, value):
121+
self.__api_secret = value
121122

122123
@property
123124
def ip_headers(self):
@@ -163,6 +164,14 @@ def trusted_proxy_depth(self, value):
163164
else:
164165
raise ConfigurationError
165166

167+
@property
168+
def logger(self):
169+
return self.__logger
170+
171+
@logger.setter
172+
def logger(self, value):
173+
self.__logger = value
174+
166175

167176
# pylint: disable=invalid-name
168177
configuration = Configuration()

castle/core/send_request.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import json
22
from castle.configuration import configuration
3+
from castle.logger import Logger
34
from castle.session import Session
45

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

1415
def build_query(self, method, path, params):
16+
url = self.build_url(path)
17+
request_data = {
18+
"auth": ('', configuration.api_secret),
19+
"timeout": configuration.request_timeout / 1000.0,
20+
"headers": self.headers,
21+
"verify": CoreSendRequest.verify(),
22+
"data": None if params is None else json.dumps(params)
23+
}
24+
25+
Logger.call("{}:".format(url), request_data.get("data"))
26+
1527
return self.session.get().request(
1628
method,
17-
self.build_url(path),
18-
auth=('', configuration.api_secret),
19-
timeout=configuration.request_timeout / 1000.0,
20-
headers=self.headers,
21-
verify=CoreSendRequest.verify(),
22-
data=None if params is None else json.dumps(params)
29+
url,
30+
**request_data
2331
)
2432

2533
def build_url(self, path):

castle/logger.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from castle.configuration import configuration
2+
3+
4+
class Logger(object):
5+
6+
@staticmethod
7+
def call(message, data=""):
8+
"""
9+
Log the message with optionally data using preconfigured logger
10+
11+
:param message: The base logger message.
12+
:param data: Additional data passed optionally.
13+
"""
14+
logger = configuration.logger
15+
16+
if not logger:
17+
return None
18+
19+
msg = "[CASTLE] {} {}".format(message, data).strip()
20+
return logger.info(msg)

castle/test/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
'castle.test.headers.filter_test',
2727
'castle.test.headers.format_test',
2828
'castle.test.ip.extract_test',
29+
'castle.test.logger_test',
2930
'castle.test.secure_mode_test',
3031
'castle.test.session_test',
3132
'castle.test.utils.clone_test',

castle/test/logger_test.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from castle.test import unittest
2+
from castle.logger import Logger
3+
from castle.configuration import configuration
4+
5+
6+
class TmpLogger(object):
7+
8+
@staticmethod
9+
def info(message):
10+
return message
11+
12+
13+
class LoggerTestCase(unittest.TestCase):
14+
15+
def test_without_logger(self):
16+
configuration.logger = None
17+
self.assertEqual(Logger.call("Test"), None)
18+
19+
def test_with_logger(self):
20+
configuration.logger = TmpLogger
21+
self.assertEqual(Logger.call("Test"), "[CASTLE] Test")

castle/utils/merge.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
class UtilsMerge(object):
22

3-
@staticmethod
4-
def call(base, extra):
3+
@classmethod
4+
def call(cls, base, extra):
55
"""
66
Deeply merge two dictionaries, overriding existing keys in the base.
77
@@ -18,7 +18,7 @@ def call(base, extra):
1818
del base[key]
1919
# If the key represents a dict on both given dicts, merge the sub-dicts
2020
elif isinstance(base.get(key), dict) and isinstance(value, dict):
21-
__class__.call(base[key], value)
21+
cls.call(base[key], value)
2222
else:
2323
# Otherwise, set the key on the base to be the value of the extra.
2424
base[key] = value

0 commit comments

Comments
 (0)