Skip to content

Commit 90e9299

Browse files
authored
Merge pull request #328 from ansforge/chore/converter-logging
chore(converter): logging structuré
2 parents 6ca6f39 + 10f87ad commit 90e9299

File tree

12 files changed

+152
-16
lines changed

12 files changed

+152
-16
lines changed

converter/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,16 +74,16 @@ gunicorn -w 4 -b 0.0.0.0:8080 converter.converter:app
7474

7575
```bash
7676
# Based on https://github.com/ansforge/SAMU-Hub-Sante/blob/main/web/lrm/client/constants.js#L5C30-L45C2
77-
curl -X POST http://localhost:8080/convert-cisu \
77+
curl -X POST http://localhost:8080/convert \
7878
-H "Content-Type: application/json" \
79-
-d "$(jq --argjson usecase "$(cat ../src/main/resources/sample/examples/RC-EDA/RC-EDA-FemmeEnceinte-DelphineVigneau.json)" '.edxl.content[0].jsonContent.embeddedJsonContent.message |= (. + $usecase)' tests/edxl_envelope_fire_to_health.json)"
79+
-d "$(jq --argjson usecase "$(cat ../src/main/resources/sample/examples/RC-EDA/RC-EDA-FemmeEnceinte-DelphineVigneau.json)" '.sourceVersion = "v3" | .targetVersion = "v3" | .cisuConversion = true | .edxl.content[0].jsonContent.embeddedJsonContent.message |= (. + $usecase)' tests/fixtures/EDXL/edxl_envelope_fire_to_health.json)"
8080
```
8181

8282
#### Convert Health to CISU Format
8383

8484
```bash
8585
# Based on https://github.com/ansforge/SAMU-Hub-Sante/blob/main/web/lrm/client/constants.js#L5C30-L45C2
86-
curl -X POST http://localhost:8080/convert-cisu \
86+
curl -X POST http://localhost:8080/convert \
8787
-H "Content-Type: application/json" \
88-
-d "$(jq --argjson usecase "$(cat ../src/main/resources/sample/examples/RS-EDA/RS-EDA-SMUR_FemmeEnceinte_DelphineVigneau.01.json)" '.edxl.content[0].jsonContent.embeddedJsonContent.message |= (. + $usecase)' tests/edxl_envelope_health_to_fire.json)"
88+
-d "$(jq --argjson usecase "$(cat ../src/main/resources/sample/examples/RS-EDA/RS-EDA-SMUR_FemmeEnceinte_DelphineVigneau.01.json)" '.sourceVersion = "v3" | .targetVersion = "v3" | .cisuConversion = true | .edxl.content[0].jsonContent.embeddedJsonContent.message |= (. + $usecase)' tests/fixtures/EDXL/edxl_envelope_health_to_fire.json)"
8989
```

converter/converter/cisu/create_case/create_case_cisu_converter.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
translate_key_words,
1818
)
1919
from converter.cisu.base_cisu_converter import BaseCISUConverter
20+
import logging
21+
22+
logger = logging.getLogger(__name__)
2023

2124

