SpringMVC - Logging, RequestMapping

뚝딱이·2022년 7월 30일
0

스프링 MVC

목록 보기
7/23

일단 프로젝트를 생성하자. JSP를 사용하지 않기 때문에 Jar로 생성한다.

Jar를 사용하면 내장 서버를 사용하고 webapp 경로도 사용하지 않는다. 내장 서버에 최적화 되어있으며, 최근에는 주로 사용한다.

반면 War는 내장 서버도 사용가능 하지만 주로 외부 서버에 배포하는 목적으로 사용된다. 톰캣을 별도로 설치하거나 JSP를 사용할 때 사용한다.

Logging

우리는 이제까지 System.out.println()을 사용해 필요한 정보들을 출력했다. 하지만 이제부턴 log를 사용하여 필요한 로그들을 출력해보자.

로깅 라이브러리

스프링 부트 라이브러리를 사용하면 sping-boot-starter-logging이 함께 포함되는데 이것은 기본으로 다음의 로깅 라이브러리를 사용한다.

  • SLF4J : 로그라이브러리를 인터페이스화한 프로젝트
  • Logback : 구현체 중 하나로 스프링부트에서 기본으로 제공한다.

로그 선언

로그의 선언은 다음과 같이 세가지 방법으로 할 수 있다.

  • private Logger log = LoggerFactory.getLogger(getClass());
  • private static final Logger log = LoggerFactory.getLogger(XXX.class)
  • @Slf4j

로그는 log.info("hello")와 같이 호출할 수 있다.

예제를 확인해보자.

//@Slf4j
//@Controller 뷰 이름 반환
@RestController //문자를 반환하면 HTTP 바디에 문자열 그대로 넣음
public class LogTestController {

    private final Logger log = LoggerFactory.getLogger(getClass());

    @RequestMapping("/log-test")
    public String logTest(){
        String name = "Spring";

        System.out.println("name = " + name);

        log.trace("t race log={}",name);
        log.debug("debug log={}",name);
        log.info("info log={}", name);
        log.warn("warn log={}",name);
        log.error("error log={}",name);
        return "ok";

    }
}
  • @RestController
    @Controller를 사용하면 반환값이 String일 경우 view이름으로 인식된다. 따라서 뷰를 찾고 뷰가 렌더링된다.
    하지만 @RestController는 반환 값이 String일 경우 HTTP 메시지 바디에 입력된다.

log.info의 출력결과는 다음과 같다.

2022-07-30 19:14:43.719  INFO 14252 --- [nio-8080-exec-3] hello.springmvc.basic.LogTestController  : info log=Spring
[        시간          ] [로그레벨][process id][쓰레드 명]   [클래스 명]                                 [로그 메세지]

위의 예제에선 info 말고도 trace, debug, warn, error 등 다양한데, 로그레벨은 아래와 같다.
TRACE > DEBUG > INFO > WARN > ERROR
예를 들어 로그레벨을 ERROR로 지정하면 ERROR만 출력되고, 로그레벨을 TRACE로 지정하면 모든 레벨이 다 출력된다.

보통 개발 서버는 debug 출력이고, 운영서버는 info 출력으로 운영에 꼭 필요한 것만 출력한다.

로그레벨 설정을 application.properties에서 할 수 있다.

#전체 로그 레벨 설정(기본 info)
logging.level.root=info
#hello.springmvc 패키지와 그 하위 로그 레벨 설정
logging.level.hello.springmvc=debug

로그 호출 방법엔 두가지가 있는데 올바른 사용법은 파라미터를 사용하는 것이다. 로그 출력 레벨이 info라고 가정하자. 그렇다면 debug는 출력되지 않는다.

  • log.debug("data="+data)
    자바는 먼저 +로 인해 더하기 연산을 먼저 한다. 따라서 debug는 출력되지도, 쓰지도 않는데 쓸모없는 리소스를 사용하게 된다. 물론 info인 경우엔 상관없다.
  • log.debug("data={}", data)
    이 경우엔 debug가 사용되지 않는 한 로직이 수행될 일이 없다.

