로그, 로깅이란?

로깅이란 시스템 동작시 시스템의 상태와 작동 정보를 시간에 따라 기록하는 것

그 기록이 로그

 

로깅의 장점

소프트웨어의 디버깅이나 모니터링을 위하여 소프트웨어 동작 상태 정보를 기록해서 볼 수 있다.

-상황에 따라 Level별 메시지 가능

-프로그램의 실행에 대한 흐름과 에러 확인 가능

-자유로운 출력 형식과 위치 가능

-프레임워크를 이용하여 쉽게 설정 가능

 

로깅 프레임워크 3가지 종류들

1. logback(사용 추천)

log4j와 유사하지만 향상된 성능과 필터링 옵션, slf4j, 자동 리로드 기능 지원

 

spring boot 환경의 경우 spring-boot-starter-web > spring-boot-starter-logging에 기본적으로 logback 구현체가 포함!

-> 따로 프레임워크를 지정하지 않는다면 자동으로 logback이 적용된다.

 

공식 메뉴얼 링크

http://logback.qos.ch/manual/index.html

 

Logback Manual

The logback manual The complete logback manual documents the latest version of logback framework. In over 150 pages and dozens of concrete examples, it covers both basic and advanced logback features, including: the overall logback architecture discussion

logback.qos.ch

https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-logging

 

Core Features

Spring Boot lets you externalize your configuration so that you can work with the same application code in different environments. You can use a variety of external configuration sources, include Java properties files, YAML files, environment variables, an

docs.spring.io

2. log4j2

logback 이후에 나온 프레임워크

logback과 유사하나 Multi Thread 환경에서의 비동기 로거(Async Logger) 케이스에 처리량이 더 높고 시간 효율이 좋다.

 

spring boot 환경의 경우 log4j2를 사용하려면 dependency에서 logback 제거 작업 필요

 

3. slf4j

로깅에 대한 추상 레이어

코드를 일정하게 유지하면서 구현체의 전환 (다른 프레임워크로의 전환)을 쉽게 지원

logback, log4j2는 slf4j의 구현체

 

로그 레벨 

TRACE  <  DEBUG  <  INFO  <  WARN  <  ERROR

 

-ERROR : 오류가 발생

-WARN  : 처리 가능한 문제, 향후 에러의 원인이 될 수 있는 경고성 메시지

-INFO  : 상태 변경과 같은 정보성 로그를 표시한다.
-DEBUG : 디버깅하기 위한 정보. 
-TRACE : Debug보다 상세한 정보

 

차후에 스프링 부트에서 logback 설정을 하는 방법도 다뤄볼까 예정 중이다.

 

참고 사이트

https://wildeveloperetrain.tistory.com/36

https://goddaehee.tistory.com/206

최신 스프링 부트 프로젝트에선 (스프링부트 2.2x 이상 버전) Junit으로 JUnit5버전이 들어간다.

Junit4를 사용하고 싶다면 build.gradle dependencies에 아래 코드를 추가하면 된다.

 

testImplementation("org.junit.vintage:junit-vintage-engine") {
 exclude group: "org.hamcrest", module: "hamcrest-core"
}

 

추가적으로 JUnit4에서 @Test는 org.junit.Test를 사용해야한다.

스프링 부트 2.2x 버전 이후부터는 자동적으로 JUnit5가 들어간다.

 

내가 현재 듣고 있는 스프링 부트 강의는 예전 강의라 JUnit4를 사용하고 있는데 Junit5를 써보고 싶었고 현재 진행 중인 스프링 부트 프로젝트도 JUnit5를 쓸 생각이기 때문에 Junit5로 구글링해서 코드를 작성해봤다.

 

작성하면서 발견한 주요 차이점들을 적어보자면 @RunWith(SpringRunner.class)이 Junit5에선 @SpringBootTest에 포함되면서 생략 가능해졌다.

Junit4에서 사용하던 @Test(expected = 클래스명.class)이 사라지면서 

IllegalStateException thrown = assertThrows(IllegalStateException.class, () -> userService.join(user2));
assertEquals("이미 존재하는 회원입니다.", thrown.getMessage());

와 같은 코드를 사용해야했다.

 

UserService 검증 Juni4 코드

package simplebook.simpleshop.service;

import simplebook.simpleshop.Domain.User;
import simplebook.simpleshop.Repository.UserRepository;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class UserServiceTest {

 @Autowired
 UserService userService;
 @Autowired
 UserRepository userRepository;
 
 @Test
 public void 회원가입() throws Exception {
  //Given
  User user = new User();
  user.setUserName("bryn");
  //When
  Long saveId = userService.join(user);
  //Then
  assertEquals(user, userRepository.findOne(saveId));
 }
 
 @Test(expected = IllegalStateException.class)
 public void 중복_회원_예외() throws Exception {
 //Given
 User user1 = new User();
 user1.setUserName("bryn");
 User user2 = new User();
 user2.setUserName("bryn");
 //When
 userService.join(user1);
 userService.join(user2); //예외가 발생해야 한다.
 //Then
 fail("예외가 발생해야 한다.");
 }
}

 

