Skip to content

Commit d604bca

Browse files
committed
feat: follows logging standadrds in aind-log-utils
1 parent 1e14988 commit d604bca

10 files changed

Lines changed: 307 additions & 31 deletions

File tree

log_config_template.yaml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
---
2+
version: 1
3+
disable_existing_loggers: false
4+
formatters:
5+
default:
6+
"()": aind_airflow_jobs.CustomJsonFormatter
7+
format: "%(asctime)s %(levelname)s %(module)s %(lineno)s %(message)s"
8+
reserved_attrs:
9+
- args
10+
- asctime
11+
- created
12+
- exc_info
13+
- exc_text
14+
- filename
15+
- funcName
16+
- levelname
17+
- levelno
18+
- lineno
19+
- message
20+
- module
21+
- msecs
22+
- msg
23+
- name
24+
- pathname
25+
- process
26+
- processName
27+
- relativeCreated
28+
- stack_info
29+
- taskName
30+
- thread
31+
- threadName
32+
- color_message
33+
rename_fields:
34+
"asctime": "timestamp"
35+
"levelname": "level"
36+
static_fields:
37+
"environment": "local"
38+
"software_version": ext://aind_airflow_jobs.__version__
39+
"software_name": "aind-airflow-jobs"
40+
handlers:
41+
console:
42+
class: logging.StreamHandler
43+
formatter: default
44+
level: INFO
45+
stream: ext://sys.stdout
46+
loggers:
47+
"":
48+
handlers:
49+
- console
50+
level: INFO
51+
propagate: true
52+
uvicorn.error:
53+
handlers:
54+
- console
55+
level: INFO
56+
propagate: false
57+
uvicorn.access:
58+
handlers:
59+
- console
60+
level: INFO
61+
propagate: false

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ readme = "README.md"
1717
dynamic = ["version"]
1818

1919
dependencies = [
20+
"log-json",
21+
"PyYAML>=6.0,<7.0",
2022
"pydantic-settings>=2.2.0,<3.0",
2123
"pydantic>=2.2.0,<3.0"
2224
]

src/aind_airflow_jobs/__init__.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,44 @@
1-
"""Package to manage airflow jobs"""
1+
"""Package to manage airflow jobs."""
2+
3+
import logging
4+
import logging.config
5+
import os
6+
from datetime import datetime, timezone
7+
from logging import LogRecord
8+
9+
import log_json
10+
import yaml
211

312
__version__ = "0.4.2"
13+
14+
15+
# We want to standardize the timestamp format to UTC and ISO-8601, which
16+
# requires a custom formatter and cannot be done through configuration only.
17+
class CustomJsonFormatter(log_json.JsonFormatter):
18+
"""Custom class to format log timestamps as ISO-8601 UTC."""
19+
20+
def formatTime(self, record: LogRecord, datefmt=None) -> str:
21+
"""
22+
Format timestamp as ISO-8601 UTC.
23+
24+
Parameters
25+
----------
26+
record : LogRecord
27+
datefmt : str, optional
28+
Unused parameter, kept for signature compatibility.
29+
30+
Returns
31+
-------
32+
str
33+
34+
"""
35+
dt = datetime.fromtimestamp(record.created, tz=timezone.utc)
36+
return dt.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
37+
38+
39+
if os.path.isfile(os.getenv("LOGGING_CONFIG_FILE", "log_config.yaml")):
40+
config_path = os.getenv("LOGGING_CONFIG_FILE", "log_config.yaml")
41+
with open(config_path, "rt", encoding="utf-8") as f:
42+
config = yaml.safe_load(f.read())
43+
logging.config.dictConfig(config)
44+
logging.info("Found logging file at: %s", config_path)

src/aind_airflow_jobs/dag_tasks/base.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,10 @@ def run_task(self):
3030
)
3131

3232
task_func()
33-
logging.info(f"Task '{task_id}' completed successfully!")
33+
logging.info(
34+
f"Task '{task_id}' completed successfully!",
35+
extra={
36+
"process_name": task_id,
37+
"pipeline_name": "airflow DAG",
38+
},
39+
)

src/aind_airflow_jobs/dag_tasks/check_connections.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,20 @@ def check_param_store_connection(self):
3030
ams_uri = os.getenv("AMS_URI")
3131
co_uri = os.getenv("CO_URI")
3232

33-
logging.info(f"default_transfer_settings: {default_transfer_settings}")
34-
logging.info(f"ams_uri: {ams_uri}")
33+
logging.info(
34+
f"default_transfer_settings: {default_transfer_settings}",
35+
extra={
36+
"process_name": "check_param_store_connection",
37+
"pipeline_name": "airflow DAG",
38+
},
39+
)
40+
logging.info(
41+
f"ams_uri: {ams_uri}",
42+
extra={
43+
"process_name": "check_param_store_connection",
44+
"pipeline_name": "airflow DAG",
45+
},
46+
)
3547

