Skip to content

Commit a61bedd

Browse files
Add query job log correlation fields
1 parent bc6cb86 commit a61bedd

5 files changed

Lines changed: 335 additions & 239 deletions

File tree

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,20 @@
1-
from celery import Celery
1+
import logging
2+
3+
from celery import Celery, signals
4+
from clp_py_utils.clp_logging import set_formatter_on_handlers
25

36
from job_orchestration.executor.query import celeryconfig
47

58
app = Celery("query")
69
app.config_from_object(celeryconfig)
710

11+
12+
@signals.after_setup_logger.connect
13+
@signals.after_setup_task_logger.connect
14+
def configure_logger_formatters(logger: logging.Logger | None = None, **_kwargs: object) -> None:
15+
if logger is not None:
16+
set_formatter_on_handlers(logger)
17+
18+
819
if "__main__" == __name__:
920
app.start()

components/job-orchestration/job_orchestration/executor/query/extract_stream_task.py

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
StorageType,
1515
WorkerConfig,
1616
)
17-
from clp_py_utils.clp_logging import set_logging_level
17+
from clp_py_utils.clp_logging import bind_log_context, set_logging_level
1818
from clp_py_utils.s3_utils import (
1919
generate_s3_url,
2020
get_credential_env_vars,
@@ -28,13 +28,20 @@
2828
run_query_task,
2929
)
3030
from job_orchestration.executor.utils import load_worker_config
31+
from job_orchestration.scheduler.constants import QueryJobType
3132
from job_orchestration.scheduler.job_config import ExtractIrJobConfig, ExtractJsonJobConfig
3233
from job_orchestration.scheduler.scheduler_data import QueryTaskStatus
3334

3435
# Setup logging
3536
logger = get_task_logger(__name__)
3637

3738

