자바 웹 파일업로드와 logging

Dear·2025년 6월 24일

TIL

목록 보기
47/74

💙 파일 업로드

MultipartFile 클래스 사용

@PostMapping("/upload")
public String uploadFile(@RequestParam("file") MultipartFile file) {
    if (!file.isEmpty()) {
        String fileName = file.getOriginalFilename();
        Path savePath = Paths.get("uploads", fileName);
        try {
            file.transferTo(savePath.toFile());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return "uploadResult";
}

스프링 3 설정

spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=20MB

스프링 4 설정

  • 기본으로 포함 되어있다
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

💙 정적 자원(Static Resources)

바뀌지 않는 파일들(코드로 처리하지 않고 그래도 보여주는 파일들)
-> CSS, JS, Image

해당 파일들은 사용자 요청이 오면 가공 없이 그대로 응답한다.

src/main/resources/static/css/style.css 라는 파일이 있을 경우
브라우저에서는 다음과 같이 사용할 수 있다.
<link rel="stylesheet" href="/css/style.css">
URL로 직접 접근하면 JS 파일이 그대로 다운로드/실행 된다.

즉, 컨트롤러를 거치지 않고, 정해진 경로 그대로 /css/style.css 라고 요청하면 바로 파일이 응답한다.

정적 자원은 "서버에 있는 파일 경로"와 "브라우저에서 요청하는 경로"가 거의 1:1 매칭이다.

자원 경로 설정

http://localhost:8080/js/jquery.js 라는 URL로 JS 파일을 요청했을때 Spring MVC의 DispatcherServlet(사용자의 모든 요청을 먼저 받아, 적절한 컨트롤러 메서드(@RequestMapping 등)에 매핑해주는 중앙 제어자)이 일반적인 요청(Controller 매핑)으로 오해해서 JS 파일을 찾지 못하고 404 에러가 발생한다.

따라서, 정적 자원들은 따로 URL을 주어야 하고, 이런 기능을 mvc:resources가 제공한다.
-> 자원 경로 설정

자원 경로 설정 방법

  • DispatcherServlet에 등록(servlet-context.xml)
    <mvc:resources mapping="/resources/**" location="/resources/" />
  • Spring Boot에서는 기본적으로 하단의 경로를 자동으로 정적 자원으로 인식한다.
    src/main/resources/static/
	src/main/resources/public/
	src/main/resources/resources/
    
	<script src="/js/jquery.js"></script>로 바로 사용 가능

💙 Spring 프로젝트에서 context path 문제

Context Path는 애플리케이션이 서버 내에서 어디에 배포되어 있는지를 나타내는 경로이다.
Spring 프로젝트 이름이 HelloSpring이고 Tomcat에 /myapp으로 배포되었다면
전체 URL 예: http://localhost:8080/myapp/... 에서
/myappcontext path이다.

<script src="/myapp/js/jquery-3.3.1.min.js" type="text/javascript"></script> 에서
context pathmyapp일 때는 문제가 없지만 /HelloSpring이거나 루트(/)일 때는 에러가 발생한다.

해결 방법

  1. 상대 경로로 작성
    <script src="js/jquery-3.3.1.min.js"></script>
    현재 HTML 파일 위치 기준으로 경로를 찾는다.(context path 영항 없음)

  2. JSP라면 EL(Expression Laguage) 사용
    <script src="${pageContext.request.contextPath}/js/jquery-3.3.1.min.js"></script>
    context path를 동적으로 가져와서 경로 깨짐을 방지한다.

  3. Spring Boot라면 context 기본적으로 /(root)라서 문제 없다.
    하지만 application.properties에서 context path를 바꿨다면 위처럼 동적 처리가 필요하다.

JSTL(Core 라이브러리) 사용

<script src="/myapp/js/jquery-3.3.1.min.js"> 에서
/myapp이 context path이다.
나중에 프로젝트 이름이 바꿔서 context path가 /helloSpring으로 바뀐다면 경로가 깨져 404 가 발생한다.

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<script src="<c:url value='/js/jquery-3.3.1.min.js' />"></script>

c:url 이 현재 context path를 자동으로 붙여준다.
-> context path가 /myapp이면 결과는 /myapp/js/jquery-3.3.1.min.js

form 태그도 마찬가지로 작동한다.

<form method="post" action="<c:url value='/board/write' />">

-> 결과 : /myapp/board/write

💙 Logging

Spring Framework는 Commons-Logging을 사용한다.
Commons-Logging은 설정의 까다로움 및 System Resource의 과다한 사용문제가 있다.

SLF4J + Logback 조합 사용

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Logger log = LoggerFactory.getLogger(MyClass.class);
log.info("SLF4J + Logback 조합의 logging");
  • SLF4J: 로깅 추상화 (가볍고 단순)
    실제 로깅은 안 하고, 어떤 로깅 구현체를 연결해주는 인터페이스 역할
  • Logback: 실제 로깅 처리 (빠르고 강력함)
    -> 실제 로그 출력(console/file 등)
    LoggerFactory.gettLogger() 내부에서 연결된 구현체가 동작하는데, 그게 Logback
  • Spring Boot도 기본적으로 이 조합 사용함

내부 동작

  1. LoggerFactory.getLogger() 호출 → SLF4J가 동작
  2. SLF4J는 내부에서 Logback 라이브러리를 자동 탐지
  3. Logback이 있으면 → logback-classic 사용됨
  4. Logback이 없으면 → 다른 로깅 구현체로 연결하거나 에러

설정 방법

  • Spring Boot
    spring-boot-starter-logging에 자동으로 포함되어 있다.
    /src/main/resources/logback-spring.xml 또는 application.yml로 커스터마이징만 하여 사용한다.

  • Maven 의존성 추가 (pom.xml에 명시 필요)
    SLF4J + logback-classic 조합이면 SLF4J가 자동으로 Logback을 인식해서 연결

<!-- SLF4J API -->
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-api</artifactId>
  <version>1.7.36</version>
</dependency>

<!-- Logback 구현체 -->
<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
  <version>1.2.11</version>
</dependency>

SLF4J + logback-classic 조합이면 SLF4J가 자동으로 Logback을 인식해서 연결한다.

  • 로그 설정 파일 작성
    src/main/resources/logback.xml
    해당 파일 작성 안 해도 기본 설정으로 동작은 한다.
<configuration>
  <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d{HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <root level="debug">
    <appender-ref ref="console"/>
  </root>
</configuration>

💙 Logging 출력 속성

log4j.xml의 PatternLayout 클래스에서 사용되는 출력 속성들

속성설명
%pdebug, info, error, fatal 등 로그 레벨 이름 출력
%m로그 메시지 출력
%d로깅 이벤트 발생 시각 출력
ex) 포맷은 %d{HH:mm:ss} 같은 형태의 SimpleDateFormat
%F로깅이 발생한 프로그램 파일 이름 출력
%l로깅이 발생한 caller의 정보 출력
%L로깅이 발생한 caller의 라인 수 출력
%M로깅이 발생한 method 이름 출력
%c로그 메시지 앞에 전체 패키지 이름이나 전체 파일 이름 출력

사용예시 (logback.xml)

<configuration>
  <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5p %c{1}.%M(%F:%L) - %m%n</pattern>
    </encoder>
  </appender>

  <root level="debug">
    <appender-ref ref="console" />
  </root>
</configuration>
패턴설명
%d{HH:mm:ss.SSS}시간 출력 (예: 15:32:10.123)
[%thread]실행한 스레드 이름 출력
%-5p로그 레벨 출력 (예: INFO, DEBUG)
%c{1}.%M(%F:%L)클래스명.메서드명(파일명:라인번호) 출력
%m로그 메시지 출력
%n줄바꿈 (new line)

실제 출력 예시
15:32:10.123 [main] INFO MyService.doSomething(MyService.java:42) - 처리 완료

💙 log4j 로그 레벨

레벨설명
FATAL시스템 차원에서 심각한 문제가 발생해 애플리케이션 작동이 불가능할 경우 해당하는 레벨. 애플리케이션에서는 사용할 일이 없음
ERROR실행 중 문제가 발생한 상태
WARN향후 시스템 오류의 원인이 될 수 있는 경고 메시지
INFO로그인, 상태변경과 같은 실제 애플리케이션 운영과 관련된 정보 메시지
DEBUG개발 시 디버깅 용도로 사용하는 메시지
TRACEDEBUG 레벨보다 상세한 로깅 정보를 출력하기 위해 도입된 레벨

FATAL < ERROR < WARN < INFO < DEBUG < TRACE

DEBUG 레벨로 했다면 INFO~FATAL까지 모두 logging 된다.

🤍 회고

오늘은 Spring의 파일 업로드와 로깅(logging)에 대해 공부했다.
그동안은 개인 프로젝트나 학습용 수준의 프로젝트만 진행해왔기 때문에, 파일 경로를 항상 프로젝트 내부에만 지정했고, context path와 같은 개념을 고려하지 않았다.
하지만 실제 프로젝트에서는 이러한 경로 설정이 매우 중요하며, 예외가 발생했을 때 경로 문제를 확인하는 것도 중요한 디버깅 포인트라는 것을 알게 되었다.

또한 로깅(logging) 역시 예전에는 단순 출력 정도로만 생각했지만, 데이터베이스와 연동된 프로젝트에서 로그의 역할이 얼마나 중요한지 알게 되었다.
특히 로그 메시지를 정렬되게 출력하면 가독성이 높아지고, 문제 분석이나 모니터링에도 큰 도움이 된다는 것을 느꼈다.

profile
친애하는 개발자

0개의 댓글