장점

  • 쓰레드 정보, 클래스 이름 같은 부가 정보를 함께 볼 수 있고, 출력 모양을 조정할 수 있다.
  • 로그 레벨에 따라 개발 서버에서는 모든 로그를 출력하고, 운영서버에서는 출력하지 않는 등 로그를 상황에 맞게 조절할 수 있다.
    • 애플리케이션 코드를 건들이지 않고 이러한 설정만으로 로그레벨을 조정할 수 있다는 것이 중요하다.
  • 시스템 아웃 콘솔에만 출력하는 것이 아니라, 파일이나 네트워크 등, 로그를 별도의 위치에 남길 수 있다.
    특히 파일로 남길 때는 일별, 특정 용량에 따라 로그를 분할하는 것도 가능하다.
  • 성능도 일반 System.out보다 좋다. (내부 버퍼링, 멀티 쓰레드 등등) 그래서 실무에서는 꼭 로그를사용해야 한다.

요청 매핑

요청이 왔을 때 어떤 컨트롤러가 와야하는지 매핑 하는 법을 알아보자.

basic

    @RequestMapping("/hello-basic")
    public String helloBasic() {
        log.info("helloBasic");
        return "ok";
    }

Http 메서드를 따로 지정해주지 않았기 때문에 모든 Http 메서드를 허용한다.
참고로 /hello-basic과 /hello-basic/은 다른 URL이지만 스프링은 같은 요청으로 매핑한다.

또한 {"/hello-basic", "hello-go"}를 넣어 다중 설정이 가능하다.

RequestMethod.GET

    @RequestMapping(value = "/mapping-get-v1", method = RequestMethod.GET)
    public String mappingGetV1() {
        log.info("mapping-get-v1");
        return "ok";
    }

Http 메서드를 Get으로 지정해준다. 따라서 /mapping-get-v1를 POST로 요청하면 405(Method Not Allowed) 를 반환한다.

GetMapping

    @GetMapping ("/mapping-get-v2")
   public String mappingGetV2() {
       log.info("mapping-get-v2");
       return "ok";
   }

@RequestMapping(value = "/mapping-get-v1", method = RequestMethod.GET)@GetMapping ("/mapping-get-v2")와 같이 변경할 수 있다.

PathVariable

    /**
     * PathVariable 사용
     * 변수명이 같으면 생략가능
     * @PathCariable("userId") String userId -> @PathVariable userID
     */
    @GetMapping("/mapping/{userId}")
    public String mappingPath(@PathVariable("userId") String data) {

        log.info("mappingPath userId={}", data);

        return "ok";
    }

리소스 경로에 식별자를 넣는 스타일을 선호한다. @RequestMapping은 URL 경로를 템플릿화 ({userId})할 수 있는데, @PathVariable을 사용하면 매칭 되는 부분을 편리하는 조회할 수 있다.

PathVariable 파라미터 생략

    @GetMapping("/mapping/{userId}")
    public String mappingPath(@PathVariable String userId) {

        log.info("mappingPath userId={}", data);

        return "ok";
    }

@PathVariable 의 이름과 파라미터 이름이 같으면 생략할 수 있다.

PathVariable 다중 사용

    @GetMapping("/mapping/users/{userId}/orders/{orderId}")
    public String mappingPath(@PathVariable String userId, @PathVariable Long orderId) {

        log.info("mappingPath userId={}, orderId={}", userId, orderId);

        return "ok";
    }

파라미터를 다중 사용할 수 있다.

특정 파라미터 조건 매핑

잘 사용하지 않는다.

    /**
     * 파라미터로 추가 매핑
     * params="mode",
     * params="!mode"
     * params="mode=debug"
     * params="mode!=debug" (! = )
     * params = {"mode=debug","data=good"}
     */@GetMapping(value = "/mapping-param", params = "mode=debug")
    public String mappingParam() {
        log.info("mappingParam");
        return "ok";
    }