UserService 검증 Junit5 코드

package simplebook.simpleshop.service;

import static org.junit.jupiter.api.Assertions.*;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import simplebook.simpleshop.Domain.User;
import simplebook.simpleshop.Repository.UserRepository;
import javax.transaction.Transactional;

@ExtendWith(SpringExtension.class)
@SpringBootTest
@Transactional
class UserServiceTest {

    @Autowired
    UserService userService;
    @Autowired
    UserRepository userRepository;

    @Test
    public void 회원가입() throws Exception {
        //Given
        User user = new User();
        user.setUserName("bryn");
        //When
        Long saveId = userService.join(user);
        //Then
        assertEquals(user, userRepository.findOne(saveId));
    }

    @Test
    public void 중복_회원_예외() throws Exception {
        //Given
        User user1 = new User();
        user1.setUserName("bryn");
        User user2 = new User();
        user2.setUserName("bryn");
        //When
        userService.join(user1);
        userService.join(user2); //예외가 발생해야 한다.
        //Then
        IllegalStateException thrown = assertThrows(IllegalStateException.class, () -> userService.join(user2));
        assertEquals("이미 존재하는 회원입니다.", thrown.getMessage());
    }

}

 

+추가)

위에서 쓰인 User domain과 다른 domain임을 참고바랍니다.

 

User 검증 Juni4 코드

import jpabook.simpleshop.domain.User;
import jpabook.simpleshop.repository.UserRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserRepositoryTest {
 @Autowired UserRepository userRepository;
 @Test
 @Transactional
 @Rollback(false)
 public void testUser() {
 
 User user = new User();
 user.setUsername("user1");
 
 Long savedId = userRepository.save(user);
 User findUser = userRepository.find(savedId);
 
 Assertions.assertThat(findUser.getId()).isEqualTo(user.getId());
 Assertions.assertThat(findUser.getUsername()).isEqualTo(user.getUsername());
 }
}

 

User 검증 Junit5 코드

package simplebook.simpleshop.User;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import javax.transaction.Transactional;

@ExtendWith(SpringExtension.class)
@SpringBootTest
class UserRepositoryTest {
    @Autowired UserRepository userRepository;

    @Transactional
    @Test
    public void testUser() throws Exception{
        User user=new User();
        user.setUserName("user1");
        
        Long saveId = userRepository.save(user);
        User findUser = userRepository.find(saveId);
        
        Assertions.assertThat(findUser.getId()).isEqualTo(user.getId());
        Assertions.assertThat(findUser.getUserName()).isEqualTo(user.getUserName());
    }
}

 

Junit5 테스트 코드를 통해 UserRepository가 정상적으로 작동함을 확인했다.

 

JPA와 DB 연결 관련 오류였다.

먼저 DB 연결 설정파일에 관한 부분에 오타가 없는지 확인해야한다.

필자는 datasource 연결 주소에 오타가 있었다. 

 

이후 실행을 했는데도 똑같은 오류가 발생했다.

생각해보니 h2 데이터베이스를 실행 안시켜서 그런가하고 cmd 창에서 h2 폴더에 접근한 후 h2 데이터베이스를 실행시켰더니 제대로 작동했다. 

 

RDS는 데이터베이스 서버를 임대받아서 오는 것이기 때문에 연결 정보만 입력해주면 서버가 항상 돌아가고 따로 실행을 시켜줄 필요가 없었는데 h2 데이터베이스는 로컬 서버로 돌아가는 데이터베이스인지 cmd 창에서 실행을 시켜준 후 스프링 부트를 돌려야 정상적으로 작동했다. RDS에 익숙해서 H2 데이터베이스 쪽에서 알아서 자체 서버를 연결해주나 어렴풋이 생각하고 넘겼었는데 생각해보니 H2 데이터베이스를 까는 것 자체가 로컬에서 작업한거라 로컬 서버로 돌아가는게 맞는 것 같다.

 

+) 이후 H2 데이터베이스에 대해 좀 알아보니 로컬 환경에서 작업, 테스트 시에 많이 사용하는 데이터베이스라고 한다.

로컬 서버로 운영되는 데이터베이스가 맞나보다. 

테스트 코드를 JUnit5로 처음 작성해보고 돌렸는데 계속 작동을 안해서 코드 문제인줄 알고 몇시간 헤맸는데 알고보니 H2데이터베이스를 실행 안시키고 돌려서 그런거였다. 강의를 들으며 사용한 기술이라 일단 따라쳐보고 시작했는데 새로운 프로그램이나 툴을 쓸때는 잘 알아보고 쓰자!

 

프로젝트를 진행하면서 .properties 파일을 .yml 파일로 변경했다.

기본적인 작성 방식은 다르지만 설정 내용들을 비슷해서 각 문법에 맞게끔 변환만 해주면 된다.

 

아래는 내가 .properties 파일을 .yml 파일로 변경한 일부 예시이다.

