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
Expand Up @@ -17,5 +17,6 @@ public class InstanceShutdownHandler {
public void onShutdown() {
log.info("Application is shutting down. Uploading today's log to S3...");
logService.uploadDailyLog("today");
log.info("Uploaded Log to S3 with PreDestroy");
}
}
69 changes: 0 additions & 69 deletions src/main/java/LuckyVicky/backend/global/s3/LogFileManager.java

This file was deleted.

94 changes: 77 additions & 17 deletions src/main/java/LuckyVicky/backend/global/s3/S3LogService.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
package LuckyVicky.backend.global.s3;

import static LuckyVicky.backend.global.util.Constant.LOG_DATE_FORMAT;
import static LuckyVicky.backend.global.util.Constant.LOG_LOGBACK_ERROR_FILE_NAME;
import static LuckyVicky.backend.global.util.Constant.LOG_LOGBACK_FILE_DIRECTORY;

import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.GetObjectRequest;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.amazonaws.services.s3.model.S3Object;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
Expand All @@ -24,50 +38,96 @@ public class S3LogService {
private String bucket;

public void uploadDailyLog(String day) {
File targetFile = getLocalLogFile(day);

if (!targetFile.exists()) {
File localFile = getLocalLogFile(day);
if (!localFile.exists()) {
log.warn("No local log file found for day={}", day);
return;
}

String s3Key = buildS3Key(day);
File tempFile = new File("temp-" + localFile.getName());

try {
// S3에 업로드
amazonS3.putObject(new PutObjectRequest(bucket, s3Key, targetFile));
log.info("Uploaded log to S3: {} -> s3://{}/{}", targetFile.getName(), bucket, s3Key);
// S3에 기존 로그 파일이 있는 경우 다운로드하여 병합
if (amazonS3.doesObjectExist(bucket, s3Key)) {
log.info("Existing log found in S3. Merging logs...");
mergeLogsFromS3(localFile, tempFile, s3Key);
} else {
Files.copy(localFile.toPath(), tempFile.toPath());
}

// 업로드 후 로컬 파일 삭제
Files.deleteIfExists(targetFile.toPath());
// 병합된 로그 파일을 S3에 업로드
amazonS3.putObject(new PutObjectRequest(bucket, s3Key, tempFile));
log.info("Uploaded merged log to S3: {} -> s3://{}/{}", tempFile.getName(), bucket, s3Key);

// 업로드 후 로컬 로그 파일 삭제 및 Logback 초기화
Files.deleteIfExists(localFile.toPath());
log.info("Deleted local log file: {}", localFile.getName());
resetLogbackContext();

} catch (Exception e) {
log.error("Failed to upload log file to S3. day={}, file={}, key={}", day, targetFile, s3Key, e);
log.error("Failed to upload or merge log file to S3. day={}, file={}, key={}", day, localFile, s3Key, e);
} finally {
tempFile.delete(); // 임시 파일 삭제
}
}

private void resetLogbackContext() {
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
loggerContext.stop(); // 기존 컨텍스트 정지

try {
loggerContext.reset(); // 컨텍스트 초기화
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(loggerContext);
configurator.doConfigure(LOG_LOGBACK_FILE_DIRECTORY + LOG_LOGBACK_ERROR_FILE_NAME);
loggerContext.start(); // 컨텍스트 다시 시작
log.info("Logback context has been reset. New log file will be created.");
} catch (JoranException e) {
log.error("Failed to reset Logback context", e);
}
}

private void mergeLogsFromS3(File localFile, File tempFile, String s3Key) throws IOException {
try (S3Object s3Object = amazonS3.getObject(new GetObjectRequest(bucket, s3Key));
InputStream s3InputStream = s3Object.getObjectContent();
OutputStream tempOutputStream = new FileOutputStream(tempFile, true);
InputStream localInputStream = new FileInputStream(localFile)) {

// S3 로그 내용을 임시 파일에 복사
copyContent(s3InputStream, tempOutputStream);

// 로컬 로그 내용을 임시 파일에 이어서 복사
copyContent(localInputStream, tempOutputStream);
}
}

private void copyContent(InputStream input, OutputStream output) throws IOException {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = input.read(buffer)) > 0) {
output.write(buffer, 0, bytesRead);
}
}

private File getLocalLogFile(String day) {
// LOG_HOME=/var/app/current/logs
String logHome = System.getProperty("LOG_HOME", "./logs");
LocalDate targetDate = "yesterday".equals(day) ? LocalDate.now().minusDays(1) : LocalDate.now();
String dateStr = targetDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
String dateStr = targetDate.format(LOG_DATE_FORMAT);

if ("yesterday".equals(day)) {
// 롤오버된 파일: /var/app/current/logs/error-2025-01-01.log
return new File(logHome, "error-" + dateStr + ".log");
} else {
// 아직 열려 있는 로그: /var/app/current/logs/error.log
return new File(logHome, "error.log");
}
}

private String buildS3Key(String day) {
// 원하는 규칙대로 S3에 업로드될 파일 키 생성
LocalDate targetDate = "yesterday".equals(day) ? LocalDate.now().minusDays(1) : LocalDate.now();
String dateStr = targetDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
String dateStr = targetDate.format(LOG_DATE_FORMAT);

String activeProfile = environment.getProperty("spring.profiles.active", "default");

return String.format("logs/" + activeProfile + "/" + activeProfile + "-error-%s.log", dateStr);
return String.format("logs/%s/%s-error-%s.log", activeProfile, activeProfile, dateStr);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ public class LogUploadScheduler {

@Scheduled(cron = "0 0 0 * * ?", zone = "Asia/Seoul")
public void uploadDailyLogToS3() {
log.error("Uploading Log to S3 with Scheduler");
log.info("Uploading Log to S3 with Scheduler");
logService.uploadDailyLog("yesterday");
log.error("Uploaded Log to S3 with Scheduler");
log.info("Uploaded Log to S3 with Scheduler");
}
}
3 changes: 0 additions & 3 deletions src/main/java/LuckyVicky/backend/global/util/Constant.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@ public class Constant {
private String fcmProjectId;

// Log
public static final String LOG_S3_DIRECTORY = "logs/";
public static final String LOG_LOCAL_FILE_DIRECTORY = "src/main/resources/logs/";
public static final String LOG_LOCAL_ERROR_FILE_NAME = "error.log";
public static final String LOG_LOGBACK_FILE_DIRECTORY = "src/main/resources/";
public static final String LOG_LOGBACK_ERROR_FILE_NAME = "logback-spring.xml";

Expand Down
Loading