Skip to content

Commit 9836085

Browse files
authored
Filter structured logs on task (#4726)
Structured logs from algorithm endpoints can contain messages from multiple invocations. By filtering on `task` these can be separated. If no task is provided, all logs will be included, i.e. also the logs from a specific task. Related to DIAGNijmegen/rse-roadmap#481
1 parent dd102fe commit 9836085

2 files changed

Lines changed: 44 additions & 3 deletions

File tree

app/grandchallenge/components/backends/utils.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,21 +55,25 @@ class ParsedLog(NamedTuple):
5555
inference_result_skipped: bool | None
5656

5757

58-
def parse_structured_log(*, log: str) -> ParsedLog | None:
58+
def parse_structured_log(*, log: str, task=None) -> ParsedLog | None:
5959
"""Parse the structured logs from SageMaker Shim"""
6060
structured_log = json.loads(log.strip())
6161

6262
message = structured_log["log"]
6363
source = SourceChoices(structured_log["source"])
6464
inference_result_skipped = structured_log.get("inference_result_skipped")
6565

66-
if structured_log["internal"] is False:
67-
# Defensive, in case the type of structured_log["internal"] is str
66+
# Defensive, in case the type of structured_log["internal"] is str
67+
if structured_log["internal"] is False and (
68+
task is None or structured_log["task"] == task
69+
):
6870
return ParsedLog(
6971
message=message,
7072
source=source,
7173
inference_result_skipped=inference_result_skipped,
7274
)
75+
else:
76+
return None
7377

7478

7579
def safe_extract(*, src: File, dest: Path):

app/tests/components_tests/test_backends.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from grandchallenge.components.backends.exceptions import ComponentException
2525
from grandchallenge.components.backends.utils import (
2626
_filter_members,
27+
parse_structured_log,
2728
user_error,
2829
)
2930
from grandchallenge.components.models import (
@@ -867,3 +868,39 @@ def test_invocation_results_signature_verified(settings):
867868
)
868869

869870
assert executor._get_inference_result() == inference_result
871+
872+
873+
def test_parse_structured_logs_filters_task():
874+
log_with_task = json.dumps(
875+
{
876+
"log": "message with task",
877+
"source": "stdout",
878+
"internal": False,
879+
"task": "1",
880+
}
881+
)
882+
log_without_task = json.dumps(
883+
{
884+
"log": "message without task",
885+
"source": "stdout",
886+
"internal": False,
887+
"task": None,
888+
}
889+
)
890+
891+
parsed_log = parse_structured_log(log=log_with_task, task="1")
892+
893+
assert parsed_log.message == "message with task"
894+
895+
parsed_log = parse_structured_log(log=log_without_task, task="1")
896+
897+
assert parsed_log is None
898+
899+
parsed_log = parse_structured_log(log=log_with_task, task=None)
900+
901+
# when `task` is None, messages with a task are __included__
902+
assert parsed_log.message == "message with task"
903+
904+
parsed_log = parse_structured_log(log=log_without_task, task=None)
905+
906+
assert parsed_log.message == "message without task"

0 commit comments

Comments
 (0)