특정 파라미터 정보가 있으면 호출하고 없다면 400 코드를 반환하며 매칭 자체가 안된다.

특정 헤더 조건 매핑

    /**
     * 특정 헤더로 추가 매핑
     * headers="mode",
     * headers="!mode"
     * headers="mode=debug"
     * headers="mode!=debug" (! = )
     */
    @GetMapping(value = "/mapping-header", headers = "mode=debug")
    public String mappingHeader() {
        log.info("mappingHeader");
        return "ok";
    }

헤더 조건을 넣지 않으면 404 코드를 반환한다.

미디어 타입 조건 매핑 - HTTP 요청 Content-Type, consume

    /**
     * Content-Type 헤더 기반 추가 매핑 Media Type
     * consumes="application/json" * consumes="!application/json"
     * consumes="application/*"
     * consumes="*\/*"
     * MediaType.APPLICATION_JSON_VALUE
     */
    @PostMapping(value = "/mapping-consume", consumes = MediaType.APPLICATION_JSON_VALUE)
    public String mappingConsumes() {
        log.info("mappingConsumes");
        return "ok";
    }

POST이면서 JSON형식으로 요청이 와야 위의 메서드가 호출된다.

consume="application/json"을 사용할 수도 있지만 위와 같이 MediaType.APPLICATION_JSON_VALUE를 사용하는 것이 더 좋다.

import org.springframework.http.MediaType;를 import 해야한다.

만약 형식이 맞지 않으면 415(Unsupported Media Type)을 반환한다.

미디어 타입 조건 매핑 - HTTP 요청 Accept, produce

    /**
     * Accept 헤더 기반 Media Type
     * produces = "text/html"
     * produces = "!text/html"
     * produces = "text/*"
     * produces = "*\/*"
     */
    @PostMapping(value = "/mapping-produce", produces = MediaType.TEXT_HTML_VALUE)
    public String mappingProduces() {
        log.info("mappingProduces");
        return "ok";
    }

produce 는 생산해내는 것이다. 따라서 받아들일 수 있는 것인 Accept와 produce가 맞아야한다.
만약 맞지 않으면 406(Not Acceptable)을 반환한다.

Accept = "application/json"이고 produce="text/plain"일 경우 Accept는 text/plain을 받아들일 수 없으므로 매핑되지 않는다.

그렇다면 Content-Type과 Accept의 차이점은 무엇일까.
Content-Type은 HTTP 메시지(요청과 응답 모두)에 담겨 보내는 데이터의 형식을 알려주는 헤더이다. Content-Type의 내용과 consumes가 일치하지 않으면 서버에서 거절한다.
Accept는 브라우저(클라이언트) 에서 웹서버로 요청시 요청메시지에 담기는 헤더이다.Accept와 produce가 일치하지 않으면, 클라이언트에서 거절한다.
관련 Q&A를 참고하자.

요청 매핑 - API 예시

    /**
     * 회원 목록 조회: GET /users
     * 회원 등록: POST /users
     * 회원 조회: GET /users/{userId}
     * 회원 수정: PATCH /users/{userId}
     * 회원 삭제: DELETE /users/{userId}
     */

위와 같은 설계를 바탕으로 코드를 짜보자.

/users가 반복되므로 @RequestMapping("/users")를 사용하여 중복을 제거하자.

    @GetMapping
    public String user(){
        return "get users";
    }

    @PostMapping
    public String addUser() {
        return"post user";
    }

    @GetMapping("/{userId}")
    public String findUser(@PathVariable String userId) {
        return "get userId=" + userId;
    }

    @PatchMapping("/{userId}")
    public String updateUser(@PathVariable String userId) {
        return "update userId=" + userId;
    }

    @DeleteMapping("/{userId}")
    public String deleteUser(@PathVariable String userId) {
        return "delete userId=" + userId;
    }

출처 : 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술

profile
백엔드 개발자 지망생

0개의 댓글