상세 컨텐츠

본문 제목

[스프링3] 5장. API를 작성하는 다양한 방법

23-24/Spring 3

by recoday 2023. 11. 3. 10:00

본문

728x90

GET API

웹 애플리케이션 서버에서 값을 가져올 때 사용하는 API
GET API를 작성하는 방법은 다양하다.

실무에서는 HTTP 메서드에 따라 컨트롤러 클래스를 구분하지 않는다고 하지만, 정확한 구분과 이해를 위해 메서드별로 클래스를 생성하였다.

 

GetController

컨트롤러에 @RestController, @RequestMapping을 붙여 내부에 선언되는 메서드에서 사용할 공통 URL 설정

@RestController
@RequestMapping("/api/v1/get-api")
public class GetController {

}

클래스 수준에서 @RequestMapping을 설정하면 내부에 선언한 메서드의 URL 리소스 앞에 @RequestMapping의 값이 공통 값으로 추가된다.

 

@RequestMapping으로 구현하기

@RequestMapping 어노테이션을 별다른 설정 없이 선언하면 HTTP의 모든 요청을 받는다.
GET 형식의 요청만 받기 위해서는 별도의 어노테이션 설정이 필요하다.

@RestController
@RequestMapping("/api/v1/get-api")
public class GetController {

    // http://localhost:8080/api/v1/get-api/hello
    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    public String getHello() {
        return "Hello World";
    }
}

http://localhost:8080/api/v1/get-api/hello URL을 통해 getHello() 메서드가 호출되고, 결과로 "Hello World"가 반환된다.

@RequestMapping 어노테이션의 method 요소의 값을 RequestMethod.GET으로 설정하면 요청 형식을 GET으로만 설정할 수 있다.

 

💡 스프링 4.3 버전 이후는 새로 나온 아래 어노테이션을 사용하기 때문에 @RequestMapping 어노테이션은 더 이상 사용되지 않는다.

  • @GetMapping
  • @PostMapping
  • @PutMapping
  • @DeleteMapping

 

매개변수가 없는 GET 메서드 구현

    // http://localhost:8080/api/v1/get-api/name
    @GetMapping(value = "/name")
    public String getName() {
        return "Corner";
    }

매개변수가 없는 요청은 http://localhost:8080/api/v1/get-api/name URL을 그대로 입력하고 요청할 때 스프링 부트 애플리케이션이 정해진 응답을 반환한다. getName() 메서드를 호출하고, 결과로 "Corner"가 반환된다.

@PathVariable을 활용한 GET 메서드 구현

웹 통신의 기본 목적은 데이터를 주고받는 것이기 때문에 대부분 매개변수를 받는 메서드를 작성한다. 매개변수를 받을 때 자주 쓰이는 방법 중 하나는 URL 자체에 값을 담아 요청하는 것이다.

    // http://localhost:8080/api/v1/get-api/variable1/{String 값}
    @GetMapping(value = "/variable1/{variable}")
    public String getVariable1(@PathVariable String variable) {
        return variable;
    }

이 메서드는 {중괄호}로 표시된 위치의 값을 받아 요청한다. 실제 요청 시 중괄호는 들어가지 않으며 값만 존재한다. 값을 간단히 전달할 때 주로 사용하는 방법이며, GET 요청에서 많이 사용된다.

 

💡 규칙

  • @GetMapping 어노테이션 값으로 URL을 입력할 때 중괄호를 사용해 어느 위치에서 값을 받을지 지정
  • @PathVariable 명시
  • @GetMapping 어노테이션과 @PathVariable에 지정된 변수의 이름 동일

 

`getVariable1()` 메서드 호출 결과

만약 @GetMapping 어노테이션에 지정한 변수 이름과 메서드 매개변수의 이름을 동일하게 맞추기 어렵다면 @PathVariable 뒤에 괄호를 열어 @GetMapping 어노테이션의 변수명을 지정한다.

    // http://localhost:8080/api/v1/get-api/variable2/{String 값}
    @GetMapping(value = "/variable2/{variable}")
    public String getVariable2(@PathVariable("variable") String var) {
        return var;
    }

@PathVariable에는 변수의 이름을 특정할 수 있는 value 요소가 존재하며, 이 위치에 변수 이름을 정의하면 매개변수와 매핑할 수 있다 => variable과 var 매핑

 

@RequestParam을 활용한 GET 메서드 구현

GET 요청을 구현할 때 쿼리 형식으로 값을 전달할 수도 있다.
URI에서 '?'를 기준으로 우측에 '{키}={값}' 형태로 구성된 요청을 전송하는 방법 -> @RequestParam 어노테이션을 명시해 쿼리 값과 매핑

    // http://localhost:8080/api/v1/get-api/request1?name=value1&email=value2&organization=value3
    @GetMapping(value = "/request1")
    public String getRequestParam(@RequestParam String name, @RequestParam String email, @RequestParam String organization) {
        return name + " " + email + " " + organization;
    }

URL을 보면 '?' 오른쪽에 쿼리스트링이 명시돼 있다. 쿼리스트링에는 키(변수의 이름)가 모두 적혀 있기 때문에 이 값을 기준으로 메서드의 매개변수에 이름을 매핑하면 값을 가져올 수 있다.
키와 @RequestParam 뒤에 적는 이름을 동일하게 설정하기 어렵다면, 앞선 방법처럼 value 요소로 매핑하면 된다.

getRequestParam1() 메서드 호출 결과 => value1 value2 value3

 

💡 만약 쿼리스트링에 어떤 값이 들어올지 모른다면 Map 객체를 활용할 수도 있다.

    // http://localhost:8080/api/v1/get-api/request2?key1=value1&key2=value2
    @GetMapping(value = "/request2")
    public String getRequestParam2(@RequestParam Map<String, String> param) {
        StringBuilder sb = new StringBuilder();

        param.entrySet().forEach(map -> {
            sb.append(map.getKey() + " : " + map.getValue() + "\n");
        });

        return sb.toString();
    }

이처럼 코드를 작성하면 값에 상관없이 요청받을 수 있다.
예를 들어, 회원가입 관련 API에서 ID가 아닌, 취미와 같은 선택 항목에 대해서는 값을 기입하지 않는 경우에 매개변수의 항목이 일정하지 않을 수 있어 Map 객체로 받는 것이 효율적이다.

 

getRequestParam2() 메서드 호출 결과
key1 : value1
key2 : value2

 

 

✅ URI와 URL의 차이

  • URL: 웹 주소, 리소스가 어디에 있는지 알려주기 위한 경로
  • URI: 특정 리소스를 식별할 수 있는 식별자
    웹에서는 URL을 통해 리소스가 어느 서버에 위치해 있는지 알 수 있으며, 그 서버에 접근해서 리소스에 접근하기 위해서는 대부분 URI가 필요하다.

 

DTO 객체를 활용한 GET 메서드 구현

DTO란?

Data Transfer Object의 약자
다른 레이어 간 데이터 교환에 활용된다.
각 클래스 및 인터페이스를 호출하면서 전달하는 매개변수로 사용되는 데이터 객체

DTO는 데이터를 교환하는 용도로만 사용하는 객체이기에 별도의 로직이 포함되지 않는다.

 

✅ DTO와 VO의 차이
DTO와 VO(Value Object)의 역할을 서로 엄밀하게 구분하지 않고 사용할 때가 많지만 정확히 구분하자면 역할과 사용법에서 차이가 있다.

  • VO: 데이터 그 자체로 의미있는 객체. 가장 특징적인 부분은 읽기전용(Read-Only)으로 설계한다는 점. 즉, VO는 값을 변경할 수 없게 만들어 데이터의 신뢰성을 유지해야 한다.
  • DTO: 데이터 전송을 위해 사용되는 데이터 컨테이너. 즉, 같은 애플리케이션 내부에서 사용되는 것이 아니라 다른 서버(시스템)로 전달하는 경우에 사용된다.

 

DTO 클래스 예시

public class MemberDto {

    private String name;
    private String email;
    private String organization;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getOrganization() {
        return organization;
    }

    public void setOrganization(String organization) {
        this.organization = organization;
    }

    @Override
    public String toString() {
        return "MemberDTO{" +
                "name='" + name + '\'' +
                ", email='" + email + '\'' +
                ", organization='" + organization + '\'' +
                '}';
    }
}

