Skip to content

Commit 00d4e91

Browse files
committed
unifying logger usage thus preventing multiple instantiation of logging-singleton
1 parent 2c7afd0 commit 00d4e91

3 files changed

Lines changed: 50 additions & 52 deletions

File tree

market_maker/bitmex.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@
66
import json
77
import base64
88
import uuid
9-
import logging
109
from market_maker.auth import APIKeyAuthWithExpires
11-
from market_maker.utils import constants, errors
10+
from market_maker.utils import constants, errors, log
1211
from market_maker.ws.ws_thread import BitMEXWebsocket
1312

13+
logger = log.setup_custom_logger('root')
14+
1415

1516
# https://www.bitmex.com/api/explorer/
1617
class BitMEX(object):
@@ -20,7 +21,6 @@ class BitMEX(object):
2021
def __init__(self, base_url=None, symbol=None, apiKey=None, apiSecret=None,
2122
orderIDPrefix='mm_bitmex_', shouldWSAuth=True, postOnly=False, timeout=7):
2223
"""Init connector."""
23-
self.logger = logging.getLogger('root')
2424
self.base_url = base_url
2525
self.symbol = symbol
2626
self.postOnly = postOnly
@@ -255,7 +255,7 @@ def retry():
255255
# Make the request
256256
response = None
257257
try:
258-
self.logger.info("sending req to %s: %s" % (url, json.dumps(postdict or query or '')))
258+
logger.info("sending req to %s: %s" % (url, json.dumps(postdict or query or '')))
259259
req = requests.Request(verb, url, json=postdict, auth=auth, params=query)
260260
prepped = self.session.prepare_request(req)
261261
response = self.session.send(prepped, timeout=timeout)
@@ -268,25 +268,25 @@ def retry():
268268

269269
# 401 - Auth error. This is fatal.
270270
if response.status_code == 401:
271-
self.logger.error("API Key or Secret incorrect, please check and restart.")
272-
self.logger.error("Error: " + response.text)
271+
logger.error("API Key or Secret incorrect, please check and restart.")
272+
logger.error("Error: " + response.text)
273273
if postdict:
274-
self.logger.error(postdict)
274+
logger.error(postdict)
275275
# Always exit, even if rethrow_errors, because this is fatal
276276
exit(1)
277277