2225
class CreateCaseCISUConverter(BaseCISUConverter):
@@ -89,21 +92,25 @@ def from_cisu_to_rs(cls, input_json: Dict[str, Any]) -> Dict[str, Any]:
8992
Returns:
9093
Converted EDXL Health JSON
9194
"""
95+
logger.info("Starting CISU to Health format conversion")
9296

9397
def set_default_location_freetext(json_data: Dict[str, Any]):
98+
logger.debug("Setting default location freetext")
9499
if not is_field_completed(json_data, "$.location.freetext"):
95100
json_data["location"]["freetext"] = (
96101
"" # need at least empty value to pass validation
97102
)
98103

99104
def add_location_detail(json_data: Dict[str, Any]):
105+
logger.debug("Adding location detail to freetext")
100106
if is_field_completed(json_data, "$.location.city.detail"):
101107
set_default_location_freetext(json_data)
102108
json_data["location"]["freetext"] += (
103109
"\nDétails de commune : " + json_data["location"]["city"]["detail"]
104110
)
105111

106112
def add_case_priority(json_data: Dict[str, Any]):
113+
logger.debug("Adding case priority")
107114
if is_field_completed(json_data, "$.initialAlert.reporting"):
108115
if not is_field_completed(json_data, "$.qualification.details"):
109116
json_data["qualification"]["details"] = {}
@@ -115,6 +122,7 @@ def add_case_priority(json_data: Dict[str, Any]):
115122
)
116123

117124
def merge_notes_freetext(json_data: Dict[str, Any]):
125+
logger.debug("Merging freetext notes")
118126
if not is_field_completed(json_data, "$.initialAlert.notes"):
119127
return json_data
120128

@@ -136,6 +144,7 @@ def merge_notes_freetext(json_data: Dict[str, Any]):
136144
return json_data
137145

138146
def add_victims_to_medical_notes(json_data: Dict[str, Any], sender_id: str):
147+
logger.debug("Adding victims to medical notes")
139148
field_value = get_field_value(json_data, "$.qualification.victims")
140149

141150
if field_value is None:
@@ -150,6 +159,7 @@ def add_victims_to_medical_notes(json_data: Dict[str, Any], sender_id: str):
150159
def add_object_to_medical_notes(
151160
json_data: Dict[str, Any], note_text: str, sender_id: str
152161
):
162+
logger.debug("Adding object to medical notes")
153163
if not is_field_completed(json_data, "$.medicalNote"):
154164
json_data["medicalNote"] = []
155165

@@ -192,6 +202,7 @@ def add_object_to_medical_notes(
192202
add_victims_to_medical_notes(output_use_case_json, sender_id)
193203

194204
# - Delete paths - /!\ It must be the last step
205+
logger.debug("Removing unnecessary paths")
195206
delete_paths(output_use_case_json, cls.CISU_PATHS_TO_DELETE)
196207

197208
return cls.format_rs_output_json(output_json, output_use_case_json)
@@ -225,15 +236,18 @@ def from_rs_to_cisu(cls, input_json: Dict[str, Any]) -> Dict[str, Any]:
225236
Returns:
226237
Converted EDXL CISU JSON
227238
"""
239+
logger.info("Starting Health format to CISU conversion")
228240

229241
def add_victim_information(json_data: Dict[str, Any]):
242+
logger.debug("Adding victim information")
230243
if not is_field_completed(json_data, "$.qualification"):
231244
json_data["qualification"] = {}
232245
json_data["qualification"]["victims"] = cls.get_victim_count(
233246
cls, input_usecase_json
234247
)
235248

236249
def get_call_taker_information(json_data: Dict[str, Any]):
250+
logger.debug("Getting call taker information")
237251
sender_id = get_sender(json_data)
238252
crra_code = sender_id[
239253
len("fr.health.") :
@@ -244,6 +258,7 @@ def get_call_taker_information(json_data: Dict[str, Any]):
244258
}
245259

246260
def add_default_external_info_type(json_data: Dict[str, Any]):
261+
logger.debug("Adding default external info type")
247262
external_info = get_field_value(json_data, "$.location.externalInfo")
248263
if external_info is not None:
249264
for info in external_info:
@@ -283,6 +298,7 @@ def add_default_external_info_type(json_data: Dict[str, Any]):
283298
add_default_external_info_type(output_usecase_json)
284299

285300
# Deletions - /!\ it must be done before copying qualification and location fields
301+
logger.debug("Removing unnecessary paths")
286302
delete_paths(output_usecase_json, cls.HEALTH_PATHS_TO_DELETE)
287303

288304
if is_field_completed(input_usecase_json, "$.initialAlert"):

converter/converter/cisu/resources_info/resources_info_cisu_converter.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
ResourcesInfoCISUConstants,
88
)
99
from converter.utils import get_field_value, set_value, delete_paths
10+
import logging
11+
12+
logger = logging.getLogger(__name__)
1013

1114

1215
class ResourcesInfoCISUConverter(BaseCISUConverter):
@@ -20,27 +23,33 @@ def get_cisu_message_type(cls) -> str:
2023

2124
@classmethod
2225
def from_cisu_to_rs(cls, edxl_json: Dict[str, Any]) -> Dict[str, Any]:
26+
logger.info("Converting from CISU to RS format for Resources Info message.")
27+
logger.debug(f"Message content: {edxl_json}")
2328
output_json = cls.copy_cisu_input_content(edxl_json)
2429
output_use_case_json = cls.copy_cisu_input_use_case_content(edxl_json)
2530
resources = get_field_value(
2631
output_use_case_json, ResourcesInfoCISUConstants.RESOURCE_PATH
2732
)
2833