DTO 클래스에는 전달하고자 하는 필드 객체 선언, getter/setter 메서드를 구현한다.
DTO 클래스에 선언된 필드는 컨트롤러의 메서드에서 쿼리 파라미터의 키와 매핑된다. 즉, 쿼리스트링의 키가 정해져 있지만 받아야 할 파라미터가 많을 경우에는 다음과 같이 DTO 객체를 활용해 코드의 가독성을 높일 수 있다.

    // http://localhost:8080/api/v1/get-api/request3?name=value1&email=value2&organization=value3
    @GetMapping(value = "/request3")
    public String getRequestParam3(MemberDto memberDto) {
        //return memberDto.getName() + " " + memberDto.getEmail() + " " + memberDto.getOrganization();
        return memberDto.toString();
    }

@getRequestParam3() 메서드 호출 결과 => MemberDTO{name='value1', email='value2', organization='value3'}

 

 

POST API

POST API는 웹 애플리케이션을 통해 데이터베이스 등의 저장소에 리소스를 저장할 때 사용되는 API이다. POST API에서는 저장하고자 하는 리소스나 값을 HTTP 바디(body)에 담아 서버에 전달한다. 그래서 URI가 GET API에 비해 간단하다.

PostController

컨트롤러 클래스에서 공통 URL 설정

@RestController
@RequestMapping("/api/v1/post-api")
public class PostController {

}

 

@RequestMapping으로 구현하기

GET API 방법과 비슷하다. 요청 처리 메서드를 정의할 때 method 요소를 RequestMethod.POST로 설정해주면 된다.

    @RequestMapping(value = "/domain", method = RequestMethod.POST)
    public String postExample() {
        return "Hello Post API";
    }

 

@RequestBody를 활용한 POST 메서드 구현

일반적으로 POST 형식의 요청은 클라이언트가 서버에 리소스를 저장하는 데 사용한다. 그러므로 클라이언트의 요청 트래픽에 값이 포함되어 있다. 즉, POST 요청에서는 리소스를 담기 위해 HTTP Body에 값을 넣어 전송한다.
Body 영역에 작성되는 값은 일반적으로 JSON(JavaScript Object Notation) 형식으로 전송된다.

 

💡 JSON이란?
JSON은 JavaScript Object Notation의 줄임말. 자바스크립트의 객체 문법을 따르는 문자 기반의 데이터 포맷이다. 현재는 자바스크립트 외에도 다수의 프로그래밍 환경에서 사용한다. 대체로 네트워크를 통해 데이터를 전달할 때 사용, 문자열 형태로 작성되기 때문에 파싱하기 쉽다.

  • post 요청 작성 예시
    // http://localhost:8080/api/v1/post-api/member
    @PostMapping(value = "/member")
    public String postMember(@RequestBody Map<String, Object> postData) {
        StringBuilder sb = new StringBuilder();

        postData.entrySet().forEach(map -> {
            sb.append(map.getKey() + " : " + map.getValue() + "\n");
        });

        return sb.toString();
    }

@RequestMapping 대신 @PostMapping을 사용하면 method 요소를 정의하지 않아도 된다.

 

Map 객체는 요청을 통해 어떤 값이 들어오게 될지 특정하기 어려울 때 주로 사용

  • postMember() 메서드 호출 결과

요청 메시지에 들어갈 값이 정해져 있다면 다음과 같이 DTO 객체를 매개변수로 삼아 작성할 수도 있다.

    // http://localhost:8080/api/v1/post-api/member2
    @PostMapping(value = "/member2")
    public String postMemberDto(@RequestBody MemberDto memberDto) {
        return memberDto.toString();
    }

이처럼 작성하면 MemberDto의 멤버 변수를 요청 메시지의 키와 매핑해 값을 가져온다.

 

 

PUT API

PUT API는 웹 애플리케이션 서버를 통해 데이터베이스 같은 저장소에 존재하는 리소스 값을 업데이트하는 데 사용한다. POST API와 비교하면 요청을 받아 실제 데이터베이스에 반영하는 과정(서비스 로직)에서 차이가 있지만, 리소스를 서버에 전달하기 위해 HTTP Body를 활용하기 때문에 컨트롤러 클래스를 구현하는 방법은 POST API와 거의 동일하다.

 

PutController

@RestController
@RequestMapping("/api/v1/put-api")
public class PutController {

}

 

@RequestBody를 활용한 PUT 메서드 구현

PUT API는 POST 메서드와 마찬가지로 값을 HTTP Body에 담아 전달한다. 서버에서는 이 값을 받기 위해 @RequestBody를 사용한다.

  • 서버에 어떤 값이 들어올지 모르는 경우 Map 객체를 활용해 값을 받음
    @PutMapping(value = "/member")
    public String postMember(@RequestBody Map<String, Object> putData) {
        StringBuilder sb = new StringBuilder();

        putData.entrySet().forEach(map -> {
            sb.append(map.getKey() + " : " + map.getValue() + "\n");
        });

        return sb.toString();
    }
  • 서버에 들어오는 요청에 담겨 있는 값이 정해져 있는 경우, DTO 객체를 활용해 구현
    // http://localhost:8080/api/v1/put-api/member1
    @PutMapping(value = "/member1")
    public String postMemberDto1(@RequestBody MemberDto memberDto) { 
        return memberDto.toString();
    }

    // http://localhost:8080/api/v1/put-api/member2
    @PutMapping(value = "/member2")
    public MemberDto postMemberDto2(@RequestBody MemberDto memberDto) { 
        return memberDto;
    }

postMemberDto1 메서드는 리턴 값이 String 타입이고, postMemberDto2 메서드는 리턴 값이 DTO 객체 타입이다.

다음 사진과 같이 첫 번째 메서드를 테스트하기 위해 PUT 요청을 보내면,


이와 같이 String 타입으로 값을 전달받게 되며, 리턴 값으로 설정한 DTO 객체의 toString 메서드 결과값이 출력된다.

HEADERS 항목의 Content-Type을 보면 'text/plain'으로서 결과값으로 일반 문자열이 전달됐음을 확인할 수 있다.

이번에는 두 번째 메서드를 테스트하기 위해 PUT 요청을 보내면,

 


다음과 같은 결과값이 나온다. 우측의 BODY 값은 실제로 형식만 유지한 채 전달된다. HEADERS 영역의 Content-Type 항목을 보면 'application/json' 형식으로 전달된 것을 확인할 수 있다.
@RestController 어노테이션이 지정된 클래스는 @RequestBody를 생략할 수 있는데, 이 @RequestBody 어노테이션은 자동으로 값을 JSON과 같은 형식으로 변환해서 전달하는 역할을 수행한다.

 

 

ResponseEntity를 활용한 PUT 메서드 구현

스프링 프레임워크에는 HttpEntity라는 클래스가 있다. HttpEntity는 다음과 같이 헤더(Header)와 Body로 구성된 HTTP 요청과 응답을 구성하는 역할을 수행한다.

 

HttpEntity 클래스

public class HttpEntity<T> {

    private final HttpHeaders headers;

    @Nullable
    private final T body;

    ...
}

RequestEntity와 ResponseEntity는 HttpEntity를 상속받아 구현한 클래스.

  • ResponseEntity - 서버에 들어온 요청에 대해 응답 데이터를 구성해 전달
  • ResponseEntity - HttpEntity로부터 HttpHeaders와 Body를 가지고 자체적으로 HttpStatus 구현

 

ResponseEntity 클래스

public class ResponseEntity<T> extends HttpEntity<T> {

    private final Object status;

    ..생략..
}

이 클래스를 활용하면 응답 코드 변경은 물론 Headers와 Body를 더욱 쉽게 구성할 수 있으며, PUT 메서드 외에 다른 메서드에서도 모두 사용할 수 있는 클래스이다.

    // http://localhost:8080/api/v1/put-api/member3
    @PutMapping(value = "/member3")
    public ResponseEntity<MemberDto> postMemberDto3(@RequestBody MemberDto memberDto) {
        return ResponseEntity.status(HttpStatus.ACCEPTED).body(memberDto);
    }

