Skip to content
Snippets Groups Projects
Unverified Commit 425b9080 authored by SKAJohanVenter's avatar SKAJohanVenter
Browse files

SAR-149 Updated log message format. Added docs and samples to README. Added...

SAR-149 Updated log message format. Added docs and samples to README. Added tests. Updated transaction interface
parent 397a9c64
No related branches found
No related tags found
No related merge requests found
...@@ -132,10 +132,20 @@ The context handler logs to the root logger by default. Logs can be sent to a cu ...@@ -132,10 +132,20 @@ The context handler logs to the root logger by default. Logs can be sent to a cu
**Example ska formatted logs for successful transaction** **Example ska formatted logs for successful transaction**
Log message formats:
- On Entry:
- Transaction [id]: Enter [name] with parameters [arguments]
- On Exit:
- Transaction [id]: Exit [name]
- On exception:
- Transaction [id]: Exception [name]
--Stacktrace--
```bash ```bash
1|2020-09-15T11:01:20.380Z|INFO|MainThread|__enter__|transactions.py#46||Start transaction Scan, txn-T0001-20200914-00005, {"other": ["config", 1, 2, 3.0]} 1|2020-09-15T11:01:20.380Z|INFO|MainThread|__enter__|transactions.py#46||Transaction [txn-T0001-20200914-00005]: Enter [Command] with parameters [{"other": ["config", 1, 2, 3.0]}]
1|2020-09-15T11:01:20.381Z|INFO|MainThread|__exit__|transactions.py#54||Exit transaction Scan, txn-T0001-20200914-00005 1|2020-09-15T11:01:20.381Z|INFO|MainThread|__exit__|transactions.py#54||Transaction [txn-T0001-20200914-00005]: Exit [Command]
``` ```
......
...@@ -9,63 +9,139 @@ from typing import Text ...@@ -9,63 +9,139 @@ from typing import Text
from ska.skuid.client import SkuidClient from ska.skuid.client import SkuidClient
class Transaction: class Transaction:
"""Transaction context handler. """Transaction context handler.
Provides: Provides:
- Transaction ID: - Transaction ID:
- Re-use existing transaction ID, if available in `params["transaction_id"]` - Re-use existing transaction ID, if available
- If no transaction ID, or empty or None, then generate a new ID - If no transaction ID, or empty or None, then generate a new ID
- context handler returns the ID used - context handler returns the transaction ID used
- Log messages on entry, exit, and exception - Log messages on entry, exit, and exception
.. Example:: .. Examples::
def command(self, parameter_json): def command(self, parameter_json):
parameters = json.reads(parameter_json) parameters = json.reads(parameter_json)
with ska.logging.transaction('My Command', parameters) as transaction_id: with ska.logging.transaction('My Command', parameters) as transaction_id:
# ... # ...
pars['transaction_id'] = transaction_id parameters['transaction_id'] = transaction_id
device.further_command(json.dumps(pars))
# ...
def command(self, parameter_json):
parameters = json.reads(parameter_json)
with ska.logging.transaction('My Command', parameters, transaction_id = "123") as transaction_id:
# ...
parameters['transaction_id'] = transaction_id
device.further_command(json.dumps(pars)) device.further_command(json.dumps(pars))
# ... # ...
def command(self, parameter_json):
parameters = json.reads(parameter_json)
parameters["txn_id_key"] = 123
with ska.logging.transaction('My Command', parameters, transaction_id_key = "txn_id_key") as transaction_id:
# ...
parameters['transaction_id'] = transaction_id
device.further_command(json.dumps(pars))
# ...
Log message formats:
On Entry:
Transaction [id]: Enter [name] with parameters [arguments]
On Exit:
Transaction [id]: Exit [name]
On exception:
Transaction [id]: Exception [name]
Stacktrace
""" """
def __init__(self, name, **params): def __init__(
# TODO: get the logger from the transaction logginh application without passing to self,
# context handler see https://gitlab.com/ska-telescope/ska-logging/-/merge_requests/7#note_414359589 name: str,
self.logger = logging.getLogger(__name__) params: dict = {},
transaction_id: str = "",
transaction_id_key: str = "transaction_id",
):
# Get the root logger
self.logger = logging.getLogger()
self._name = name self._name = name
self._params = params self._params = params
self.transaction_id = self._get_new_or_existing_transaction_id() self._transaction_id_key = transaction_id_key
self._transaction_id = (
transaction_id if self._is_valid_id(transaction_id) else ""
)
if not self._transaction_id:
self._transaction_id = self._get_from_params_or_generate_new_id()
self.logger.info(f"Generated transaction ID {self._transaction_id}")
if transaction_id and params.get(self._transaction_id_key):
self.logger.info(
f"Received 2 transaction IDs {transaction_id} and"
f" {params.get(transaction_id_key)}, using {self._transaction_id}"
)
def __enter__(self): def __enter__(self):
params_json = json.dumps(self._params) params_json = json.dumps(self._params)
self.logger.info(f"Start transaction {self._name}, {self.transaction_id}, {params_json}") self.logger.info(
return self.transaction_id f"Transaction [{self._transaction_id}]: Enter[{self._name}] "
f"with parameters[{params_json}]"
)
return self._transaction_id
def __exit__(self, exc_type, exc_val, exc_tb): def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type: if exc_type:
self.logger.exception(f"Transaction application encountered exception executing " self.logger.exception(
f"{self._name}, {self.transaction_id}") f"Transaction[{self._transaction_id}]: Exception[{self._name}]"
)
self.logger.info(f"Exit transaction {self._name}, {self.transaction_id}") self.logger.info(f"Transaction[{self._transaction_id}]: Exit[{self._name}]")
if exc_type: if exc_type:
raise raise
def _get_new_or_existing_transaction_id(self): def _get_from_params_or_generate_new_id(self):
transaction_id = self._params.get("transaction_id") """Use the transaction_id_key to get the transaction ID from the parameters
or generate a new one if it's not there.
Returns
-------
[String]
[transaction ID]
"""
transaction_id = self._params.get(self._transaction_id_key)
if not self._is_valid_id(transaction_id): if not self._is_valid_id(transaction_id):
transaction_id = self._generate_new_id() transaction_id = self._generate_new_id()
return transaction_id return transaction_id
def _is_valid_id(self, transaction_id): def _is_valid_id(self, transaction_id):
"""Check if the ID is valid
Parameters
----------
transaction_id : [String]
[The transaction ID]
Returns
-------
[bool]
[Whether the ID is valid or not]
"""
if isinstance(transaction_id, Text) and transaction_id.strip(): if isinstance(transaction_id, Text) and transaction_id.strip():
return True return True
return False return False
def _generate_new_id(self): def _generate_new_id(self):
"""Use TransactionIdGenerator to generate a new transaction ID
Returns
-------
[String]
[The newly generated transaction ID]
"""
id_generator = TransactionIdGenerator() id_generator = TransactionIdGenerator()
return id_generator.next() return id_generator.next()
......
...@@ -95,6 +95,13 @@ def get_first_record_and_log_message(logger): ...@@ -95,6 +95,13 @@ def get_first_record_and_log_message(logger):
log_message = recorder.logs[0] log_message = recorder.logs[0]
return record, log_message return record, log_message
def get_second_record_and_log_message(logger):
recorder = get_named_handler(logger, "recorder")
record = recorder.records[1]
log_message = recorder.logs[1]
return record, log_message
def get_last_record_and_log_message(logger): def get_last_record_and_log_message(logger):
recorder = get_named_handler(logger, "recorder") recorder = get_named_handler(logger, "recorder")
record = recorder.records[-1] record = recorder.records[-1]
......
...@@ -15,91 +15,134 @@ from tests.conftest import ( ...@@ -15,91 +15,134 @@ from tests.conftest import (
get_first_record_and_log_message, get_first_record_and_log_message,
get_last_record_and_log_message, get_last_record_and_log_message,
get_all_record_logs, get_all_record_logs,
get_second_record_and_log_message,
) )
class TestTransactionIdGeneration: class TestTransactionIdGeneration:
"""Tests for :class:`~ska.logging.transaction` related to ID generation.""" """Tests for :class:`~ska.logging.transaction` related to ID generation."""
def test_existing_valid_id_is_reused(self, id_generator_stub): def test_preference_order(self, id_generator_stub):
parameters = {"transaction_id": "abc1234", "other": "config"} parameters = {
with transaction("name", **parameters) as transaction_id: "other": "config",
"transaction_id": "xyz123",
"other_transaction_id_key": "def789",
}
with transaction(
"name", parameters, transaction_id="abc1234"
) as transaction_id:
assert transaction_id == "abc1234"
parameters = {
"other": "config",
"transaction_id": "xyz123",
"other_transaction_id_key": "def789",
}
with transaction(
"name", parameters, transaction_id_key="other_transaction_id_key"
) as transaction_id:
assert transaction_id == "def789"
parameters = {
"other": "config",
"transaction_id": "xyz123",
"other_transaction_id_key": "def789",
}
with transaction(
"name",
parameters,
transaction_id="abc1234",
transaction_id_key="other_transaction_id_key",
) as transaction_id:
assert transaction_id == "abc1234" assert transaction_id == "abc1234"
def test_new_id_generated_if_id_is_empty(self, id_generator_stub): def test_new_id_generated_if_id_is_empty(self, id_generator_stub):
parameters = {"transaction_id": "", "other": "config"} parameters = {"transaction_id": "", "other": "config"}
with transaction("name", **parameters) as transaction_id: with transaction("name", parameters) as transaction_id:
assert transaction_id == id_generator_stub.last_id assert transaction_id == id_generator_stub.last_id
def test_new_id_generated_if_id_is_none(self, id_generator_stub): def test_new_id_generated_if_id_is_none(self, id_generator_stub):
parameters = {"transaction_id": None, "other": "config"} parameters = {"transaction_id": None, "other": "config"}
with transaction("name", **parameters) as transaction_id: with transaction("name", parameters) as transaction_id:
assert transaction_id == id_generator_stub.last_id assert transaction_id == id_generator_stub.last_id
def test_new_id_generated_if_id_is_only_white_space(self, id_generator_stub): def test_new_id_generated_if_id_is_only_white_space(self, id_generator_stub):
parameters = {"transaction_id": "\t\n \r\n", "other": "config"} parameters = {"transaction_id": "\t\n \r\n", "other": "config"}
with transaction("name", **parameters) as transaction_id: with transaction("name", parameters) as transaction_id:
assert transaction_id == id_generator_stub.last_id assert transaction_id == id_generator_stub.last_id
def test_new_id_generated_if_id_is_not_string_type(self, id_generator_stub): def test_new_id_generated_if_id_is_not_string_type(self, id_generator_stub):
parameters = {"transaction_id": 1234.5, "other": "config"} parameters = {"transaction_id": 1234.5, "other": "config"}
with transaction("name", **parameters) as transaction_id: with transaction("name", parameters) as transaction_id:
assert transaction_id == id_generator_stub.last_id assert transaction_id == id_generator_stub.last_id
def test_new_id_generated_if_id_is_not_present(self, id_generator_stub): def test_new_id_generated_if_id_is_not_present(self, id_generator_stub):
parameters = {"other": "config"} parameters = {"other": "config"}
with transaction("name", **parameters) as transaction_id: with transaction("name", parameters) as transaction_id:
assert transaction_id == id_generator_stub.last_id assert transaction_id == id_generator_stub.last_id
def test_new_id_generated_if_params_empty(self, id_generator_stub): def test_new_id_generated_if_params_empty(self, id_generator_stub):
parameters = {} parameters = {}
with transaction("name", **parameters) as transaction_id: with transaction("name", parameters) as transaction_id:
assert transaction_id == id_generator_stub.last_id assert transaction_id == id_generator_stub.last_id
def test_id_provider_only_used_once_for_one_new_id(self, id_generator_stub): def test_id_provider_only_used_once_for_one_new_id(self, id_generator_stub):
parameters = {} parameters = {}
with transaction("name", **parameters): with transaction("name", parameters):
assert id_generator_stub.call_count == 1 assert id_generator_stub.call_count == 1
def test_id_provider_not_used_for_existing_valid_id(self, id_generator_stub): def test_id_provider_not_used_for_existing_valid_id(self, id_generator_stub):
parameters = {"transaction_id": "abc1234"} parameters = {"transaction_id": "abc1234"}
with transaction("name", **parameters): with transaction("name", parameters):
assert id_generator_stub.call_count == 0 assert id_generator_stub.call_count == 0
class TestTransactionLogging: class TestTransactionLogging:
"""Tests for :class:`~ska.logging.transaction` related to logging.""" """Tests for :class:`~ska.logging.transaction` related to logging."""
def test_name_and_id_and_params_in_context_handler(self, id_generator_stub, recording_logger): def test_name_and_id_and_params_in_context_handler(
self, id_generator_stub, recording_logger
):
parameters = {"other": ["config", 1, 2, 3.0]} parameters = {"other": ["config", 1, 2, 3.0]}
with transaction("name", **parameters) as transaction_id: with transaction("name", parameters) as transaction_id:
pass pass
_, first_log_message = get_first_record_and_log_message(recording_logger) _, first_log_message = get_first_record_and_log_message(recording_logger)
_, second_log_message = get_second_record_and_log_message(recording_logger)
_, last_log_message = get_last_record_and_log_message(recording_logger) _, last_log_message = get_last_record_and_log_message(recording_logger)
# __enter__ log message # __enter__ log message
assert "Start transaction" in first_log_message assert "Generated transaction ID" in first_log_message
assert "name" in first_log_message assert "Enter" in second_log_message
assert transaction_id in first_log_message assert "name" in second_log_message
assert json.dumps(parameters) in first_log_message assert "other" in second_log_message
assert transaction_id in second_log_message
assert json.dumps(parameters) in second_log_message
# __exit__ log message # __exit__ log message
assert "Exit transaction" in last_log_message assert "Exit" in last_log_message
assert "name" in last_log_message assert "name" in last_log_message
assert transaction_id in last_log_message assert transaction_id in last_log_message
def test_exception_logs_transaction_id_and_command(self, id_generator_stub, recording_logger): def test_exception_logs_transaction_id_and_command(
self, id_generator_stub, recording_logger
):
parameters = {"other": ["config", 1, 2, 3.0]} parameters = {"other": ["config", 1, 2, 3.0]}
with pytest.raises(RuntimeError): with pytest.raises(RuntimeError):
with transaction("name", **parameters) as transaction_id: with transaction("name", parameters) as transaction_id:
raise RuntimeError("Something went wrong") raise RuntimeError("Something went wrong")
record_logs = get_all_record_logs(recording_logger) record_logs = get_all_record_logs(recording_logger)
for log_msg in record_logs: for log_msg in record_logs:
if "RuntimeError" in log_msg and transaction_id in log_msg and "name" in log_msg: if (
"RuntimeError" in log_msg
and transaction_id in log_msg
and "name" in log_msg
):
return return
assert 0, f"RuntimeError and transaction tag not found in exception logs: {record_logs}" assert (
0
), f"RuntimeError and transaction tag not found in exception logs: {record_logs}"
class TestTransactionIdGenerator: class TestTransactionIdGenerator:
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment