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

Python logging switch #1263

Merged
merged 15 commits into from
Oct 6, 2023
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
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