postMemberDto3 메서드의 리턴 타입을 ResponseEntity로 설정하고 리턴 값을 만든다. status에 넣을 수 있는 값은 다양한데, HttpStatus.ACCEPTED는 응답 코드 202를 가지고 있다. 즉, 이 메서드를 대상으로 요청을 수행하면 다음과 같이 응답 코드가 202로 변경된다.

 

 

DELETE API

DELETE API는 웹 애플리케이션 서버를 거쳐 데이터베이스 등의 저장소에 있는 리소스를 삭제할 때 사용한다. 서버에서는 클라이언트로부터 리소스를 식별할 수 있는 값을 받아 데이터베이스나 캐시에 있는 리소스를 조회하고 삭제하는 역할을 수행한다. 이때 컨트롤러를 통해 값을 받는 단계에서는 간단한 값을 받기 때문에 GET 메서드와 같이 URI에 값을 넣어 요청을 받는 형식으로 구현된다.

DeleteController

@RestController
@RequestMapping("/api/v1/delete-api")
public class DeleteController {

}

 

@PathVariable과 @RequestParam을 활용한 DELETE 메서드 구현

@PathVariable을 이용하면 다음과 같이 URI에 포함된 값을 받아 로직 처리 가능

    // http://localhost:8080/api/v1/delete-api/{String값}
    @DeleteMapping(value = "/{variable}")
    public String DeleteVariable(@PathVariable String variable) {
        return variable;
    }

@DeleteMapping 어노테이션에 정의한 value의 이름과 메서드의 매개변수 이름을 동일하게 설정해야 삭제할 값이 주입된다.

또는 다음과 같이 @RequestParam 어노테이션을 통해 쿼리스트링 값을 받을 수도 있다.

    // http://localhost:8080/api/v1/delete-api/request1?email=value
    @DeleteMapping(value = "/request1")
    public String getRequestParam1(@RequestParam String email) {
        return "e-mail : " + email;
    }

 

REST API 명세를 문서화하는 방법 - Swagger

API를 개발하면 명세를 관리해야 한다. 명세란 해당 API가 어떤 로직을 수행하는지 설명하고 이 로직을 수행하기 위해 어떤 값을 요청하며, 이에 따른 응답값으로는 무엇을 받을 수 있는지를 정리한 자료이다.

API는 개발 과정에서 계속 변경되므로 작성한 명세 문서도 주기적인 업데이트가 필요하다. 이 같은 문제를 해결하기 위해 등장한 것이 Swagger라는 오픈소스 프로젝트이다.

 

Swagger 사용을 위해 pom.xml파일에 의존성 추가

pom.xml

        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>

        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>

SwaggerConfiguration - Swagger 설정 코드

@Configuration
@EnableSwagger2
public class SwaggerConfiguration {

    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.springboot.api"))
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("Spring Boot Open API Test with Swagger")
                .description("설명 부분")
                .version("1.0.0")
                .build();
    }
}

이렇게 하면 Swagger 사용을 위한 기본적인 설정이 완료된다.
Swagger에서 스캔할 패키지 범위를 RequestHandlerSelectors.basePackage() 메서드를 사용해 설정한다.

 

⛔ 이대로 스프링 애플리케이션을 실행할 경우, 다음과 같은 오류가 발생한다.

[Spring] Swagger Issue "org.springframework.web.servlet.mvc.condition.PatternsRequestCondition.toString()"

SpringBoot 버전이 2.6 이상일 시 요청 경로를 ControllerHandler에 매칭시키기 위한 무언가가 작동되는데, spring.mvc.pathmatch.matching-strategy 기본값이 ant_path_matcher에서 path_patter_parser로 변경이 되어서 문제가 되는 것이라고 한다. 이를 ant_path_matcher로 변경해주면 된다.

 

application.properties

spring.mvc.pathmatch.matching-strategy=ant_path_matcher
    참고: https://hyomyo.tistory.com/102

 

스프링 애플리케이션 실행 후 웹 브라우저를 통해 http://localhost:8080/swagger-ui.html로 접속하면 다음과 같이 Swagger 페이지가 출력된다.