3648
if not default_transfer_settings:
3749
raise AssertionError(
@@ -66,7 +78,13 @@ def check_slurm_connection(self):
6678
settings = SlurmClientSettings()
6779
slurm_api = settings.create_api_client()
6880
response = slurm_api.slurm_v0040_get_ping()
69-
logging.info(f"SLURM ping response: {response}")
81+
logging.info(
82+
f"SLURM ping response: {response}",
83+
extra={
84+
"process_name": "check_slurm_connection",
85+
"pipeline_name": "airflow DAG",
86+
},
87+
)
7088

7189
def check_vast_connection(self):
7290
"""Check that airflow can read files from VAST."""
@@ -89,7 +107,11 @@ def check_hpc_connection(self):
89107
ssh_command_output = self.airflow_task_settings.task_input_str or ""
90108
decoded = base64.b64decode(ssh_command_output).decode("utf-8")
91109
logging.info(
92-
f"SSH Command Output: {ssh_command_output}. Decoded: {decoded}"
110+
f"SSH Command Output: {ssh_command_output}. Decoded: {decoded}",
111+
extra={
112+
"process_name": "check_hpc_connection",
113+
"pipeline_name": "airflow DAG",
114+
},
93115
)
94116

95117
if decoded.strip() != "Hello World":

src/aind_airflow_jobs/handlers/alert_handler.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,13 @@ def send_log_message(
8686
level = level_name if isinstance(level_name, int) else logging.INFO
8787
logger = logging.getLogger(__name__)
8888
logger.setLevel(logging.INFO)
89-
logger.log(level, sanitized_message)
89+
logger.log(
90+
level,
91+
sanitized_message,
92+
extra={
93+
"acquisition_name": job_name,
94+
"process_name": task_id,
95+
"pipeline_name": "airflow DAG",
96+
"run_id": run_id,
97+
},
98+
)

src/aind_airflow_jobs/handlers/slurm_v2_handler.py

Lines changed: 111 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,13 @@ def _requeue_failed_jobs(self, job_list: List[V0040JobInfo]) -> bool:
347347
)
348348
logging.warning(
349349
f"Restarting {job.job_state} job: {job_id_to_retry}. "
350-
f"Restart count: {restart_count}"
350+
f"Restart count: {restart_count}",
351+
extra={
352+
"process_name": "submit_slurm_job_array",
353+
"pipeline_name": "airflow DAG",
354+
"job_id": job_id_to_retry,
355+
"job_state": job.job_state,
356+
},
351357
)
352358
sleep(1)
353359
command = f"scontrol requeue {job_id_to_retry}"
@@ -463,11 +469,27 @@ def _monitor_job(
463469
last_backfill=V0040Uint64NoVal(),
464470
last_update=V0040Uint64NoVal(),
465471
)
466-
logging.debug(f"Initialized placeholder: {job_response}")
472+
logging.debug(
473+
f"Initialized placeholder: {job_response}",
474+
extra={
475+
"process_name": "monitor_slurm_job",
476+
"pipeline_name": "airflow DAG",
477+
"acquisition_name": job_name,
478+
"job_id": job_id,
479+
},
480+
)
467481
try:
468482
job_response = self.slurm.slurm_v0040_get_job(job_id=str(job_id))
469483
except NotFoundException:
470-
logging.warning("Looking for job info in database...")
484+
logging.warning(
485+
"Looking for job info in database...",
486+
extra={
487+
"process_name": "monitor_slurm_job",
488+
"pipeline_name": "airflow DAG",
489+
"acquisition_name": job_name,
490+
"job_id": job_id,
491+
},
492+
)
471493
slurm_db_api = SlurmdbApi(api_client=self.slurm.api_client)
472494
slurm_db_response = slurm_db_api.slurmdb_v0040_get_job(
473495
job_id=str(job_id)
@@ -509,7 +531,15 @@ def _monitor_job(
509531
"start_time": start_time,
510532
}
511533
)
512-
logging.info(message)
534+
logging.info(
535+
message,
536+
extra={
537+
"process_name": "monitor_slurm_job",
538+
"pipeline_name": "airflow DAG",
539+
"acquisition_name": job_name,
540+
"job_id": job_id,
541+
},
542+
)
513543
job_status = dict()
514544
is_finished, is_error = self._check_job_status(
515545
job_response, job_status
@@ -543,7 +573,15 @@ def _monitor_job(
543573
"start_time": start_time,
544574
}
545575
)
546-
logging.info(message)
576+
logging.info(
577+
message,
578+
extra={
579+
"process_name": "monitor_slurm_job",
580+
"pipeline_name": "airflow DAG",
581+
"acquisition_name": job_name,
582+
"job_id": job_id,
583+
},
584+
)
547585
is_finished, is_error = self._check_job_status(
548586
job_response, job_status
549587
)
@@ -562,14 +600,30 @@ def _monitor_job(
562600
remote_mnt_dir=self.remote_mnt_dir,
563601
local_mnt_dir=self.local_mnt_dir,
564602
)
565-
logging.exception(f"std_err:\n{std_err_msg}")
603+
logging.exception(
604+
f"std_err:\n{std_err_msg}",
605+
extra={
606+
"process_name": "monitor_slurm_job",
607+
"pipeline_name": "airflow DAG",
608+
"acquisition_name": job_name,
609+
"job_id": job_id,
610+
},
611+
)
566612
raise Exception(
567613
f"There were errors with the slurm job. "
568614
f"Job: {message}. "
569615
f"Errors: {errors}"
570616
)
571617
else:
572-
logging.info("Job is Finished!")
618+
logging.info(
619+
"Job is Finished!",
620+
extra={
621+
"process_name": "monitor_slurm_job",
622+
"pipeline_name": "airflow DAG",
623+
"acquisition_name": job_name,
624+
"job_id": job_id,
625+
},
626+
)
573627
return start_time, end_time
574628

575629
def _std_err_filepath(self, job_id: Union[int, str]) -> str:
@@ -595,10 +649,34 @@ def run_job(self):
595649
submit_response = self._submit_job()
596650
job_id = submit_response.job_id
597651
job_name = self.job_properties.name
598-
logging.info(f"Job Name: {job_name}")
599-
logging.info(f"Job ID: {job_id}")
652+
logging.info(
653+
f"Job Name: {job_name}",
654+
extra={
655+
"process_name": "run_slurm_job",
656+
"pipeline_name": "airflow DAG",
657+
"acquisition_name": job_name,
658+
"job_id": job_id,
659+
},
660+
)
661+
logging.info(
662+
f"Job ID: {job_id}",
663+
extra={
664+
"process_name": "run_slurm_job",
665+
"pipeline_name": "airflow DAG",
666+
"acquisition_name": job_name,
667+
"job_id": job_id,
668+
},
669+
)
600670
std_err = self._std_err_filepath(job_id=job_id)
601-
logging.info(f"Please check {std_err} for additional logs.")
671+
logging.info(
672+
f"Please check {std_err} for additional logs.",
673+
extra={
674+
"process_name": "run_slurm_job",
675+
"pipeline_name": "airflow DAG",
676+
"acquisition_name": job_name,
677+
"job_id": job_id,
678+
},
679+
)
602680
self._monitor_job(submit_response=submit_response)
603681

604682

@@ -657,7 +735,13 @@ def _requeue_failed_jobs(self, job_list: List[V0040JobInfo]) -> bool:
657735
)
658736
logging.warning(
659737
f"Restarting {job.job_state} job: {job_id_to_retry}. "
660-
f"Restart count: {restart_count}"
738+
f"Restart count: {restart_count}",
739+
extra={
740+
"process_name": "slurm_job_sensor",
741+
"pipeline_name": "airflow DAG",
742+
"job_id": job_id_to_retry,
743+
"job_state": job.job_state,
744+
},
661745
)
662746
sleep(1)
663747
command = f"scontrol requeue {job_id_to_retry}"
@@ -751,15 +835,29 @@ def get_job_status(self) -> Tuple[bool, int, int]:
751835
remote_mnt_dir=self.remote_mnt_dir,
752836
local_mnt_dir=self.local_mnt_dir,
753837
)
754-
logging.exception(f"std_err:\n{std_err_msg}")
838+
logging.exception(
839+
f"std_err:\n{std_err_msg}",
840+
extra={
841+
"process_name": "slurm_job_sensor",
842+
"pipeline_name": "airflow DAG",
843+
"job_id": self.job_id,
844+
},
845+
)
755846
raise Exception(
756847
f"There were errors with the slurm job. "
757848
f"Job: {self.job_id}. "
758849
f"Errors: {errors}"
759850
)
760851
return is_finished, start_time, end_time
761852
except NotFoundException:
762-
logging.warning("Looking for job info in database...")
853+
logging.warning(
854+
"Looking for job info in database...",
855+
extra={
856+
"process_name": "slurm_job_sensor",
857+
"pipeline_name": "airflow DAG",
858+
"job_id": job_id,
859+
},
860+
)
763861
slurm_db_api = SlurmdbApi(api_client=self.slurm.api_client)
764862
slurm_db_response = slurm_db_api.slurmdb_v0040_get_job(
765863
job_id=str(job_id)

0 commit comments

Comments
 (0)