Skip to content

Commit

Permalink
Merge branch 'develop' of https://github.com/robcarver17/pysystemtrade
Browse files Browse the repository at this point in the history
…into develop
  • Loading branch information
rob committed Oct 6, 2023
2 parents 9b605c4 + 9f8d63c commit 078fe75
Show file tree
Hide file tree
Showing 17 changed files with 115 additions and 119 deletions.
12 changes: 11 additions & 1 deletion docs/production.md
Original file line number Diff line number Diff line change
Expand Up @@ -799,7 +799,7 @@ For other methods, like `label()`, `setup()`, each should be taken on a case by

> Multiple calls to getLogger() with the same name will always return a reference to the same Logger object.
So our outer object handles the context attributes, and the inner `logging.Logger` object does the rest. We cannot copy logger instances as we did with the legacy system. Instead, we can manage the attributes with three ways to merge: *overwrite* (the default), *preserve*, and *clear*.
So our outer object handles the context attributes, and the inner `logging.Logger` object does the rest. We cannot copy logger instances as we did with the legacy system. Instead, we can manage the attributes with four ways to merge: *overwrite* (the default), *preserve*, *clear*, and *temp*.

```python
# merging attributes: method 'overwrite' (default if no method supplied)
Expand All @@ -824,6 +824,16 @@ So our outer object handles the context attributes, and the inner `logging.Logge
clear.info("clear, type 'first', stage 'one'")
clear.info("clear, type 'second', no stage", method="clear", type="second")
clear.info("clear, no attributes", method="clear")