기존 코드에 Swagger 명세를 추가해보자.

  • 기존 코드
      @ApiOperation(value = "GET 메서드 예제", notes = "@RequestParam을 활용한 GET Method")
      @GetMapping(value = "/request1")
      public String getRequestParam(
              @ApiParam(value = "이름", required = true) @RequestParam String name,
              @ApiParam(value = "이메일", required = true) @RequestParam String email,
              @ApiParam(value = "회사", required = true) @RequestParam String organization) {
          return name + " " + email + " " + organization;
      }
     
    // http://localhost:8080/api/v1/put-api/member1
    @PutMapping(value = "/member1")
    public String postMemberDto1(@RequestBody MemberDto memberDto) { 
        return memberDto.toString();
    }

    // http://localhost:8080/api/v1/put-api/member2
    @PutMapping(value = "/member2")
    public MemberDto postMemberDto2(@RequestBody MemberDto memberDto) {
        return memberDto;
    }
  •  
  •  
  • @ApiOperation: 대상 API의 설명을 작성하기 위한 어노테이션
  • @ApiParam: 매개변수에 대한 설명 및 설정을 위한 어노테이션. 메서드의 매개변수뿐 아니라 DTO 객체를 매개변수로 사용할 경우 DTO 클래스 내의 매개변수도 정의할 수 있다.

위와 같이 설정한 후 해당 API의 명세를 Swagger 페이지에서 살펴보면 다음과 같다.

@ApiOperation에서 작성한 내용은 그림 상단에 표기되고, @ApiParam에 정의한 내용은 아래 'Parameters' 영역의 'Description' 항목에 추가됐다. 이처럼 Swagger는 해당 API가 무엇인지 설명하고 어떤 값이 필요한지 한눈에 보여준다.

Swagger에서는 직접 통신도 가능하다. [Try it out] 버튼을 클릭해서 각 항목의 값을 기입하고


[Excute] 버튼을 누르면 자동으로 완성된 요청 URL을 확인할 수 있고, 그에 대한 결과값도 받을 수 있다.

 

로깅 라이브러리 - Logback

로깅(logging)이란?

애플리케이션이 동작하는 동안 시스템의 상태나 동작 정보를 시간순으로 기록하는 것.

개발 영역 중 '비기능 요구사항' (사용자나 고객에게 필요한 기능은 아니라는 뜻)
하지만 로깅은 디버깅하거나 개발 이후 발생한 문제를 해결할 때 원인을 분석하는 데 꼭 필요한 요소이다.

자바 진영에서 가장 많이 사용되는 로깅 프레임워크는 Logback이다.

Logback이란?

📌 log4j 이후에 출시된 로깅 프레임워크로서 slf4j를 기반으로 구현됐으며, 과거에 사용되던 log4j에 비해 성능이 월등하다. 스프링 부트의 spring-boot-starter-web 라이브러리 내부에 내장되어 있어 별도의 의존성을 추가하지 않아도 사용 가능하다.

Logback의 특징

  • 크게 5개의 로그 레벨(ERROR, WARN, INFO, DEBUG, TRACE) 설정
    • ERROR: 로직 수행 중 시스템에 심각한 문제가 발생해 애플리케이션 작동이 불가능한 경우
    • WARN: 시스템 에러의 원인이 될 수 있는 경고 레벨
    • INFO: 애플리케이션 상태 변경과 같은 정보 전달
    • DEBUG: 애플리케이션 디버깅을 위한 메시지를 표시하는 레벨
    • TRACE: DEBUG 레벨보다 더 상세한 메시지를 표현하기 위한 레벨
  • 실제 운영 환경과 개발 환경에서 각각 다른 출력 레벨을 설정해 로그 확인
  • Logback 설정 파일을 일정 시간마다 스캔해 애플리케이션을 재가동하지 않아도 설정 변경
  • 별도 프로그램 지원 없이 자체적으로 로그 파일 압축
  • 저장된 로그 파일에 대한 보관 기간 등을 설정해 관리

Logback 설정

일반적으로 클래스패스(classpath)에 있는 설정 파일을 자동으로 참조하므로 Logback 설정 파일은 리소스 폴더 안에 생성한다. 파일명의 경우 일반적인 자바 또는 스프링 프로젝트에서는 logback.xml이라는 이름으로 참조하지만 스프링 부트에서는 logback-spring.xml 파일을 참조한다.

  • Logback 설정 파일 예시
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <property name="LOG_PATH" value="./logs"/>  // Property 영역

    <!-- Appenders -->  // Appender 영역
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
        <encoder>  // Encoder 영역
            <pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] [%thread] %logger %msg%n</pattern>  // Pattern 영역
        </encoder>
    </appender>

    <appender name="INFO_LOG"  // Appender 영역 class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
        <file>${LOG_PATH}/info.log</file>
        <append>true</append>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/info_${type}.%d{yyyy-MM-dd}.gz</fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>  // Encoder 영역
            <pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] [%thread] %logger %msg%n</pattern>  // Pattern 영역
        </encoder>
    </appender>

    <!-- TRACE > DEBUG > INFO > WARN > ERROR > OFF -->
    <!-- Root Logger -->  // Root 영역
    <root level="INFO">
        <appender-ref ref="console"/>
        <appender-ref ref="INFO_LOG"/>
    </root>
</configuration>
  •  
  • Property 영역
  • Appender 영역
  • Encoder 영역
  • Pattern 영역
  • Root 영역

각 영역 가운데 Logback 설정에서 가장 중요한 Appender 영역과 Root 영역에 대해 좀 더 자세히 알아보자.

Appender 영역

로그의 형태를 설정하고 어떤 방법으로 출력할지 설정하는 곳

Appender 자체는 하나의 인터페이스를 의미, 하위에 여러 구현체 존재

  • Appender의 상속 구조

Logback 설정 파일을 이용하면 그림의 각 구현체를 등록해서 로그를 원하는 형식으로 출력할 수 있다.

Appender의 대표적인 구현체

  • ConsoleAppender: 콘솔에 로그 출력
  • FileAppender: 파일에 로그 저장
  • RollingFileAppender: 여러 개의 파일을 순화하면서 로그 저장
  • SMTPAppender: 메일로 로그 전송
  • DBAppender: 데이터베이스에 로그 저장

로그를 어떤 방식으로 저장할지 지정하는 방법
위 Logback 설정 파일 코드에서 appender 요소의 class 속성에 각 구현체를 정의한다. 그리고 하단에 filter 요소로 각 Appender가 어떤 레벨로 로그를 기록하는지 지정한다.
다음으로 encoder 요소를 통해 로그의 표현 형식을 패턴으로 정의한다.

대표적인 패턴

  • %Logger{length}: 로거의 이름
  • %-5level: 로그 레벨, -5는 출력 고정폭의 값
  • %msg{%message}: 로그 메시지
  • %d: 로그 기록 시간
  • %p: 로깅 레벨
  • %F: 로깅이 발생한 애플리케이션 파일명
  • %M: 로깅이 발생한 메서드 이름
  • %I: 로깅이 발생한 호출지의 정보
  • %thread: 현재 스레드명
  • %t: 로깅이 발생한 스레드명
  • %c: 로깅이 발생한 카테고리
  • %C: 로깅이 발생한 클래스명
  • %m: 로그 메시지
  • %n: 줄바꿈
  • %r: 애플리케이션 실행 후 로깅이 발생한 시점까지의 시간
  • %L: 로깅이 발생한 호출 지점의 라인 수

로그 형식 예

<pattern>[%d{yyyy-MM-dd HH:mm:ss, SSS}] [%-5level] [%thread] %logger %msg%n</pattern>

 

Root 영역

설정 파일에 정의된 Appender를 활용하려면 Root 영역에서 Appender를 참조해 로깅 레벨을 설정한다. 만약 특정 패키지에 대해 다른 로깅 레벨을 설정하고 싶다면 root 대신 logger를 사용해 다음과 같이 지정할 수 있다.

    <root level="INFO">
        <appender-ref ref="console"/>
        <appender-ref ref="INFO_LOG"/>
    </root>

    또는

    <logger name="com.springboot.api.controller" level="DEBUG" additivity="false">
        <appender-ref ref="console"/>
        <appender-ref ref="INFO_LOG"/>
    </logger>

name 속성: 패키지 단위로 로깅이 적용될 범위 지정
level 속성: 앞에서 지정한 패키지 범위에 하위 패키지 포함 여부 결정. 기본값 true(하위 패키지 모두 포함)

 