39+
def _get_query_job_type(job_config: dict) -> str:
40+
if "orig_file_id" in job_config:
41+
return QueryJobType.EXTRACT_IR.name
42+
return QueryJobType.EXTRACT_JSON.name
43+
44+
3845
def _make_clp_command_and_env_vars(
3946
clp_home: Path,
4047
worker_config: WorkerConfig,
@@ -201,7 +208,6 @@ def extract_stream_entry_point(
201208
logger.info(f"Started {task_name} task for job {job_id}")
202209

203210
start_time = datetime.datetime.now()
204-
task_status: QueryTaskStatus
205211
sql_adapter = SqlAdapter(Database.model_validate(clp_metadata_db_conn_params))
206212

207213
# Load configuration
@@ -309,18 +315,26 @@ def extract_stream(
309315
results_cache_uri: str,
310316
dataset: str | None = None,
311317
) -> dict[str, Any]:
312-
try:
313-
return extract_stream_entry_point(
314-
job_id,
315-
task_id,
316-
job_config,
317-
archive_id,
318-
clp_metadata_db_conn_params,
319-
results_cache_uri,
320-
dataset,
321-
)
322-
except SoftTimeLimitExceeded:
323-
logger.exception(
324-
f"Stream extraction task job_id={job_id} task_id={task_id} exceeded soft time limit."
325-
)
326-
raise
318+
with bind_log_context(
319+
job_id=job_id,
320+
query_job_type=_get_query_job_type(job_config),
321+
task_id=task_id,
322+
archive_id=archive_id,
323+
dataset=dataset,
324+
celery_task_id=self.request.id,
325+
):
326+
try:
327+
return extract_stream_entry_point(
328+
job_id,
329+
task_id,
330+
job_config,
331+
archive_id,
332+
clp_metadata_db_conn_params,
333+
results_cache_uri,
334+
dataset,
335+
)
336+
except SoftTimeLimitExceeded:
337+
logger.exception(
338+
f"Stream extraction task job_id={job_id} task_id={task_id} exceeded soft time limit."
339+
)
340+
raise

components/job-orchestration/job_orchestration/executor/query/fs_search_task.py

Lines changed: 68 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import datetime
2+
import hashlib
23
import os
34
from pathlib import Path
45
from typing import Any
@@ -13,7 +14,7 @@
1314
StorageType,
1415
WorkerConfig,
1516
)
16-
from clp_py_utils.clp_logging import set_logging_level
17+
from clp_py_utils.clp_logging import bind_log_context, set_logging_level
1718
from clp_py_utils.s3_utils import (
1819
generate_s3_url,
1920
get_credential_env_vars,
@@ -34,6 +35,13 @@
3435
logger = get_task_logger(__name__)
3536

3637

38+
def _get_query_log_context(query_string: str) -> dict[str, str]:
39+
return {
40+
"query": query_string,
41+
"query_hash": hashlib.sha256(query_string.encode("utf-8")).hexdigest()[:16],
42+
}
43+
44+
3745
def _make_core_clp_command_and_env_vars(
3846
clp_home: Path,
3947
worker_config: WorkerConfig,
@@ -243,47 +251,48 @@ def search_entry_point(
243251
clp_home = Path(os.getenv("CLP_HOME"))
244252
search_config = SearchJobConfig.model_validate(msgpack.unpackb(job_config_blob))
245253

246-
task_command, core_clp_env_vars = _make_command_and_env_vars(
247-
clp_home=clp_home,
248-
worker_config=worker_config,
249-
archive_id=archive_id,
250-
search_config=search_config,
251-
results_cache_uri=results_cache_uri,
252-
results_collection=job_id,
253-
dataset=dataset,
254-
)
255-
if not task_command:
256-
logger.error(f"Error creating {task_name} command")
257-
return report_task_failure(
254+
with bind_log_context(**_get_query_log_context(search_config.query_string)):
255+
task_command, core_clp_env_vars = _make_command_and_env_vars(
256+
clp_home=clp_home,
257+
worker_config=worker_config,
258+
archive_id=archive_id,
259+
search_config=search_config,
260+
results_cache_uri=results_cache_uri,
261+
results_collection=job_id,
262+
dataset=dataset,
263+
)
264+
if not task_command:
265+
logger.error(f"Error creating {task_name} command")
266+
return report_task_failure(
267+
sql_adapter=sql_adapter,
268+
task_id=task_id,
269+
start_time=start_time,
270+
)
271+
272+
task_results, _ = run_query_task(
258273
sql_adapter=sql_adapter,
274+
logger=logger,
275+
clp_logs_dir=clp_logs_dir,
276+
task_command=task_command,
277+
env_vars=core_clp_env_vars,
278+
task_name=task_name,
279+
job_id=job_id,
259280
task_id=task_id,
260281
start_time=start_time,
261282
)
262283

263-
task_results, _ = run_query_task(
264-
sql_adapter=sql_adapter,
265-
logger=logger,
266-
clp_logs_dir=clp_logs_dir,
267-
task_command=task_command,
268-
env_vars=core_clp_env_vars,
269-
task_name=task_name,
270-
job_id=job_id,
271-
task_id=task_id,
272-
start_time=start_time,
273-
)
274-
275-
storage_config = worker_config.stream_output.storage
276-
if (
277-
StorageType.S3 == storage_config.type
278-
and search_config.write_to_file
279-
and QueryTaskStatus.SUCCEEDED == task_results.status
280-
):
281-
s3_config = storage_config.s3_config
282-
src_file = Path(worker_config.stream_output.get_directory()) / job_id / archive_id
283-
dest_path = f"{job_id}/{archive_id}"
284-
upload_results_to_s3(task_results, s3_config, src_file, dest_path)
284+
storage_config = worker_config.stream_output.storage
285+
if (
286+
StorageType.S3 == storage_config.type
287+
and search_config.write_to_file
288+
and QueryTaskStatus.SUCCEEDED == task_results.status
289+
):
290+
s3_config = storage_config.s3_config
291+
src_file = Path(worker_config.stream_output.get_directory()) / job_id / archive_id
292+
dest_path = f"{job_id}/{archive_id}"
293+
upload_results_to_s3(task_results, s3_config, src_file, dest_path)
285294

286-
return task_results.model_dump()
295+
return task_results.model_dump()
287296

288297

289298
@app.task(bind=True)
@@ -297,16 +306,26 @@ def search(
297306
results_cache_uri: str,
298307
dataset: str | None = None,
299308
) -> dict[str, Any]:
300-
try:
301-
return search_entry_point(
302-
job_id,
303-
task_id,
304-
job_config_blob,
305-
archive_id,
306-
clp_metadata_db_conn_params,
307-
results_cache_uri,
308-
dataset,
309-
)
310-
except SoftTimeLimitExceeded:
311-
logger.exception(f"Search task job_id={job_id} task_id={task_id} exceeded soft time limit.")
312-
raise
309+
with bind_log_context(
310+
job_id=job_id,
311+
query_job_type="SEARCH_OR_AGGREGATION",
312+
task_id=task_id,
313+
archive_id=archive_id,
314+
dataset=dataset,
315+
celery_task_id=self.request.id,
316+
):
317+
try:
318+
return search_entry_point(
319+
job_id,
320+
task_id,
321+
job_config_blob,
322+
archive_id,
323+
clp_metadata_db_conn_params,
324+
results_cache_uri,
325+
dataset,
326+
)
327+
except SoftTimeLimitExceeded:
328+
logger.exception(
329+
f"Search task job_id={job_id} task_id={task_id} exceeded soft time limit."
330+
)
331+
raise

components/job-orchestration/job_orchestration/executor/query/utils.py

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from typing import Any
1010

1111
from clp_py_utils.clp_config import QUERY_TASKS_TABLE_NAME
12+
from clp_py_utils.clp_logging import bind_log_context
1213
from clp_py_utils.sql_adapter import SqlAdapter
1314

1415
from job_orchestration.executor.utils import log_file_contents
@@ -53,6 +54,8 @@ def run_query_task(
5354
) -> tuple[QueryTaskResult, str]:
5455
clo_log_path = get_task_log_file_path(clp_logs_dir, job_id, task_id)
5556
clo_log_file = open(clo_log_path, "w")
57+
clo_log_file.write(f"job_id={job_id} task_id={task_id} task_name={task_name}\n")
58+
clo_log_file.flush()
5659

5760
task_status = QueryTaskStatus.RUNNING
5861
update_query_task_metadata(
@@ -69,34 +72,36 @@ def run_query_task(
6972
env=env_vars,
7073
)
7174

72-
def sigterm_handler(_signo, _stack_frame):
73-
logger.debug("Entered sigterm handler")
74-
if task_proc.poll() is None:
75-
logger.debug(f"Trying to kill {task_name} process")
76-
# Kill the process group in case the task process also forked
77-
os.killpg(os.getpgid(task_proc.pid), signal.SIGTERM)
78-
os.waitpid(task_proc.pid, 0)
79-
logger.info(f"Cancelling {task_name} task.")
80-
# Add 128 to follow convention for exit codes from signals
81-
# https://tldp.org/LDP/abs/html/exitcodes.html#AEN23549
82-
sys.exit(_signo + 128)
83-
84-
# Register the function to kill the child process at exit
85-
signal.signal(signal.SIGTERM, sigterm_handler)
86-
87-
logger.info(f"Waiting for {task_name} to finish")
88-
# `communicate` is equivalent to `wait` in this case, but avoids deadlocks when piping to
89-
# stdout/stderr.
90-
stdout_data, _ = task_proc.communicate()
91-
return_code = task_proc.returncode
92-
if 0 != return_code:
93-
task_status = QueryTaskStatus.FAILED
94-
logger.error(
95-
f"{task_name} task {task_id} failed for job {job_id} - return_code={return_code}"
96-
)
97-
else:
98-
task_status = QueryTaskStatus.SUCCEEDED
99-
logger.info(f"{task_name} task {task_id} completed for job {job_id}")
75+
with bind_log_context(clp_subprocess_pid=task_proc.pid):
76+
77+
def sigterm_handler(_signo, _stack_frame):
78+
logger.debug("Entered sigterm handler")
79+
if task_proc.poll() is None:
80+
logger.debug(f"Trying to kill {task_name} process")
81+
# Kill the process group in case the task process also forked
82+
os.killpg(os.getpgid(task_proc.pid), signal.SIGTERM)
83+
os.waitpid(task_proc.pid, 0)
84+
logger.info(f"Cancelling {task_name} task.")
85+
# Add 128 to follow convention for exit codes from signals
86+
# https://tldp.org/LDP/abs/html/exitcodes.html#AEN23549
87+
sys.exit(_signo + 128)
88+
89+
# Register the function to kill the child process at exit
90+
signal.signal(signal.SIGTERM, sigterm_handler)
91+
92+
logger.info(f"Waiting for {task_name} to finish")
93+
# `communicate` is equivalent to `wait` in this case, but avoids deadlocks when piping to
94+
# stdout/stderr.
95+
stdout_data, _ = task_proc.communicate()
96+
return_code = task_proc.returncode
97+
if 0 != return_code:
98+
task_status = QueryTaskStatus.FAILED
99+
logger.error(
100+
f"{task_name} task {task_id} failed for job {job_id} - return_code={return_code}"
101+
)
102+
else:
103+
task_status = QueryTaskStatus.SUCCEEDED
104+
logger.info(f"{task_name} task {task_id} completed for job {job_id}")
100105

101106
clo_log_file.close()
102107
if 0 != return_code:

0 commit comments

Comments
 (0)