크게 정리를 해본다.
일단
자 이제 자세히 살펴본다.
log : 기록하다
logging 이란?
✔️ 방법은 크게 2가지
1) System.out.println()
이전까지 내가 사용했던 방법
하지만 실무에서는 사용하지 않는다
왜?!
2) 로깅(Logging) 라이브러리
로깅 라이브러리를 이용해서 남긴다.
최소한의 정보(날짜/시간/타입 등)를 제공
데이터를 서버에 저장하고 파일화 가능
✔️ 로그 레벨
https://blog.lulab.net/programmer/what-should-i-log-with-an-intention-method-and-level/
로그 레벨은 6가지
TREACE>DEBUG>INFO>WARN>ERROR>FATAL
어떤 로그를 언제 작성할 것인가?
최소한 명확한 목적은 가지는 레벨을 Error와 Info
try {
val user = userRepository.findById(userId)
user.modifyMobile(mobile)
userRepository.save(user)
} catch (e: ConnectionException) {
log.error("Fail to find a userDB is disconnected (userId: $userId)")
throw InvalidUserServiceException(ErrorCode.DB_CONNECTION_ERROR, e)
}
return try {
val user = userService.findById(userId)
log.info("User is ${user.status} status (userId: $userId)")
return user.status
} catch (e: NonexistentUserException) {
log.info("User is not exist (userId: $userId)")
return UserStatus.NOT_REGISTERED
}
Exception이 발생하는 경우 무의식적으로 ERROR 레벨을 사용하는 경우가 있으나 시나리오 상 의도된 Exception이라면 INFO레벨로 작성✔️ logging.level.root로 일정 레벨 이상의 로그만 출력하기
1. application.properties에 들어간다.
2. " logging.level.root = ERROR " 를 추가한다.
결과
저 노란색 레벨의 로그만 콘솔에 찍힌다.
1. application.properties에 들어간다.
2. " logging.level.root = WARN " 를 추가한다.
따라서 TRACE의 경우 모든 정보가 찍힌다.
✔️ 로그 선언
private Logger log = LoggerFactory.getLogger(getclass);
private static final Logger log = LoggerFactory.getLogger(Xxx.class);
@Slf4j : 롬복사용
✔️ 로그 호출
log.info("username={}",username);
System.out.println("username="+username);
올바른 로그 호출법
우리가 log.debug("username="+username);
로 호출하지 않는 이유가 있나요?
그 이유는 저 방식이 메로리를 잡아 먹을 수도 있기 때문이다.
만약의 개발자가 출력 레벨을 INFO로 설정했다면 저 내용을 출력되지 않는다. 그렇지만 Java는 연산을 미리 해버리기 때문에 username="Kim"이라는 내용을 메모리에 저장하는 것이다. 즉 출력되지도 않을 데이터를 Java가 연산을 진행해 메모리에 저장하는 것이다. 따라서 저렇게 호출하지 않도록 한다!
@RestController
public class MappingController {
private Logger log = LoggerFactory.getLogger(getClass());
@RequestMapping("/hello-basic")
public String helloBasic(){
log.info("helloBasic");
return "ok";
}
}
@GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable("userId") String data){
log.info("mappingPath userId={}",data);
return "ok";
}
@GetMapping("/mapping/users/{userId}/order/{orderId}")
public String mappingManyPath(@PathVariable String userId, @PathVariable Long orderId){
log.info("mappingPath userId={}, orderId={}",userId, orderId);
return "ok";
}
@GetMapping(value="/mapping-param",params = "mode=debug")
public String mappingParam() {
log.info("mappingParam");
return "ok";
}
@GetMapping(value="/mapping-header",headers= "mode=debug")
public String mappingHeader() {
log.info("mappingHeader");
return "ok";
}
@PostMapping(value = "/mapping-consume", consumes ="application/json")
public String mappingConsumes() {
log.info("mappingConsumes");
return "ok";
}
@PostMapping(value = "/mapping-produce", produces="text/html")
public String mappingProduces() {
log.info("mappingProduces");
return "ok";
}
@Slf4j
@RestController
public class RequestHeaderController {
@RequestMapping("/headers")
public String headers(HttpServletRequest request,
HttpServletResponse response,
HttpMethod httpMethod,
Locale locale,
@RequestHeader MultiValueMap<String, String> multiValueMap,
@RequestHeader("host") String host,
@CookieValue(value="myCookie",required = false) String cookie){
HTTP 요청 메세지에서 데이터를 조회할 때 우리는 3가지 방식을 앞서 배웠다.
one) Get을 통해서 url에 있는 쿼리 파라미터에서 데이터를 가지고 왔다.
two) Post를 통해서 url이 아닌 HTTP 메세지 바디에서 쿼리 파라미터를 가지고 왔다.
three) Http message body에 데이터를 직접 담아서 요청하며 HTTP API에서 주로 사용하였고 데이터 형식은 주로 JSON을 사용했다. HTTP 요청 형식은 POST,PUT,PATCH이다.
먼저 저 위에 있는 one과 two 방법에 대해서 알아보자
불편했던 방법에서 좀 더 편리한 방법으로 단계를 밟아보며 정리하였다.
GET : url파라미터
POST : client가 html이 띄워진 브라우저에 데이터 입력
@Slf4j
@Controller
public class RequestParamController {
@RequestMapping("/request-param-v1")
public void requestParamV1(HttpServletRequest request, HttpServletResponse response) throws IOException {
String username = request.getParameter("username");
int age = Integer.parseInt(request.getParameter("age"));
log.info("username={}, age={}",username,age);
response.getWriter().write("ok");
}
🔴 @RequestParam - 기본
@ResponseBody
@RequestMapping("/request-param-v2")
public String requestParamV2(@RequestParam("username") String memberName,
@RequestParam("age") int memberAge) {
log.info("username={}, age={}",memberName,memberAge);
return "ok";
}
if(HTTP 파라미터 이름 == 변수 이름)
@ResponseBody
@RequestMapping("/request-param-v2")
public String requestParamV2(@RequestParam String username,
@RequestParam int age) {
log.info("username={}, age={}",memberName,memberAge);
return "ok";
}
@RequestParam ("username") 저 괄호부분 생략 가능
추가로 변수의 형이 기본형이라면 int, String, Integer 등
@ResponseBody
@RequestMapping("/request-param-v2")
public String requestParamV2(String username,int age) {
log.info("username={}, age={}",memberName,memberAge);
return "ok";
}
@RequestParam도 생략 가능
하지만 이 방법은 추천하지 않는다고 한다. 명확하게 무엇을 요청 파라미터로 가지고 있는지 알 수 없기 때문이다.
🔴 @RequestParam -필수로 쿼리 파라미터를 요청
꼭 필수적으로 들어와야 하는 쿼리 파라미터 정보가 있도록
있어야 메소드가 호출되도록 설정할 수 있다.
바로 @RequestParam에 required 속성을 넣는 것이다.
@ResponseBody
@RequestMapping("/request-param-required")
public String requestParamRequired(@RequestParam(required = true) String username,
@RequestParam(required = false) int age) {
log.info("username={}, age={}",username,age);
return "ok";
}
우리가 local:8080/request-param-required?username="kim"를 호출하면 ERROR가 발생한다!
왜?
그것은 age의 형이 int이기 때문에 int의 기본값을 0이고 required가 false인 경우라도
ocal:8080/request-param-required?username="kim"&age=10
이라고 호출해야 하는 모순이 발생한다.
따라서
아래와 같이 age의 형을 Integer로 변경하여 null값이 기본 값으로 올수 있도록 한다.
@ResponseBody
@RequestMapping("/request-param-required")
public String requestParamRequired(@RequestParam(required = true) String username,
@RequestParam(required = false) Integer age) {
log.info("username={}, age={}",username,age);
return "ok";
}
🔴 @RequestParam - 파라미터에 값이 없는 경우 기본값 설정
@ResponseBody
@RequestMapping("/request-param-default")
public String requestParamDefault(@RequestParam(required = true, defaultValue = "guest") String username,
@RequestParam(required = false, defaultValue="-1") int age) {
log.info("username={}, age={}",username,age);
return "ok";
}
localhost:8080/request-param-default?username=
이렇게 빈 문자열을 넣어도 guest로 설정된다.
required는 의미가 없다. 이미 기본값을 통해서 값이 정해져 있기 때문이다.
🔴 @RequestParam - 그냥 모든 요청 파라미터를 받고 싶을 때
@ResponseBody
@RequestMapping("/request-param-map")
public String requestParamDefault(@RequestParam Map<String, Object> paramMap) {
log.info("username={}, age={}",paramMap.get("username"),paramMap.get("age"));
return "ok";
}
Map을 통해서 받지만 하나의 파라미터에 여러개의 값이 존재하는 경우 MultiValueMap을 이용해서 받는다.
HelloData 객체 생성
package hello.springmvc.basic;
import lombok.Data;
@Data
public class HelloData {
private String username;
private int age;
}
...
@Slf4j
@Controller
public class RequestParamController {
@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@ModelAttribute HelloData helloData){
log.info("username={} , age={}",helloData.getUsername(),helloData.getAge());
return "ok";
}
}
🔴 @ModelAttribute생략
@ResponseBody
@RequestMapping("/model-attribute-v2")
public String modelAttributeV2( HelloData helloData){
log.info("username={} , age={}",helloData.getUsername(),helloData.getAge());
return "ok";
}
@RequestParam과 @ModelAttribute를 사용하지 못한다. 쿼리 파라미터로 데이터가 넘어오는 것이 아니라 HTTP message body를 통해서 데이터가 넘어오기 때문이다.
🔴 request.getInputStream()을 이용
@Slf4j
@Controller
public class RequestBodyStringController {
@PostMapping("/request-body-string-v1")
public void requestBodyString(HttpServletRequest request, HttpServletResponse response)
throws IOException {
ServletInputStream inputStream = request.getInputStream();
//InputStream과 StreamUtils
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
log.info("messageBody={}",messageBody);
response.getWriter().write("ok");
}
🔴 InputStream(Reader)과 OutputStream(Writer)을 이용하여
HTTP 요청 메세지 바디를 직접 조회 및 결과 출력
@PostMapping("/request-body-string-v2")
public void requestBodyStringV2(InputStream inputStream, Writer responseWriter)
throws IOException {
//InputStream과 StreamUtils
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
log.info("messageBody={}",messageBody);
responseWriter.write("ok");
}
🔴 HttpEntity : HTTP header과 body 정보를 편리하게 조회하며 응답에도 사용 가능하다.
@PostMapping("/request-body-string-v3")
public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) {
String messageBody = httpEntity.getBody();
log.info("messageBody={}",messageBody);
return new HttpEntity<>("ok");//임에도 view로 조회되지 않는 HttpEntity
}
🔴 @RequestBody : 가장 간단하고 실무에서 많이 사용하는 방법
메세지 바디정보를 편리하게 조회
@ResponseBody//응답을 view가 아닌 HTTP 메시지 바디에 직접 담아 전달
@PostMapping("/request-body-string-v4")
public String requestBodyStringV4(@RequestBody String messageBody) {
log.info("messageBody={}",messageBody);
return "ok";
}
헤더정보가 필요하다면 HttpEntity를 사용하거나 @RequestHeader를 사용
JSON으로 받는 경우
ObjectMapper를 이용해야 한다.
https://recordsoflife.tistory.com/599
자세한 설명은 위 블로그를 참고하자.
간단하게 설명하면 JSON으로 들어온 데이터를 객체화 시키는 역직렬화와
객체화된 데이터를 JSON형식으로 바꾸는 직렬화를 손쉽게 만들어주는 것이 ObjectMapper이다.
예를 들어서
Car가 String color와 String type으로 이루어져 있다고 가정하면
ObjectMapper objectMapper = new ObjectMapper();
Car car = new Car("yellow","renault");
objectMapper.writeValue(new File("target/car.json"),car);
String json = "{ \"color\" : \"Black\", \"type\" : \"FIAT\" }";
Car car = objectMapper.readValue(json,Car.class);
🔴 HttpServletRequest의 getInputStream()을 이용
@Slf4j
@Controller
public class RequestBodyJsonController {
private ObjectMapper objectMapper = new ObjectMapper();
@PostMapping("/request-body-json-v1")
public void requestBodyJsonV1(HttpServletRequest request, HttpServletResponse response)
throws IOException {
ServletInputStream inputStream = request.getInputStream();
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
log.info("messageBody={}",messageBody);
HelloData data = objectMapper.readValue(messageBody, HelloData.class);
log.info("username={},age={}",data.getUsername(), data.getAge());
response.getWriter().write("ok");
}
🔴 @RequestBody를 이용해서 바로 조회하기
@ResponseBody
@PostMapping("/request-body-json-v2")
public String requestBodyJsonV2(@RequestBody String messageBody)
throws IOException {
log.info("messageBody={}",messageBody);
HelloData data = objectMapper.readValue(messageBody, HelloData.class);
log.info("username={},age={}",data.getUsername(), data.getAge());
return "ok";
}
🔴 ObjectMapper 없이 바로 객체로 변환하는 방법 - @RequestBody
@ResponseBody
@PostMapping("/request-body-json-v3")
public String requestBodyJsonV3(@RequestBody HelloData data)
throws IOException {
log.info("username={},age={}",data.getUsername(), data.getAge());
return "ok";
}
여기서 중요한 것은 앞서 배웠던 쿼리 파라미터를 바로 객체로 받아버리는 @ModelAttribute와 메소드의 매개변수 부분이 매우 흡사하다. 따라서
@RequestBody 애노테이션을 통해서 객체를 받을 때는 @RequestBody를 생략할 수 없다! 생략하면 @ModelAttribute로 인식해버린다.
🔴 ObjectMapper 없이 바로 객체로 변환하는 방법 - HttpEntity
@ResponseBody
@PostMapping("/request-body-json-v4")
public String requestBodyJsonV4(HttpEntity<HelloData> httpEntity)
throws IOException {
HelloData data = httpEntity.getBody();
log.info("username={},age={}",data.getUsername(), data.getAge());
return "ok";
}