2934
for resource in resources:
35+
logger.debug(f"Processing resource: {resource}")
3036
state = get_field_value(resource, ResourcesInfoCISUConstants.STATE_PATH)
3137
set_value(resource, ResourcesInfoCISUConstants.STATE_PATH, [state])
3238

3339
return cls.format_rs_output_json(output_json, output_use_case_json)
3440

3541
@classmethod
3642
def from_rs_to_cisu(cls, edxl_json: Dict[str, Any]) -> Dict[str, Any]:
43+
logger.info("Converting from RS to CISU format for Resources Info message.")
44+
logger.debug(f"Message content: {edxl_json}")
3745
output_json = cls.copy_rs_input_content(edxl_json)
3846
output_use_case_json = cls.copy_rs_input_use_case_content(edxl_json)
3947

4048
resources = get_field_value(
4149
output_use_case_json, ResourcesInfoCISUConstants.RESOURCE_PATH
4250
)
4351
for resource in resources:
52+
logger.debug(f"Processing resource: {resource}")
4453
rs_vehicle_type = get_field_value(
4554
resource, ResourcesInfoCISUConstants.VEHICLE_TYPE_PATH
4655
)

converter/converter/conversion_strategy/cisu_conversion_strategy.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import logging
2+
13
from converter.cisu.create_case.create_case_cisu_converter import (
24
CreateCaseCISUConverter,
35
)
@@ -16,15 +18,20 @@
1618
extract_message_type_from_message_content,
1719
)
1820

21+
logger = logging.getLogger(__name__)
22+
1923

2024
def cisu_conversion_strategy(edxl_json, source_version, target_version):
21-
print(f"CISU Conversion initiated from {source_version} to {target_version}")
25+
logger.info(f"CISU Conversion initiated from {source_version} to {target_version}")
2226

2327
direction = compute_message_direction(edxl_json)
2428
message_content = extract_message_content(edxl_json)
2529
selected_strategy = select_conversion_strategy(message_content)
2630

