300 lines
8.5 KiB
TypeScript
300 lines
8.5 KiB
TypeScript
|
|
// 배치 실행 로그 서비스
|
||
|
|
// 작성일: 2024-12-24
|
||
|
|
|
||
|
|
import prisma from "../config/database";
|
||
|
|
import {
|
||
|
|
BatchExecutionLog,
|
||
|
|
CreateBatchExecutionLogRequest,
|
||
|
|
UpdateBatchExecutionLogRequest,
|
||
|
|
BatchExecutionLogFilter,
|
||
|
|
BatchExecutionLogWithConfig
|
||
|
|
} from "../types/batchExecutionLogTypes";
|
||
|
|
import { ApiResponse } from "../types/batchTypes";
|
||
|
|
|
||
|
|
export class BatchExecutionLogService {
|
||
|
|
/**
|
||
|
|
* 배치 실행 로그 목록 조회
|
||
|
|
*/
|
||
|
|
static async getExecutionLogs(
|
||
|
|
filter: BatchExecutionLogFilter = {}
|
||
|
|
): Promise<ApiResponse<BatchExecutionLogWithConfig[]>> {
|
||
|
|
try {
|
||
|
|
const {
|
||
|
|
batch_config_id,
|
||
|
|
execution_status,
|
||
|
|
start_date,
|
||
|
|
end_date,
|
||
|
|
page = 1,
|
||
|
|
limit = 50
|
||
|
|
} = filter;
|
||
|
|
|
||
|
|
const skip = (page - 1) * limit;
|
||
|
|
const take = limit;
|
||
|
|
|
||
|
|
// WHERE 조건 구성
|
||
|
|
const where: any = {};
|
||
|
|
|
||
|
|
if (batch_config_id) {
|
||
|
|
where.batch_config_id = batch_config_id;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (execution_status) {
|
||
|
|
where.execution_status = execution_status;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (start_date || end_date) {
|
||
|
|
where.start_time = {};
|
||
|
|
if (start_date) {
|
||
|
|
where.start_time.gte = start_date;
|
||
|
|
}
|
||
|
|
if (end_date) {
|
||
|
|
where.start_time.lte = end_date;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 로그 조회
|
||
|
|
const [logs, total] = await Promise.all([
|
||
|
|
prisma.batch_execution_logs.findMany({
|
||
|
|
where,
|
||
|
|
include: {
|
||
|
|
batch_config: {
|
||
|
|
select: {
|
||
|
|
id: true,
|
||
|
|
batch_name: true,
|
||
|
|
description: true,
|
||
|
|
cron_schedule: true,
|
||
|
|
is_active: true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
orderBy: { start_time: 'desc' },
|
||
|
|
skip,
|
||
|
|
take
|
||
|
|
}),
|
||
|
|
prisma.batch_execution_logs.count({ where })
|
||
|
|
]);
|
||
|
|
|
||
|
|
return {
|
||
|
|
success: true,
|
||
|
|
data: logs as BatchExecutionLogWithConfig[],
|
||
|
|
pagination: {
|
||
|
|
page,
|
||
|
|
limit,
|
||
|
|
total,
|
||
|
|
totalPages: Math.ceil(total / limit)
|
||
|
|
}
|
||
|
|
};
|
||
|
|
} catch (error) {
|
||
|
|
console.error("배치 실행 로그 조회 실패:", error);
|
||
|
|
return {
|
||
|
|
success: false,
|
||
|
|
message: "배치 실행 로그 조회 중 오류가 발생했습니다.",
|
||
|
|
error: error instanceof Error ? error.message : "알 수 없는 오류"
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 배치 실행 로그 생성
|
||
|
|
*/
|
||
|
|
static async createExecutionLog(
|
||
|
|
data: CreateBatchExecutionLogRequest
|
||
|
|
): Promise<ApiResponse<BatchExecutionLog>> {
|
||
|
|
try {
|
||
|
|
const log = await prisma.batch_execution_logs.create({
|
||
|
|
data: {
|
||
|
|
batch_config_id: data.batch_config_id,
|
||
|
|
execution_status: data.execution_status,
|
||
|
|
start_time: data.start_time || new Date(),
|
||
|
|
end_time: data.end_time,
|
||
|
|
duration_ms: data.duration_ms,
|
||
|
|
total_records: data.total_records || 0,
|
||
|
|
success_records: data.success_records || 0,
|
||
|
|
failed_records: data.failed_records || 0,
|
||
|
|
error_message: data.error_message,
|
||
|
|
error_details: data.error_details,
|
||
|
|
server_name: data.server_name || process.env.HOSTNAME || 'unknown',
|
||
|
|
process_id: data.process_id || process.pid?.toString()
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
return {
|
||
|
|
success: true,
|
||
|
|
data: log as BatchExecutionLog,
|
||
|
|
message: "배치 실행 로그가 생성되었습니다."
|
||
|
|
};
|
||
|
|
} catch (error) {
|
||
|
|
console.error("배치 실행 로그 생성 실패:", error);
|
||
|
|
return {
|
||
|
|
success: false,
|
||
|
|
message: "배치 실행 로그 생성 중 오류가 발생했습니다.",
|
||
|
|
error: error instanceof Error ? error.message : "알 수 없는 오류"
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 배치 실행 로그 업데이트
|
||
|
|
*/
|
||
|
|
static async updateExecutionLog(
|
||
|
|
id: number,
|
||
|
|
data: UpdateBatchExecutionLogRequest
|
||
|
|
): Promise<ApiResponse<BatchExecutionLog>> {
|
||
|
|
try {
|
||
|
|
const log = await prisma.batch_execution_logs.update({
|
||
|
|
where: { id },
|
||
|
|
data: {
|
||
|
|
execution_status: data.execution_status,
|
||
|
|
end_time: data.end_time,
|
||
|
|
duration_ms: data.duration_ms,
|
||
|
|
total_records: data.total_records,
|
||
|
|
success_records: data.success_records,
|
||
|
|
failed_records: data.failed_records,
|
||
|
|
error_message: data.error_message,
|
||
|
|
error_details: data.error_details
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
return {
|
||
|
|
success: true,
|
||
|
|
data: log as BatchExecutionLog,
|
||
|
|
message: "배치 실행 로그가 업데이트되었습니다."
|
||
|
|
};
|
||
|
|
} catch (error) {
|
||
|
|
console.error("배치 실행 로그 업데이트 실패:", error);
|
||
|
|
return {
|
||
|
|
success: false,
|
||
|
|
message: "배치 실행 로그 업데이트 중 오류가 발생했습니다.",
|
||
|
|
error: error instanceof Error ? error.message : "알 수 없는 오류"
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 배치 실행 로그 삭제
|
||
|
|
*/
|
||
|
|
static async deleteExecutionLog(id: number): Promise<ApiResponse<void>> {
|
||
|
|
try {
|
||
|
|
await prisma.batch_execution_logs.delete({
|
||
|
|
where: { id }
|
||
|
|
});
|
||
|
|
|
||
|
|
return {
|
||
|
|
success: true,
|
||
|
|
message: "배치 실행 로그가 삭제되었습니다."
|
||
|
|
};
|
||
|
|
} catch (error) {
|
||
|
|
console.error("배치 실행 로그 삭제 실패:", error);
|
||
|
|
return {
|
||
|
|
success: false,
|
||
|
|
message: "배치 실행 로그 삭제 중 오류가 발생했습니다.",
|
||
|
|
error: error instanceof Error ? error.message : "알 수 없는 오류"
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 특정 배치의 최신 실행 로그 조회
|
||
|
|
*/
|
||
|
|
static async getLatestExecutionLog(
|
||
|
|
batchConfigId: number
|
||
|
|
): Promise<ApiResponse<BatchExecutionLog | null>> {
|
||
|
|
try {
|
||
|
|
const log = await prisma.batch_execution_logs.findFirst({
|
||
|
|
where: { batch_config_id: batchConfigId },
|
||
|
|
orderBy: { start_time: 'desc' }
|
||
|
|
});
|
||
|
|
|
||
|
|
return {
|
||
|
|
success: true,
|
||
|
|
data: log as BatchExecutionLog | null
|
||
|
|
};
|
||
|
|
} catch (error) {
|
||
|
|
console.error("최신 배치 실행 로그 조회 실패:", error);
|
||
|
|
return {
|
||
|
|
success: false,
|
||
|
|
message: "최신 배치 실행 로그 조회 중 오류가 발생했습니다.",
|
||
|
|
error: error instanceof Error ? error.message : "알 수 없는 오류"
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 배치 실행 통계 조회
|
||
|
|
*/
|
||
|
|
static async getExecutionStats(
|
||
|
|
batchConfigId?: number,
|
||
|
|
startDate?: Date,
|
||
|
|
endDate?: Date
|
||
|
|
): Promise<ApiResponse<{
|
||
|
|
total_executions: number;
|
||
|
|
success_count: number;
|
||
|
|
failed_count: number;
|
||
|
|
success_rate: number;
|
||
|
|
average_duration_ms: number;
|
||
|
|
total_records_processed: number;
|
||
|
|
}>> {
|
||
|
|
try {
|
||
|
|
const where: any = {};
|
||
|
|
|
||
|
|
if (batchConfigId) {
|
||
|
|
where.batch_config_id = batchConfigId;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (startDate || endDate) {
|
||
|
|
where.start_time = {};
|
||
|
|
if (startDate) {
|
||
|
|
where.start_time.gte = startDate;
|
||
|
|
}
|
||
|
|
if (endDate) {
|
||
|
|
where.start_time.lte = endDate;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const logs = await prisma.batch_execution_logs.findMany({
|
||
|
|
where,
|
||
|
|
select: {
|
||
|
|
execution_status: true,
|
||
|
|
duration_ms: true,
|
||
|
|
total_records: true
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
const total_executions = logs.length;
|
||
|
|
const success_count = logs.filter((log: any) => log.execution_status === 'SUCCESS').length;
|
||
|
|
const failed_count = logs.filter((log: any) => log.execution_status === 'FAILED').length;
|
||
|
|
const success_rate = total_executions > 0 ? (success_count / total_executions) * 100 : 0;
|
||
|
|
|
||
|
|
const validDurations = logs
|
||
|
|
.filter((log: any) => log.duration_ms !== null)
|
||
|
|
.map((log: any) => log.duration_ms!);
|
||
|
|
const average_duration_ms = validDurations.length > 0
|
||
|
|
? validDurations.reduce((sum: number, duration: number) => sum + duration, 0) / validDurations.length
|
||
|
|
: 0;
|
||
|
|
|
||
|
|
const total_records_processed = logs
|
||
|
|
.filter((log: any) => log.total_records !== null)
|
||
|
|
.reduce((sum: number, log: any) => sum + (log.total_records || 0), 0);
|
||
|
|
|
||
|
|
return {
|
||
|
|
success: true,
|
||
|
|
data: {
|
||
|
|
total_executions,
|
||
|
|
success_count,
|
||
|
|
failed_count,
|
||
|
|
success_rate,
|
||
|
|
average_duration_ms,
|
||
|
|
total_records_processed
|
||
|
|
}
|
||
|
|
};
|
||
|
|
} catch (error) {
|
||
|
|
console.error("배치 실행 통계 조회 실패:", error);
|
||
|
|
return {
|
||
|
|
success: false,
|
||
|
|
message: "배치 실행 통계 조회 중 오류가 발생했습니다.",
|
||
|
|
error: error instanceof Error ? error.message : "알 수 없는 오류"
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|