Skip to content

Commit 14e8214

Browse files
committed
♻️ Add custom K8s job execution that retrieves pod logs before cleanup to fix error reporting when Matrix Profile jobs fail
1 parent fd8ca6e commit 14e8214

2 files changed

Lines changed: 338 additions & 61 deletions

File tree

moderate/moderate/matrix_profile/__init__.py

Lines changed: 55 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,12 @@
1616
op,
1717
sensor,
1818
)
19-
from dagster_k8s import execute_k8s_job
2019
from pydantic import BaseModel, ValidationError
2120

2221
from moderate.enums import Variables
2322
from moderate.matrix_profile.log_utils import (
23+
execute_k8s_job_with_log_capture,
2424
extract_error_summary,
25-
get_current_namespace,
26-
parse_job_name_from_exception,
27-
retrieve_pod_logs,
2825
upload_logs_to_s3,
2926
)
3027
from moderate.resources import (
@@ -80,52 +77,48 @@ class MatrixProfileJobResult:
8077

8178
def handle_job_failure(
8279
context: OpExecutionContext,
83-
exception: Exception,
8480
config: MatrixProfileJobConfig,
8581
s3_object_storage: S3ObjectStorageResource,
82+
logs: Union[str, None],
83+
error_message: Union[str, None],
8684
) -> MatrixProfileJobResult:
87-
context.log.error("Failed to run Matrix Profile job: %s", exception)
85+
"""Handle a failed Matrix Profile job by processing logs and creating result.
86+
87+
Args:
88+
context: Dagster op execution context.
89+
config: Job configuration.
90+
s3_object_storage: S3 storage resource for uploading logs.
91+
logs: Pod logs retrieved before job cleanup (may be None).
92+
error_message: Error message from job failure.
93+
94+
Returns:
95+
MatrixProfileJobResult with error information.
96+
"""
97+
context.log.error("Matrix Profile job failed: %s", error_message)
8898

89-
# Attempt to retrieve and process pod logs for user-friendly error reporting
9099
error_summary = None
91100
error_logs_key = None
92101

93-
job_name = parse_job_name_from_exception(exception)
94-
95-
if job_name:
96-
namespace = get_current_namespace()
97-
context.log.info(
98-
"Attempting to retrieve logs for job %s in namespace %s",
99-
job_name,
100-
namespace,
102+
if logs:
103+
# Extract user-friendly error summary
104+
error_summary = extract_error_summary(logs)
105+
context.log.debug("Extracted error summary: %s", error_summary)
106+
107+
# Upload full logs to S3 for reference
108+
s3_client = s3_object_storage.get_client()
109+
error_logs_key, upload_error = upload_logs_to_s3(
110+
s3_client=s3_client,
111+
bucket=s3_object_storage.job_outputs_bucket_name,
112+
logs=logs,
113+
workflow_job_id=config.workflow_job_id,
101114
)
102115

103-
logs, logs_error = retrieve_pod_logs(namespace=namespace, job_name=job_name)
104-
105-
if logs:
106-
# Extract user-friendly error summary
107-
error_summary = extract_error_summary(logs)
108-
context.log.debug("Extracted error summary: %s", error_summary)
109-
110-
# Upload full logs to S3 for reference
111-
s3_client = s3_object_storage.get_client()
112-
error_logs_key, upload_error = upload_logs_to_s3(
113-
s3_client=s3_client,
114-
bucket=s3_object_storage.job_outputs_bucket_name,
115-
logs=logs,
116-
workflow_job_id=config.workflow_job_id,
117-
)
118-
119-
if upload_error:
120-
context.log.warning("Failed to upload logs to S3: %s", upload_error)
121-
else:
122-
context.log.warning("Failed to retrieve pod logs: %s", logs_error)
116+
if upload_error:
117+
context.log.warning("Failed to upload logs to S3: %s", upload_error)
123118
else:
124-
context.log.warning(
125-
"Could not parse job name from exception, unable to retrieve pod logs"
126-
)
119+
context.log.warning("No pod logs available for error analysis")
127120

128-
# Fall back to original exception message if no summary could be extracted
121+
# Fall back to generic message if no summary could be extracted
129122
if not error_summary:
130123
error_summary = (
131124
"The analysis job failed. Please check the input data format "
@@ -153,27 +146,32 @@ def run_matrix_profile(
153146
"Running Matrix Profile job (image=%s) (output_key=%s)", image, output_key
154147
)
155148

156-
env_vars = {
157-
"S3_ACCESS_KEY_ID": s3_object_storage.access_key_id,
158-
"S3_SECRET_ACCESS_KEY": s3_object_storage.secret_access_key,
159-
"OUTPUT_KEY": output_key,
160-
"OUTPUT_BUCKET": config.output_bucket,
161-
"FILE_URL": config.file_url,
162-
"ANALYSIS_VARIABLE": config.analysis_variable,
163-
}
164-
165-
env_vars = ["{}={}".format(k, v) for k, v in env_vars.items()]
149+
env_vars = [
150+
"S3_ACCESS_KEY_ID={}".format(s3_object_storage.access_key_id),
151+
"S3_SECRET_ACCESS_KEY={}".format(s3_object_storage.secret_access_key),
152+
"OUTPUT_KEY={}".format(output_key),
153+
"OUTPUT_BUCKET={}".format(config.output_bucket),
154+
"FILE_URL={}".format(config.file_url),
155+
"ANALYSIS_VARIABLE={}".format(config.analysis_variable),
156+
]
157+
158+
# Use custom job execution that captures logs before cleanup on failure
159+
job_result = execute_k8s_job_with_log_capture(
160+
context=context,
161+
image=image,
162+
env_vars=env_vars,
163+
image_pull_policy=config.image_pull_policy,
164+
timeout=config.timeout_secs,
165+
)
166166

167-
try:
168-
execute_k8s_job(
167+
if not job_result.success:
168+
return handle_job_failure(
169169
context=context,
170-
image=image,
171-
image_pull_policy=config.image_pull_policy,
172-
timeout=config.timeout_secs,
173-
env_vars=env_vars,
170+
config=config,
171+
s3_object_storage=s3_object_storage,
172+
logs=job_result.logs,
173+
error_message=job_result.error_message,
174174
)
175-
except Exception as ex:
176-
return handle_job_failure(context, ex, config, s3_object_storage)
177175

178176
context.log.info(
179177
"Checking if output report exists: s3://%s/%s",

0 commit comments

Comments
 (0)