Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package site.icebang.domain.log.mapper;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;

import site.icebang.domain.workflow.dto.ExecutionLogDto;
import site.icebang.domain.workflow.dto.log.WorkflowLogQueryCriteria;

@Mapper
public interface ExecutionLogMapper {
List<ExecutionLogDto> selectLogsByCriteria(WorkflowLogQueryCriteria criteria);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package site.icebang.domain.log.service;

import java.util.List;

import org.springframework.stereotype.Service;

import lombok.RequiredArgsConstructor;

import site.icebang.domain.log.mapper.ExecutionLogMapper;
import site.icebang.domain.workflow.dto.ExecutionLogDto;
import site.icebang.domain.workflow.dto.log.WorkflowLogQueryCriteria;

@Service
@RequiredArgsConstructor
public class ExecutionLogService {
private final ExecutionLogMapper executionLogMapper;

public List<ExecutionLogDto> getRawLogs(WorkflowLogQueryCriteria criteria) {
return executionLogMapper.selectLogsByCriteria(criteria);
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
package site.icebang.domain.workflow.controller;

import java.util.List;

import org.springframework.web.bind.annotation.*;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;

import site.icebang.common.dto.ApiResponse;
import site.icebang.common.dto.PageParams;
import site.icebang.common.dto.PageResult;
import site.icebang.domain.log.service.ExecutionLogService;
import site.icebang.domain.workflow.dto.WorkflowHistoryDTO;
import site.icebang.domain.workflow.dto.WorkflowRunDetailResponse;
import site.icebang.domain.workflow.dto.log.ExecutionLogSimpleDto;
import site.icebang.domain.workflow.dto.log.WorkflowLogQueryCriteria;
import site.icebang.domain.workflow.service.WorkflowHistoryService;

@RestController
@RequestMapping("/v0/workflow-runs")
@RequiredArgsConstructor
public class WorkflowHistoryController {
private final WorkflowHistoryService workflowHistoryService;
private final ExecutionLogService executionLogService;

@GetMapping("")
public ApiResponse<PageResult<WorkflowHistoryDTO>> getWorkflowHistoryList(
Expand All @@ -35,4 +42,11 @@ public ApiResponse<WorkflowRunDetailResponse> getWorkflowRunDetail(@PathVariable
WorkflowRunDetailResponse response = workflowHistoryService.getWorkflowRunDetail(runId);
return ApiResponse.success(response);
}

@GetMapping("/logs")
public ApiResponse<List<ExecutionLogSimpleDto>> getTaskExecutionLog(
@Valid @ModelAttribute WorkflowLogQueryCriteria requestDto) {
return ApiResponse.success(
ExecutionLogSimpleDto.from(executionLogService.getRawLogs(requestDto)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,6 @@ public class ExecutionLogDto {
private String logMessage;
private Instant executedAt;
private Integer durationMs;
private String traceId;
private String errorCode;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package site.icebang.domain.workflow.dto.log;

import java.time.Instant;
import java.util.List;
import java.util.stream.Collectors;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import site.icebang.domain.workflow.dto.ExecutionLogDto;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ExecutionLogSimpleDto {
private String logLevel;
private String logMessage;
private Instant executedAt;

public static ExecutionLogSimpleDto from(ExecutionLogDto executionLogDto) {
return ExecutionLogSimpleDto.builder()
.logLevel(executionLogDto.getLogLevel())
.logMessage(executionLogDto.getLogMessage())
.executedAt(executionLogDto.getExecutedAt())
.build();
}

public static List<ExecutionLogSimpleDto> from(List<ExecutionLogDto> executionLogList) {
return executionLogList.stream().map(ExecutionLogSimpleDto::from).collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package site.icebang.domain.workflow.dto.log;

public enum ExecutionType {
WORKFLOW,
JOB,
TASK
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package site.icebang.domain.workflow.dto.log;

import java.util.List;
import java.util.stream.Collectors;

import site.icebang.domain.workflow.dto.ExecutionLogDto;

public record TaskExecutionMessagesDto(List<String> messages) {
public static TaskExecutionMessagesDto from(List<ExecutionLogDto> executionLogList) {
List<String> messages =
executionLogList.stream().map(ExecutionLogDto::getLogMessage).collect(Collectors.toList());

return new TaskExecutionMessagesDto(messages);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package site.icebang.domain.workflow.dto.log;

import java.math.BigInteger;

import jakarta.validation.constraints.Pattern;
import lombok.Builder;
import lombok.Data;

@Data
@Builder
public class WorkflowLogQueryCriteria {
private final String traceId;
private final BigInteger sourceId;

@Pattern(regexp = "^(WORKFLOW|JOB|TASK)$", message = "실행 타입은 WORKFLOW, JOB, TASK 중 하나여야 합니다")
private final String executionType;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import java.time.Instant;
import java.util.UUID;

import org.slf4j.MDC;

import lombok.Getter;
import lombok.NoArgsConstructor;

Expand All @@ -20,7 +22,8 @@ public class WorkflowRun {

private WorkflowRun(Long workflowId) {
this.workflowId = workflowId;
this.traceId = UUID.randomUUID().toString(); // 고유 추적 ID 생성
// MDC에서 현재 요청의 traceId를 가져오거나, 없으면 새로 생성
this.traceId = MDC.get("traceId") != null ? MDC.get("traceId") : UUID.randomUUID().toString();
this.status = "RUNNING";
this.startedAt = Instant.now();
this.createdAt = this.startedAt;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="site.icebang.domain.log.mapper.ExecutionLogMapper">

<resultMap id="ExecutionLogResultMap" type="site.icebang.domain.workflow.dto.ExecutionLogDto">
<id property="id" column="id"/>
<result property="executionType" column="execution_type"/>
<result property="sourceId" column="source_id"/>
<result property="runId" column="run_id"/>
<result property="logLevel" column="log_level"/>
<result property="status" column="status"/>
<result property="logMessage" column="log_message"/>
<result property="executedAt" column="executed_at"/>
<result property="durationMs" column="duration_ms"/>
<result property="traceId" column="trace_id"/>
<result property="errorCode" column="error_code"/>
</resultMap>

<select id="selectLogsByCriteria" resultMap="ExecutionLogResultMap">
SELECT
id,
execution_type,
source_id,
run_id,
log_level,
status,
log_message,
executed_at,
duration_ms,
trace_id,
error_code
FROM
execution_log

<where>
<if test="traceId != null and traceId != ''">
AND trace_id = #{traceId}
</if>

<if test="sourceId != null">
AND source_id = #{sourceId}
</if>

<if test="executionType != null and executionType != ''">
AND execution_type = #{executionType}
</if>

</where>

ORDER BY executed_at DESC

</select>

</mapper>
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
-- execution_log 테스트 데이터 (H2용)
INSERT INTO execution_log (execution_type, source_id, log_level, executed_at, log_message, trace_id, run_id, status, duration_ms, error_code) VALUES
('WORKFLOW', 1, 'INFO', '2025-09-26 12:42:02.000', '========== 워크플로우 실행 시작: WorkflowId=1 ==========', '68d60b8a2f4cd59a880cf71f189b4ca5', NULL, NULL, NULL, NULL),
('WORKFLOW', 1, 'INFO', '2025-09-26 12:42:02.000', '총 2개의 Job을 순차적으로 실행합니다.', '68d60b8a2f4cd59a880cf71f189b4ca5', NULL, NULL, NULL, NULL),
('JOB', 1, 'INFO', '2025-09-26 12:42:02.000', '---------- Job 실행 시작: JobId=1, JobRunId=1 ----------', '68d60b8a2f4cd59a880cf71f189b4ca5', NULL, NULL, NULL, NULL),
('JOB', 1, 'INFO', '2025-09-26 12:42:02.000', 'Job (JobRunId=1) 내 총 7개의 Task를 순차 실행합니다.', '68d60b8a2f4cd59a880cf71f189b4ca5', NULL, NULL, NULL, NULL),
('TASK', 1, 'INFO', '2025-09-26 12:42:02.000', 'Task 실행 시작: TaskId=1, Name=키워드 검색 태스크', '68d60b8a2f4cd59a880cf71f189b4ca5', NULL, NULL, NULL, NULL),
('TASK', 1, 'INFO', '2025-09-26 12:42:02.000', 'Task 실행 시도 #1: TaskId=1, TaskRunId=1', '68d60b8a2f4cd59a880cf71f189b4ca5', NULL, NULL, NULL, NULL),
('TASK', 1, 'ERROR', '2025-09-26 12:42:02.000', 'Task 최종 실패: TaskRunId=1, Message=FastApiAdapter 호출에 실패했습니다.', '68d60b8a2f4cd59a880cf71f189b4ca5', NULL, NULL, NULL, NULL),
('TASK', 2, 'INFO', '2025-09-26 12:42:02.000', 'Task 실행 시작: TaskId=2, Name=상품 검색 태스크', '68d60b8a2f4cd59a880cf71f189b4ca5', NULL, NULL, NULL, NULL),
('TASK', 2, 'INFO', '2025-09-26 12:42:02.000', 'Task 실행 시도 #1: TaskId=2, TaskRunId=2', '68d60b8a2f4cd59a880cf71f189b4ca5', NULL, NULL, NULL, NULL),
('TASK', 2, 'ERROR', '2025-09-26 12:42:02.000', 'Task 최종 실패: TaskRunId=2, Message=FastApiAdapter 호출에 실패했습니다.', '68d60b8a2f4cd59a880cf71f189b4ca5', NULL, NULL, NULL, NULL),
('TASK', 3, 'INFO', '2025-09-26 12:42:02.000', 'Task 실행 시작: TaskId=3, Name=상품 매칭 태스크', '68d60b8a2f4cd59a880cf71f189b4ca5', NULL, NULL, NULL, NULL),
('TASK', 3, 'INFO', '2025-09-26 12:42:02.000', 'Task 실행 시도 #1: TaskId=3, TaskRunId=3', '68d60b8a2f4cd59a880cf71f189b4ca5', NULL, NULL, NULL, NULL),
('TASK', 3, 'ERROR', '2025-09-26 12:42:02.000', 'Task 최종 실패: TaskRunId=3, Message=FastApiAdapter 호출에 실패했습니다.', '68d60b8a2f4cd59a880cf71f189b4ca5', NULL, NULL, NULL, NULL),
('TASK', 4, 'INFO', '2025-09-26 12:42:02.000', 'Task 실행 시작: TaskId=4, Name=상품 유사도 분석 태스크', '68d60b8a2f4cd59a880cf71f189b4ca5', NULL, NULL, NULL, NULL),
('TASK', 4, 'INFO', '2025-09-26 12:42:02.000', 'Task 실행 시도 #1: TaskId=4, TaskRunId=4', '68d60b8a2f4cd59a880cf71f189b4ca5', NULL, NULL, NULL, NULL),
('TASK', 4, 'ERROR', '2025-09-26 12:42:02.000', 'Task 최종 실패: TaskRunId=4, Message=FastApiAdapter 호출에 실패했습니다.', '68d60b8a2f4cd59a880cf71f189b4ca5', NULL, NULL, NULL, NULL),
('TASK', 5, 'INFO', '2025-09-26 12:42:02.000', 'Task 실행 시작: TaskId=5, Name=상품 정보 크롤링 태스크', '68d60b8a2f4cd59a880cf71f189b4ca5', NULL, NULL, NULL, NULL),
('TASK', 5, 'INFO', '2025-09-26 12:42:02.000', 'Task 실행 시도 #1: TaskId=5, TaskRunId=5', '68d60b8a2f4cd59a880cf71f189b4ca5', NULL, NULL, NULL, NULL),
('TASK', 5, 'ERROR', '2025-09-26 12:42:02.000', 'Task 최종 실패: TaskRunId=5, Message=FastApiAdapter 호출에 실패했습니다.', '68d60b8a2f4cd59a880cf71f189b4ca5', NULL, NULL, NULL, NULL),
('TASK', 6, 'INFO', '2025-09-26 12:42:02.000', 'Task 실행 시작: TaskId=6, Name=S3 업로드 태스크', '68d60b8a2f4cd59a880cf71f189b4ca5', NULL, NULL, NULL, NULL),
('TASK', 6, 'INFO', '2025-09-26 12:42:02.000', 'Task 실행 시도 #1: TaskId=6, TaskRunId=6', '68d60b8a2f4cd59a880cf71f189b4ca5', NULL, NULL, NULL, NULL),
('TASK', 6, 'ERROR', '2025-09-26 12:42:02.000', 'Task 최종 실패: TaskRunId=6, Message=FastApiAdapter 호출에 실패했습니다.', '68d60b8a2f4cd59a880cf71f189b4ca5', NULL, NULL, NULL, NULL),
('TASK', 7, 'INFO', '2025-09-26 12:42:02.000', 'Task 실행 시작: TaskId=7, Name=상품 선택 태스크', '68d60b8a2f4cd59a880cf71f189b4ca5', NULL, NULL, NULL, NULL),
('TASK', 7, 'INFO', '2025-09-26 12:42:02.000', 'Task 실행 시도 #1: TaskId=7, TaskRunId=7', '68d60b8a2f4cd59a880cf71f189b4ca5', NULL, NULL, NULL, NULL),
('TASK', 7, 'ERROR', '2025-09-26 12:42:03.000', 'Task 최종 실패: TaskRunId=7, Message=FastApiAdapter 호출에 실패했습니다.', '68d60b8a2f4cd59a880cf71f189b4ca5', NULL, NULL, NULL, NULL),
('JOB', 1, 'ERROR', '2025-09-26 12:42:03.000', 'Job 실행 실패: JobRunId=1', '68d60b8a2f4cd59a880cf71f189b4ca5', NULL, NULL, NULL, NULL),
('JOB', 2, 'INFO', '2025-09-26 12:42:03.000', '---------- Job 실행 시작: JobId=2, JobRunId=2 ----------', '68d60b8a2f4cd59a880cf71f189b4ca5', NULL, NULL, NULL, NULL),
('JOB', 2, 'INFO', '2025-09-26 12:42:03.000', 'Job (JobRunId=2) 내 총 2개의 Task를 순차 실행합니다.', '68d60b8a2f4cd59a880cf71f189b4ca5', NULL, NULL, NULL, NULL),
('TASK', 8, 'INFO', '2025-09-26 12:42:03.000', 'Task 실행 시작: TaskId=8, Name=블로그 RAG 생성 태스크', '68d60b8a2f4cd59a880cf71f189b4ca5', NULL, NULL, NULL, NULL),
('TASK', 8, 'INFO', '2025-09-26 12:42:03.000', 'Task 실행 시도 #1: TaskId=8, TaskRunId=8', '68d60b8a2f4cd59a880cf71f189b4ca5', NULL, NULL, NULL, NULL),
('TASK', 8, 'ERROR', '2025-09-26 12:42:03.000', 'Task 최종 실패: TaskRunId=8, Message=FastApiAdapter 호출에 실패했습니다.', '68d60b8a2f4cd59a880cf71f189b4ca5', NULL, NULL, NULL, NULL),
('TASK', 9, 'INFO', '2025-09-26 12:42:03.000', 'Task 실행 시작: TaskId=9, Name=블로그 발행 태스크', '68d60b8a2f4cd59a880cf71f189b4ca5', NULL, NULL, NULL, NULL),
('TASK', 9, 'INFO', '2025-09-26 12:42:03.000', 'Task 실행 시도 #1: TaskId=9, TaskRunId=9', '68d60b8a2f4cd59a880cf71f189b4ca5', NULL, NULL, NULL, NULL),
('TASK', 9, 'ERROR', '2025-09-26 12:42:03.000', 'Task 최종 실패: TaskRunId=9, Message=FastApiAdapter 호출에 실패했습니다.', '68d60b8a2f4cd59a880cf71f189b4ca5', NULL, NULL, NULL, NULL),
('JOB', 2, 'ERROR', '2025-09-26 12:42:03.000', 'Job 실행 실패: JobRunId=2', '68d60b8a2f4cd59a880cf71f189b4ca5', NULL, NULL, NULL, NULL),
('WORKFLOW', 1, 'INFO', '2025-09-26 12:42:03.000', '========== 워크플로우 실행 실패 : WorkflowRunId=1 ==========', '68d60b8a2f4cd59a880cf71f189b4ca5', NULL, NULL, NULL, NULL);
Loading
Loading