278278
# 404, can be thrown if order canceled or does not exist.
279279
elif response.status_code == 404:
280280
if verb == 'DELETE':
281-
self.logger.error("Order not found: %s" % postdict['orderID'])
281+
logger.error("Order not found: %s" % postdict['orderID'])
282282
return
283-
self.logger.error("Unable to contact the BitMEX API (404). " +
283+
logger.error("Unable to contact the BitMEX API (404). " +
284284
"Request: %s \n %s" % (url, json.dumps(postdict)))
285285
exit_or_throw(e)
286286

287287
# 429, ratelimit; cancel orders & wait until X-RateLimit-Reset
288288
elif response.status_code == 429:
289-
self.logger.error("Ratelimited on current request. Sleeping, then trying again. Try fewer " +
289+
logger.error("Ratelimited on current request. Sleeping, then trying again. Try fewer " +
290290
"order pairs or contact support@bitmex.com to raise your limits. " +
291291
"Request: %s \n %s" % (url, json.dumps(postdict)))
292292

@@ -296,18 +296,18 @@ def retry():
296296
reset_str = datetime.datetime.fromtimestamp(int(ratelimit_reset)).strftime('%X')
297297

298298
# We're ratelimited, and we may be waiting for a long time. Cancel orders.
299-
self.logger.warning("Canceling all known orders in the meantime.")
299+
logger.warning("Canceling all known orders in the meantime.")
300300
self.cancel([o['orderID'] for o in self.open_orders()])
301301

302-
self.logger.error("Your ratelimit will reset at %s. Sleeping for %d seconds." % (reset_str, to_sleep))
302+
logger.error("Your ratelimit will reset at %s. Sleeping for %d seconds." % (reset_str, to_sleep))
303303
time.sleep(to_sleep)
304304

305305
# Retry the request.
306306
return retry()
307307

308308
# 503 - BitMEX temporary downtime, likely due to a deploy. Try again
309309
elif response.status_code == 503:
310-
self.logger.warning("Unable to contact the BitMEX API (503), retrying. " +
310+
logger.warning("Unable to contact the BitMEX API (503), retrying. " +
311311
"Request: %s \n %s" % (url, json.dumps(postdict)))
312312
time.sleep(3)
313313
return retry()
@@ -336,22 +336,22 @@ def retry():
336336
return orderResults
337337

338338
elif 'insufficient available balance' in message:
339-
self.logger.error('Account out of funds. The message: %s' % error['message'])
339+
logger.error('Account out of funds. The message: %s' % error['message'])
340340
exit_or_throw(Exception('Insufficient Funds'))
341341

342342

343343
# If we haven't returned or re-raised yet, we get here.
344-
self.logger.error("Unhandled Error: %s: %s" % (e, response.text))
345-
self.logger.error("Endpoint was: %s %s: %s" % (verb, path, json.dumps(postdict)))
344+
logger.error("Unhandled Error: %s: %s" % (e, response.text))
345+
logger.error("Endpoint was: %s %s: %s" % (verb, path, json.dumps(postdict)))
346346
exit_or_throw(e)
347347

348348
except requests.exceptions.Timeout as e:
349349
# Timeout, re-run this request
350-
self.logger.warning("Timed out on request: %s (%s), retrying..." % (path, json.dumps(postdict or '')))
350+
logger.warning("Timed out on request: %s (%s), retrying..." % (path, json.dumps(postdict or '')))
351351
return retry()
352352

353353
except requests.exceptions.ConnectionError as e:
354-
self.logger.warning("Unable to contact the BitMEX API (%s). Please check the URL. Retrying. " +
354+
logger.warning("Unable to contact the BitMEX API (%s). Please check the URL. Retrying. " +
355355
"Request: %s %s \n %s" % (e, url, json.dumps(postdict)))
356356
time.sleep(1)
357357
return retry()

market_maker/utils/log.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
import logging
22
from market_maker.settings import settings
33

4+
loggers = {}
5+
46

57
def setup_custom_logger(name, log_level=settings.LOG_LEVEL):
6-
formatter = logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(module)s - %(message)s')
8+
if loggers.get(name):
9+
return loggers[name]
10+
11+
logger = logging.getLogger(name)
12+
loggers[name] = logger
713

14+
formatter = logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(module)s - %(message)s')
815
handler = logging.StreamHandler()
916
handler.setFormatter(formatter)
10-
11-
logger = logging.getLogger(name)
1217
logger.setLevel(log_level)
1318
logger.addHandler(handler)
1419
return logger

market_maker/ws/ws_thread.py

Lines changed: 24 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,17 @@
99
import logging
1010
from market_maker.settings import settings
1111
from market_maker.auth.APIKeyAuth import generate_expires, generate_signature
12-
from market_maker.utils.log import setup_custom_logger
12+
from market_maker.utils import log
1313
from market_maker.utils.math import toNearest
1414
from future.utils import iteritems
1515
from future.standard_library import hooks
1616
with hooks(): # Python 2/3 compat
1717
from urllib.parse import urlparse, urlunparse
1818

1919

20+
logger = log.setup_custom_logger('root')
21+
22+
2023
# Connects to BitMEX websocket for streaming realtime data.
2124
# The Marketmaker still interacts with this as if it were a REST Endpoint, but now it can get
2225
# much more realtime data without heavily polling the API.
@@ -31,7 +34,6 @@ class BitMEXWebsocket():
3134
MAX_TABLE_LEN = 200
3235

3336
def __init__(self):
34-
self.logger = logging.getLogger('root')
3537
self.__reset()
3638

3739
def __del__(self):
@@ -40,7 +42,7 @@ def __del__(self):
4042
def connect(self, endpoint="", symbol="XBTN15", shouldAuth=True):
4143
'''Connect to the websocket and initialize data stores.'''
4244

43-
self.logger.debug("Connecting WebSocket.")
45+
logger.debug("Connecting WebSocket.")
4446
self.symbol = symbol
4547
self.shouldAuth = shouldAuth
4648

@@ -57,15 +59,15 @@ def connect(self, endpoint="", symbol="XBTN15", shouldAuth=True):
5759
urlParts[0] = urlParts[0].replace('http', 'ws')
5860
urlParts[2] = "/realtime?subscribe=" + ",".join(subscriptions)
5961
wsURL = urlunparse(urlParts)
60-
self.logger.info("Connecting to %s" % wsURL)
62+
logger.info("Connecting to %s" % wsURL)
6163
self.__connect(wsURL)
62-
self.logger.info('Connected to WS. Waiting for data images, this may take a moment...')
64+
logger.info('Connected to WS. Waiting for data images, this may take a moment...')
6365

6466
# Connected. Wait for partials
6567
self.__wait_for_symbol(symbol)
6668
if self.shouldAuth:
6769
self.__wait_for_account()
68-
self.logger.info('Got all market data. Starting.')
70+
logger.info('Got all market data. Starting.')
6971

7072
#
7173
# Data methods
@@ -132,7 +134,7 @@ def recent_trades(self):
132134
#
133135
def error(self, err):
134136
self._error = err
135-
self.logger.error(err)
137+
logger.error(err)
136138
self.exit()
137139

138140
def exit(self):
@@ -145,7 +147,7 @@ def exit(self):
145147

146148
def __connect(self, wsURL):
147149
'''Connect to the websocket in a thread.'''
148-
self.logger.debug("Starting thread")
150+
logger.debug("Starting thread")
149151

150152
ssl_defaults = ssl.get_default_verify_paths()
151153
sslopt_ca_certs = {'ca_certs': ssl_defaults.cafile}
@@ -157,11 +159,10 @@ def __connect(self, wsURL):
157159
header=self.__get_auth()
158160
)
159161

160-
setup_custom_logger('websocket', log_level=settings.LOG_LEVEL)
161162
self.wst = threading.Thread(target=lambda: self.ws.run_forever(sslopt=sslopt_ca_certs))
162163
self.wst.daemon = True
163164
self.wst.start()
164-
self.logger.info("Started thread")
165+
logger.info("Started thread")
165166

166167
# Wait for connect before continuing
167168
conn_timeout = 5
@@ -170,7 +171,7 @@ def __connect(self, wsURL):
170171
conn_timeout -= 1
171172

172173
if not conn_timeout or self._error:
173-
self.logger.error("Couldn't connect to WS! Exiting.")
174+
logger.error("Couldn't connect to WS! Exiting.")
174175
self.exit()
175176
sys.exit(1)
176177

@@ -180,7 +181,7 @@ def __get_auth(self):
180181
if self.shouldAuth is False:
181182
return []
182183

183-
self.logger.info("Authenticating with API Key.")
184+
logger.info("Authenticating with API Key.")
184185
# To auth to the WS using an API key, we generate a signature of a nonce and
185186
# the WS API endpoint.
186187
nonce = generate_expires()
@@ -208,14 +209,14 @@ def __send_command(self, command, args):
208209
def __on_message(self, message):
209210
'''Handler for parsing WS messages.'''
210211
message = json.loads(message)
211-
self.logger.debug(json.dumps(message))
212+
logger.debug(json.dumps(message))
212213

213214
table = message['table'] if 'table' in message else None
214215
action = message['action'] if 'action' in message else None
215216
try:
216217
if 'subscribe' in message:
217218
if message['success']:
218-
self.logger.debug("Subscribed to %s." % message['subscribe'])
219+
logger.debug("Subscribed to %s." % message['subscribe'])
219220
else:
220221
self.error("Unable to subscribe to %s. Error: \"%s\" Please check and restart." %
221222
(message['request']['args'][0], message['error']))
@@ -238,13 +239,13 @@ def __on_message(self, message):
238239
# 'update' - update row
239240
# 'delete' - delete row
240241
if action == 'partial':
241-
self.logger.debug("%s: partial" % table)
242+
logger.debug("%s: partial" % table)
242243
self.data[table] += message['data']
243244
# Keys are communicated on partials to let you know how to uniquely identify
244245
# an item. We use it for updates.
245246
self.keys[table] = message['keys']
246247
elif action == 'insert':
247-
self.logger.debug('%s: inserting %s' % (table, message['data']))
248+
logger.debug('%s: inserting %s' % (table, message['data']))
248249
self.data[table] += message['data']
249250

250251
# Limit the max length of the table to avoid excessive memory usage.
@@ -253,7 +254,7 @@ def __on_message(self, message):
253254
self.data[table] = self.data[table][(BitMEXWebsocket.MAX_TABLE_LEN // 2):]
254255

255256
elif action == 'update':
256-
self.logger.debug('%s: updating %s' % (table, message['data']))
257+
logger.debug('%s: updating %s' % (table, message['data']))
257258
# Locate the item in the collection and update it.
258259
for updateData in message['data']:
259260
item = findItemByKeys(self.keys[table], self.data[table], updateData)
@@ -267,7 +268,7 @@ def __on_message(self, message):
267268
contExecuted = updateData['cumQty'] - item['cumQty']
268269
if contExecuted > 0:
269270
instrument = self.get_instrument(item['symbol'])
270-
self.logger.info("Execution: %s %d Contracts of %s at %.*f" %
271+
logger.info("Execution: %s %d Contracts of %s at %.*f" %
271272
(item['side'], contExecuted, item['symbol'],
272273
instrument['tickLog'], item['price']))
273274

@@ -279,21 +280,21 @@ def __on_message(self, message):
279280
self.data[table].remove(item)
280281

281282
elif action == 'delete':
282-
self.logger.debug('%s: deleting %s' % (table, message['data']))
283+
logger.debug('%s: deleting %s' % (table, message['data']))
283284
# Locate the item in the collection and remove it.
284285
for deleteData in message['data']:
285286
item = findItemByKeys(self.keys[table], self.data[table], deleteData)
286287
self.data[table].remove(item)
287288
else:
288289
raise Exception("Unknown action: %s" % action)
289290
except:
290-
self.logger.error(traceback.format_exc())
291+
logger.error(traceback.format_exc())
291292

292293
def __on_open(self):
293-
self.logger.debug("Websocket Opened.")
294+
logger.debug("Websocket Opened.")
294295

295296
def __on_close(self):
296-
self.logger.info('Websocket Closed')
297+
logger.info('Websocket Closed')
297298
self.exit()
298299

299300
def __on_error(self, error):
@@ -318,16 +319,8 @@ def findItemByKeys(keys, table, matchData):
318319

319320
if __name__ == "__main__":
320321
# create console handler and set level to debug
321-
logger = logging.getLogger()
322-
logger.setLevel(logging.DEBUG)
323-
ch = logging.StreamHandler()
324-
# create formatter
325-
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
326-
# add formatter to ch
327-
ch.setFormatter(formatter)
328-
logger.addHandler(ch)
322+
logger = log.setup_custom_logger('websocket', logging.DEBUG)
329323
ws = BitMEXWebsocket()
330-
ws.logger = logger
331324
ws.connect("https://testnet.bitmex.com/api/v1")
332325
while(ws.ws.sock.connected):
333326
sleep(1)

0 commit comments

Comments
 (0)