2731
if direction == CISUConstants.TO_CISU:
32+
logger.info(
33+
"Conversion RS -> CISU",
34+
)
2835
if target_version != CISUConstants.MAINTAINED_CISU_VERSION:
2936
raise ValueError(
3037
f"Unknown target version {target_version}. Must be: {CISUConstants.MAINTAINED_CISU_VERSION}"
@@ -36,6 +43,9 @@ def cisu_conversion_strategy(edxl_json, source_version, target_version):
3643

3744
return selected_strategy.from_rs_to_cisu(rs_json_message)
3845
elif direction == CISUConstants.FROM_CISU:
46+
logger.info(
47+
"Conversion CISU -> RS",
48+
)
3949
if source_version != CISUConstants.MAINTAINED_CISU_VERSION:
4050
raise ValueError(
4151
f"Unknown source version {source_version}. Must be: {CISUConstants.MAINTAINED_CISU_VERSION}"

converter/converter/conversion_strategy/health_conversion_strategy.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
GeoPositionsUpdateConverter,
3333
)
3434
from converter.versions.rpis.rpis_converter import RpisConverter
35+
import logging
3536

3637
from converter.utils import (
3738
extract_message_content,
@@ -40,7 +41,9 @@
4041

4142

4243
def health_conversion_strategy(edxl_json, source_version: str, target_version: str):
43-
print(f"Health Conversion initiated from {source_version} to {target_version}")
44+
logging.info(
45+
f"Health Conversion initiated from {source_version} to {target_version}"
46+
)
4447

4548
message_content = extract_message_content(edxl_json)
4649
selected_strategy = select_conversion_strategy(message_content)

converter/converter/converter.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
1-
from flask import Flask, request, jsonify
2-
1+
from flask import Flask, request, jsonify, g
2+
import logging
33
from converter.conversion_strategy.conversion_strategy import conversion_strategy
4+
from converter.utils import (
5+
get_sender,
6+
get_recipient,
7+
extract_message_type_from_message_content,
8+
extract_message_content,
9+
)
10+
from converter.logging_config import configure_logging, LoggingKeys
11+
12+
configure_logging()
413

514
app = Flask(__name__)
15+
logger = logging.getLogger(__name__)
616

717

818
def raise_error(message, code: int = 400):
9-
print(f"[ERROR] {message}")
19+
logger.error(message)
1020
return jsonify({"error": message}), code
1121

1222

@@ -16,11 +26,26 @@ def convert():
1626
return raise_error("Content-Type must be application/json")
1727

1828
req_data = request.get_json()
29+
logger.debug(f"Received conversion request: {req_data}")
1930
source_version = req_data.get("sourceVersion")
2031
target_version = req_data.get("targetVersion")
2132
edxl_json = req_data.get("edxl")
2233
is_cisu_conversion = req_data.get("cisuConversion", False)
2334

35+
# Store data in request context to be used in logs
36+
try:
37+
setattr(g, LoggingKeys.DISTRIBUTION_ID.value, edxl_json.get("distributionID"))
38+
setattr(g, LoggingKeys.SENDER_ID.value, get_sender(edxl_json))
39+
setattr(g, LoggingKeys.RECIPIENT_ID.value, get_recipient(edxl_json))
40+
message_content = extract_message_content(edxl_json)
41+
setattr(
42+
g,
43+
LoggingKeys.MESSAGE_TYPE.value,
44+
extract_message_type_from_message_content(message_content),
45+
)
46+
except Exception:
47+
pass
48+
2449
if not source_version or not target_version or not edxl_json:
2550
return raise_error(
2651
f"Missing required fields: sourceVersion={source_version}, targetVersion={target_version}, edxl present={edxl_json is not None}"
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import logging
2+
from pythonjsonlogger.json import JsonFormatter
3+
from flask import has_request_context, g
4+
from enum import Enum
5+
6+
7+
class LoggingKeys(Enum):
8+
DISTRIBUTION_ID = "distributionId"
9+
SENDER_ID = "senderId"
10+
RECIPIENT_ID = "recipientId"
11+
MESSAGE_TYPE = "messageType"
12+
13+
14+
# Using filter to add context variable: https://docs.python.org/3/howto/logging-cookbook.html#using-filters-to-impart-contextual-information
15+
class DistributionContextFilter(logging.Filter):
16+
def filter(self, record):
17+
if has_request_context():
18+
for key in LoggingKeys:
19+
setattr(record, key.value, getattr(g, key.value, None))
20+
return True
21+
22+
23+
def configure_logging():
24+
root = logging.getLogger()
25+
for h in root.handlers[:]:
26+
root.removeHandler(h)
27+
28+
handler = logging.StreamHandler()
29+
30+
formatter = JsonFormatter(
31+
"%(asctime)s %(levelname)s %(message)s",
32+
rename_fields={
33+
"asctime": "timestamp",
34+
"levelname": "level",
35+
},
36+
)
37+
handler.setFormatter(formatter)
38+
handler.addFilter(DistributionContextFilter())
39+
40+
root.addHandler(handler)
41+
root.setLevel(logging.INFO)
42+
43+
root.propagate = True

converter/converter/utils.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
44
from typing import List, Dict, Any, Optional
55
from jsonpath_ng import parse
66
from yaml import dump
7+
import logging
78

89
from converter.constants import Constants
910

11+
logger = logging.getLogger(__name__)
12+
1013

1114
def get_recipient(edxl_json: Dict[str, Any]) -> str:
1215
return edxl_json["descriptor"]["explicitAddress"]["explicitAddressValue"]
@@ -141,7 +144,7 @@ def is_field_completed(json_data: Dict[str, Any], json_path: str):
141144
jsonpath_expr = parse(json_path)
142145
return len(jsonpath_expr.find(json_data)) >= 1
143146
except Exception as e:
144-
print(f"Error raised in is_field_completed : {e}")
147+
logger.error(f"Error raised in is_field_completed : {e}")
145148
raise
146149

147150

@@ -158,7 +161,7 @@ def get_field_value(json_data: Dict[str, Any], json_path: str):
158161
return matches[0].value
159162

160163
except Exception as e:
161-
print(f"Error raised in is_field_completed : {e}")
164+
logger.error(f"Error raised in is_field_completed : {e}")
162165
raise
163166

164167

@@ -194,7 +197,7 @@ def update_json_value(data, jsonpath_query, new_value):
194197
match.full_path.update(data, new_value)
195198

196199
except Exception as e:
197-
print(f"Error raised in update_json_value: {e}")
200+
logger.error(f"Error raised in update_json_value: {e}")
198201
raise
199202

200203

0 commit comments

Comments
 (0)