# merging attributes: method 'temp'
temp = get_logger("temp", {"type": "first"})
temp.info("type should be 'first'")
temp.info(
"type should be 'second' temporarily",
method="temp",
type="second",
)
temp.info("type should be back to 'first'")
```

#### Cleaning old logs
Expand Down
33 changes: 17 additions & 16 deletions examples/logging/poc.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,35 +67,36 @@
stage="one",
)

# merging attributes: method 'temp'
temp = get_logger("temp", {"type": "first"})
temp.info("type should be 'first'")
temp.info(
"type should be 'second' temporarily",
method="temp",
type="second",
)
temp.info("type should be back to 'first'")

# levels
level = get_logger("Level")
level.setLevel(logging.WARNING)
level.info("does not print")
level.warning("does print")

# level aliases
alias = get_logger("Alias")
alias.msg("msg() is a temporary alias for DEBUG")
alias.terse("terse() is a temporary alias for INFO")
alias.warn("warn() is a temporary alias for %s", "WARNING")

# alias 'setup'
setup = get_logger("Setup", {"stage": "one", "type": "first"})
setup.info("stage one, type first")
setup = setup.setup(stage="two")
setup.info("stage two, no type")

# alias 'label'
label = get_logger("Label", {"stage": "one"})
label.info("stage one")
label.label(stage="two")
label.info("stage two")
# replacing log.label() - we want to update the log attributes permanently - same as
# overwrite
label = get_logger("label", {"stage": "whatever"})
label.info("Should have 'stage' of 'whatever'")
label.info("Updating log attributes", instrument_code="GOLD")
label.info("Should have 'stage' of 'whatever', and 'instrument_code' 'GOLD'")

# alias 'setup_empty_except_keep_type'
keep_type = get_logger("Keep_Type", {"type": "first", "stage": "one"})
keep_type.info("type first, stage one")
keep_type = keep_type.setup_empty_except_keep_type()
keep_type.info("type first, no stage")

# critical mail
level.critical("sends mail")
# level.critical("sends mail")
4 changes: 2 additions & 2 deletions sysbrokers/IB/ib_Fx_prices_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def get_list_of_fxcodes(self) -> list:
try:
config_data = self._get_ib_fx_config()
except missingFile:
self.log.warn("Can't get list of fxcodes for IB as config file missing")
self.log.warning("Can't get list of fxcodes for IB as config file missing")
return []

list_of_codes = get_list_of_codes(config_data=config_data)
Expand Down Expand Up @@ -104,7 +104,7 @@ def _get_config_info_for_code(self, currency_code: str) -> ibFXConfig:
config_data = self._get_ib_fx_config()
except missingFile as e:
new_log = self.log.setup(**{CURRENCY_CODE_LOG_LABEL: currency_code})
new_log.warn(
new_log.warning(
"Can't get IB FX config for %s as config file missing" % currency_code
)
raise missingInstrument from e
Expand Down
18 changes: 10 additions & 8 deletions sysbrokers/IB/ib_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ def __init__(
ib_ipaddress: str = arg_not_supplied,
ib_port: int = arg_not_supplied,
account: str = arg_not_supplied,
log: pst_logger = get_logger("connectionIB"),
log_name: str = "connectionIB",
):
"""
:param client_id: client id
:param ipaddress: IP address of machine running IB Gateway or TWS. If not passed then will get from private config file, or defaults
:param port: Port listened to by IB Gateway or TWS
:param log: logging object
:param log_name: calling log name
:param mongo_db: mongoDB connection
"""

Expand All @@ -50,19 +50,21 @@ def __init__(
# mongoIBclientIDtracker(database_name="another")

# If you copy for another broker include these lines
self._init_log(log, client_id)
self._log = get_logger(
"connectionIB",
{
TYPE_LOG_LABEL: log_name,
BROKER_LOG_LABEL: "IB",
CLIENTID_LOG_LABEL: client_id,
},
)

# You can pass a client id yourself, or let IB find one

self._init_connection(
ipaddress=ipaddress, port=port, client_id=client_id, account=account
)

def _init_log(self, log, client_id: int):
new_log = log.setup_empty_except_keep_type()
new_log.label(**{BROKER_LOG_LABEL: "IB", CLIENTID_LOG_LABEL: client_id})
self._log = new_log

def _init_connection(
self, ipaddress: str, port: int, client_id: int, account=arg_not_supplied
):
Expand Down
2 changes: 1 addition & 1 deletion sysbrokers/IB/ib_instruments_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def get_list_of_instruments(self) -> list:
try:
config = self.ib_config
except missingFile:
self.log.warn(
self.log.warning(
"Can't get list of instruments because IB config file missing"
)
return []
Expand Down
4 changes: 3 additions & 1 deletion syscontrol/strategy_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ def get_strategy_class_instance(data: dataBlob, strategy_name: str, process_name
)

strategy_data = dataBlob(log_name=process_name)
strategy_data.log.label(**{STRATEGY_NAME_LOG_LABEL: strategy_name})
strategy_data.log.debug(
"Updating log attributes", **{STRATEGY_NAME_LOG_LABEL: strategy_name}
)

strategy_class_instance = strategy_class_object(
strategy_data, strategy_name, **other_args
Expand Down
2 changes: 1 addition & 1 deletion sysdata/data_blob.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ def _get_new_ib_connection(self) -> connectionIB:
client_id = self._get_next_client_id_for_ib()
while True:
try:
ib_conn = connectionIB(client_id, log=self.log)
ib_conn = connectionIB(client_id, log_name=self.log_name)
for id in failed_ids:
self.db_ib_broker_client_id.release_clientid(id)
return ib_conn
Expand Down
4 changes: 2 additions & 2 deletions sysdata/futures/instruments.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def get_instrument_data(
return futuresInstrumentWithMetaData.create_empty()

def delete_instrument_data(self, instrument_code: str, are_you_sure: bool = False):
self.log.label(instrument_code=instrument_code)
self.log.debug("Updating log attributes", instrument_code=instrument_code)

if are_you_sure:
if self.is_code_in_data(instrument_code):
Expand Down Expand Up @@ -124,7 +124,7 @@ def add_instrument_data(
):
instrument_code = instrument_object.instrument_code

self.log.label(instrument_code=instrument_code)
self.log.debug("Updating log attributes", instrument_code=instrument_code)

if self.is_code_in_data(instrument_code):
if ignore_duplication:
Expand Down
4 changes: 2 additions & 2 deletions sysdata/futures/roll_calendars.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def get_roll_calendar(self, instrument_code: str) -> rollCalendar:
raise Exception("Calendar for %s not found!" % instrument_code)

def delete_roll_calendar(self, instrument_code: str, are_you_sure=False):
self.log.label(instrument_code=instrument_code)
self.log.debug("Updating log attributes", instrument_code=instrument_code)

if are_you_sure:
if self.is_code_in_data(instrument_code):
Expand All @@ -60,7 +60,7 @@ def add_roll_calendar(
ignore_duplication: bool = False,
):

self.log.label(instrument_code=instrument_code)
self.log.debug("Updating log attributes", instrument_code=instrument_code)

if self.is_code_in_data(instrument_code):
if ignore_duplication:
Expand Down
4 changes: 2 additions & 2 deletions sysdata/futures/rolls_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def get_roll_parameters(self, instrument_code: str) -> rollParameters:
)

def delete_roll_parameters(self, instrument_code: str, are_you_sure: bool = False):
self.log.label(instrument_code=instrument_code)
self.log.debug("Updating log attributes", instrument_code=instrument_code)

if are_you_sure:
if self.is_code_in_data(instrument_code):
Expand All @@ -67,7 +67,7 @@ def add_roll_parameters(
ignore_duplication: bool = False,
):

self.log.label(instrument_code=instrument_code)
self.log.debug("Updating log attributes", instrument_code=instrument_code)

if self.is_code_in_data(instrument_code):
if ignore_duplication:
Expand Down
65 changes: 22 additions & 43 deletions syslogging/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def process(self, msg, kwargs):
new_kwargs = dict()

method = kwargs.pop("method", "overwrite")
if method not in ["clear", "preserve", "overwrite"]:
if method not in ["clear", "preserve", "overwrite", "temp"]:
raise ValueError(f"Invalid value for 'method': {method}")

for k, v in kwargs.items():
Expand All @@ -34,22 +34,29 @@ def process(self, msg, kwargs):
else:
new_kwargs[k] = v

merged = self._merge_attributes(method, attrs)
new_kwargs["extra"] = merged
self.extra = merged

if self.extra:
return "%s %s" % (self.extra, msg), new_kwargs
"""
Four possible ways to deal with attributes
1. temp: passed values overwrite existing for one message, then discarded
2. clear: clear existing, use passed values
3. preserve: merge with existing values preserved
4. overwrite: merge with existing values overwritten
"""
if method == "temp":
if self.extra:
return "%s %s" % ({**self.extra, **attrs}, msg), new_kwargs
else:
return "%s %s" % (attrs, msg), new_kwargs
else:
return "%s" % msg, new_kwargs
merged = self._merge_attributes(method, attrs)
new_kwargs["extra"] = merged
self.extra = merged

if self.extra:
return "%s %s" % (self.extra, msg), new_kwargs
else:
return "%s" % msg, new_kwargs

def _merge_attributes(self, method, attributes):
"""
Three possible ways to deal with attributes
1. clear: clear existing, use passed values
2. preserve: merge with existing values preserved
3. overwrite: merge with existing values overwritten
"""
if not self.extra or method == "clear":
merged = attributes
elif method == "preserve":
Expand All @@ -63,42 +70,14 @@ def setup(self, **kwargs):
# Create a copy of me with different attributes
warnings.warn(
"The 'setup' function is deprecated; instead, "
"update attributes with method=clear/preserve/overwrite",
"update attributes with method=clear/preserve/overwrite/temp",
DeprecationWarning,
2,
)
attributes = {**kwargs}
self._check_attributes(attributes)
return DynamicAttributeLogger(logging.getLogger(self.name), attributes)

def label(self, **kwargs):
# permanently add new attributes to me
warnings.warn(
"The 'label' function is deprecated; instead, "
"update attributes with method=clear/preserve/overwrite",
DeprecationWarning,
2,
)
if not self.extra:
attributes = {**kwargs}
else:
attributes = {**self.extra, **kwargs}
self._check_attributes(attributes)
self.extra = attributes

def setup_empty_except_keep_type(self):
warnings.warn(
"The 'setup_empty_except_keep_type' function is deprecated; instead, "
"update attributes with method=clear/preserve/overwrite",
DeprecationWarning,
2,
)
if self.extra and TYPE_LOG_LABEL in self.extra:
attributes = {TYPE_LOG_LABEL: self.extra[TYPE_LOG_LABEL]}
else:
attributes = {}
return DynamicAttributeLogger(logging.getLogger(self.name), attributes)

def _check_attributes(self, attributes: dict):
if attributes:
bad_attributes = get_list_of_disallowed_attributes(attributes)
Expand Down
52 changes: 17 additions & 35 deletions syslogging/tests/logging_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def test_name(self, caplog):

def test_attributes_good(self, caplog):
logger = get_logger("my_type", {"stage": "bar"})
logger.warn("foo %s", "bar")
logger.warning("foo %s", "bar")
assert caplog.record_tuples == [
("my_type", logging.WARNING, "{'stage': 'bar'} foo bar")
]
Expand All @@ -24,7 +24,7 @@ def test_attributes_bad(self):

def test_no_name_no_attributes(self, caplog):
logger = get_logger("")
logger.warn("foo")
logger.warning("foo")
assert caplog.record_tuples == [("root", logging.WARNING, "foo")]

def test_attributes_clear(self, caplog):
Expand Down Expand Up @@ -63,6 +63,21 @@ def test_attributes_overwrite(self, caplog):
)
]

def test_attributes_temp(self, caplog):
temp = get_logger("temp", {"stage": "first"})
temp.info("setting temp 'type' attribute", method="temp", type="one")
assert caplog.record_tuples[0] == (
"temp",
logging.INFO,
"{'stage': 'first', 'type': 'one'} setting temp 'type' attribute",
)
temp.info("no type attribute")
assert caplog.record_tuples[1] == (
"temp",
logging.INFO,
"{'stage': 'first'} no type attribute",
)

def test_setup(self):
logger = get_logger("my_type", {"stage": "bar"})
logger = logger.setup(stage="left")
Expand All @@ -77,36 +92,3 @@ def test_setup_bad(self):
logger = get_logger("my_type", {"stage": "bar"})
with pytest.raises(Exception):
logger.setup(foo="bar")

def test_label(self):
logger = get_logger("my_type", {"stage": "bar"})
logger.label(stage="left", instrument_code="ABC")
assert logger.name == "my_type"
assert logger.extra["stage"] == "left"
assert logger.extra["instrument_code"] == "ABC"

no_attrs = get_logger("no_attrs")
no_attrs.label(instrument_code="XYZ")
assert no_attrs.extra["instrument_code"] == "XYZ"

def test_label_bad(self):
logger = get_logger("my_type", {"stage": "bar"})
with pytest.raises(Exception):
logger.label(stage="left", foo="bar")

def test_setup_empty_with(self):
setup_with = get_logger("Setup_With", {"type": "foo", "stage": "one"})
assert setup_with.extra["type"] == "foo"
assert setup_with.extra["stage"] == "one"
setup_with = setup_with.setup_empty_except_keep_type()
assert setup_with.extra["type"] == "foo"
assert "stage" not in setup_with.extra

def test_setup_empty_without(self):
setup_without = get_logger("Setup_Without", {"stage": "one"})
setup_without = setup_without.setup_empty_except_keep_type()
assert "type" not in setup_without.extra

setup_without = get_logger("Setup_Without")
setup_without = setup_without.setup_empty_except_keep_type()
assert "type" not in setup_without.extra
Loading

0 comments on commit 078fe75

Please sign in to comment.