Logback 적용

Logback은 출력할 메시지를 Appender에게 전달할 Logger 객체를 각 클래스에 정의해 사용한다.

private final Logger LOGGER = (Logger) LoggerFactory.getLogger(GetController.class);

클래스의 LOGGER 전역 변수로 Logger 객체를 정의해 주고,

다음과 같이 로그를 출력하는 코드를 삽입해주면 된다.

    // http://localhost:8080/api/v1/get-api/hello
    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    public String getHello() {
        LOGGER.info("getHello 메서드가 호출되었습니다.");
        return "Hello World";
    }

❗❗이렇게 하면 콘솔 화면에 로그가 출력되는데, 오류가 발생하여 @Slf4j 어노테이션을 이용하여 다음과 같이 코드를 수정해주었다.

@Slf4j
@RestController
@RequestMapping("/api/v1/get-api")
public class GetController {

    // http://localhost:8080/api/v1/get-api/hello
    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    public String getHello() {
        log.info("getHello 메서드가 호출되었습니다.");
        return "Hello World";
    }

그러면 다음과 같이 콘솔에 로그가 출력되는 것을 볼 수 있다.

[2023-10-29 19:53:20.081] [INFO ] [http-nio-8080-exec-1] com.springboot.api.controller.GetController getHello 메서드가 호출되었습니다.


로그를 통해 컨트롤러에 들어오는 값을 확인하고 싶다면 다음과 같이 코드를 작성하면 된다. 변수를 지정해 변수로 들어오는 값을 로깅한다. 변수의 값이 들어갈 부분을 {중괄호}로 지정하면 포매팅을 통해 로그 메시지가 구성된다.

    // http://localhost:8080/api/v1/get-api/variable1/{String 값}
    @GetMapping(value = "/variable1/{variable}")
    public String getVariable1(@PathVariable String variable) {
        log.info("@PathVariable을 통해 들어온 값: {}", variable);
        return variable;
    }

메서드를 통해 String 값으로 Corner를 주고 호출하면 다음과 같은 출력 결과가 나온다.

[2023-10-29 19:59:10.335] [INFO ] [http-nio-8080-exec-8] com.springboot.api.controller.GetController @PathVariable을 통해 들어온 값: Corner

 

더보기

QUIZ

1. 웹 애플리케이션 서버에서 값을 가져올 때 사용하는 API는?

답: GET API

 

2. URL 자체에 값을 담아 요청하는 어노테이션 이름은?

답: @PathVariable

 

3. URI에서 '?'를 기준으로 우측에 '{키}={값}' 형태로 구성된 요청을 전송하는 방법으로, 이 어노테이션을 명시해 쿼리 값과 매핑한다. 이 어노테이션은?

답: @RequestParam

 

4. 다른 레이어 간 데이터 교환에 활용되는 데이터 객체는?

답: DTO

 

5. POST API를 만들 때 사용하는 어노테이션 이름은?

답: @PostMapping

 

6. POST 요청에서는 리소스를 담기 위해 HTTP Body에 값을 넣어 전송하는데, 이를 위해 사용하는 어노테이션의 이름은?

답: @RequestBody

 

7. 애플리케이션이 동작하는 동안 시스템의 상태나 동작 정보를 시간순으로 기록하는 것은?

답: 로깅

 

8. @RequestParam을 활용한 GET 메서드를 구현하시오. (형식 자유)

ex)

// http://localhost:8080/api/v1/get-api/request1?name=value1&email=value2&organization=value3
    @GetMapping(value = "/request1")
    public String getRequestParam(@RequestParam String name, @RequestParam String email, @RequestParam String organization) {
        return name + " " + email + " " + organization;
    }

 

9. @PathVariable을 활용한 GET 메서드를 구현하시오. (형식 자유)

ex)

// http://localhost:8080/api/v1/get-api/variable1/{String 값}
    @GetMapping(value = "/variable1/{variable}")
    public String getVariable1(@PathVariable String variable) {
        return variable;
    }

 

 

 

[출처] 책 스프링 부트 핵심 가이드 (장정우), 위키북스

 

 

 

 

ⓒ 머핀

728x90

관련글 더보기