변경 이후 설정한 포트 번호로 스프링부트가 잘 작동함을 확인했다.

 

한글로 적힌 부분들은 각자의 환경에 맞게 넣어주시면 됩니다.

 

application.properties 

spring.profiles.include=prod,jwt,oauth,redis,mail

application.yml

spring:
  application:
    name: 9T
  profiles:
    include: jwt,oauth,redis,mail
    active: prod

application-prod.properties

spring.config.activate.on-profile=prod

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://RDS 엔드포인트:3306/데이터베이스 테이블 이름?serverTimezone=Asia/Seoul&characterEncoding=UTF-8
spring.datasource.username=RDS 사용자 이름
spring.datasource.password=RDS 비밀번호
spring.jpa.hibernate.ddl-auto=update
spring.jpa.generate-ddl=true

spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true

server.port=9000

application-prod.yml

server:
  port: 9000

spring:
  application:
    name: prod

  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver # mysql 8버전
    # driver-class-name: com.mysql.jdbc.Driver # mysql 5버전
    url: jdbc:mysql://RDS 엔드포인트:3306/데이터베이스 이름?autoReconnect=true&serverTimezone=Asia/Seoul&characterEncoding=UTF-8
    username: RDS 유저이름
    password: RDS 비밀번호

  jpa:
    show-sql: true
    generate-ddl: true
    #database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
    hibernate:
      ddl-auto: update
    properties:
      hibernate.enable_lazy_load_no_trans: true
      hibernate.format_sql: true

application-dev.yml

server:
  port: 8080

spring:
  application:
    name: dev

  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver # mysql 8버전
    # driver-class-name: com.mysql.jdbc.Driver # mysql 5버전
    url: jdbc:mysql://RDS 엔드포인트:3306/데이터베이스 이름?autoReconnect=true&serverTimezone=Asia/Seoul&characterEncoding=UTF-8
    username: RDS 유저이름
    password: RDS 비밀번호

  jpa:
    show-sql: true
    generate-ddl: true
    #database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
    hibernate:
      ddl-auto: update
    properties:
      hibernate.enable_lazy_load_no_trans: true
      hibernate.format_sql: true

 

application-redis.properties

spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=비밀번호
spring.redis.lettuce.pool.max-active=10
spring.redis.lettuce.pool.max-idle=10
spring.redis.lettuce.pool.min-idle=2

application-redis.yml

spring:
  redis:
    host: localhost
    port: 6379
    password: 비밀번호
    jedis:
      pool:
        max-active: 10
        max-idle: 10
        min-idle: 2

application-jwt.yml

spring:
  jwt:
    secretKey: 시크릿키

application-oauth.yml

spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id:클라이언트아이디
            client-secret: 클라이언트시크릿키
            redirect: 리다이렉트 주소 예시는 https://localhost:9000/googleLogin
            url:
              login: https://accounts.google.com/o/oauth2/v2/auth
              token: https://oauth2.googleapis.com/token
              profile: https://www.googleapis.com/oauth2/v3/userinfo

 

 

참고 사이트

https://blog.naver.com/PostView.nhn?blogId=codingspecialist&logNo=221499365350

https://github.com/SangHyunGil/SpringSecurityJWT-Local-OAuth2-EmailAuth-Redis/blob/main/src/main/resources/application.yml

https://blog.startsomething.dev/2021/05/08/yaml%EC%97%90%EC%84%9C-%EB%94%B0%EC%98%B4%ED%91%9C%EB%A5%BC-%EC%8D%A8%EC%95%BC-%EB%90%A0%EA%B9%8C%EC%9A%94/

https://velog.io/@code12/spring-boot-OAuth%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%B4-%EC%86%8C%EC%85%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0-myBatis%EA%B5%AC%EA%B8%80-%EB%A1%9C%EA%B7%B8%EC%9D%B8

https://velog.io/@seho100/Spring-boot%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-JWT-%EA%B5%AC%ED%98%84

application.properties와 applicaiton.yml 파일 모두 외부 설정 값 등을 관리하는 파일

스프링부트가 자동으로 로딩하는 설정 파일들

maven이 gradle로 넘어가고 있듯이 xml, properties 파일에서 yml로 넘어가는 트렌드

 

applicaion.properties

STS 진행시 자동 생성

파일 포맷 name=vaule

 

application.yml

인텔리제이에서  진행시 자동 생성

가독성이 좋다

.xml 파일과 거의 유사

계층 구조 형식으로 값 지정 가능

.yml 파일을 이용하기 위해선 SnakeYAML 라이브러리가 필요한데 이는 보통 spring-boot-starter의 의존성이 기본 제공

 

참고사이트

https://sillutt.tistory.com/entry/IntelliJ-applicationproperties-%EA%B3%BC-applicationyml%EC%9D%98-%EC%B0%A8%EC%9D%B4?category=365562

https://velog.io/@tjswlsdl135/application.properties-vs-application.yml

https://goddaehee.tistory.com/213

 

 

+ Recent posts