로그(log)란? 프로그램 또는 시스템에서 발생하는 이벤트, 정보, 상태, 오류 등을 기록한 것을 말하고, 이러한 로그를 생성하고 저장하는 것을 로깅(logging)이라고 한다. 로그는 프로그램의 동작을 추적해 문제를 해겨하거나 성능을 분석하기 위한 목적으로 활용된다.
스프링부트는 기본적으로 로그백(logback)이라는 로깅 도구를 사용하여 로그를 관리한다. 현재 서버에 생성되는 로그파일(sbb.log)에는 몇 가지 문제가 있다. 1. 서비스를 다시 실행할 경우 이전 로그가 삭제된다. 2. 로그가 쌓일수록 파일의 용량이 커지며 무한대로 증가한다. 3. 로그 시간이 시스템 시간이 아닌 UTC시간으로 출력된다.
logging.logback.rollingpolicy.max-history=30 -로그 파일을 유지할 기간(일)을 설정한다. 30일만 유지하도록 지정. logging.logback.rollingpolicy.max-file-size=100MB -로그파일 1개의 최대용량을 설정한다. 100MB로 설정. logging.file.name=logs/sbb.log -로그 파일의 이름을 설정한다. logs디렉터리 아래에 sbb.log 생성. logging.logback.rollingpolicy.file-name-pattern=${LOG_FILE}.%d{yyyy-MM-dd}-%i.log -로그 파일의 용량이 초과하거나 날짜가 변경될 경우 새로 만들어질 로그 파일의 이름 규칙 logging.pattern.dateformat=yyyy-MM-dd HH:mm:ss.SSS,Asia/Seoul -로그 출력시 출력되는 날자와 시간의 형식을 지정한다.
이처럼 수정 후 빌드 버전(sbb-0.0.3.jar)을 변경하여 배포 파일을 새로 작성하여 서버에 업로드하자.
아래 글 참고
build.gradle
...
group = 'com.mysite'
version = '0.0.3'
...
위처럼 수정한 다음 build.gradle 우클릭 [Gradle -> Refresh...]
[Run -> Run Configuration]클릭 후 Gradle Task의 'sbb' Run하기
sbb-0.0.3.jar 파일이 생성된 것을 확인
파일질라에서 서버로 업로드하기
서버에서 start.sh 변경하기
1. 로깅 설정을 변경했으므로 서버에서 적용하기 위해 mobaXterm으로 돌아가 파일을 수정해보자.
nano start.sh 입력 후 위처럼 파일 수정하기. 수정 후에는 Ctrl+O -> Ctrl+X
2. 기존의 로그는 더 이상 필요하지 않으므로 삭제하기.
ubuntu@jumpto:~/sbb$ rm sbb.log
3. SBB 서비스를 다음과 같이 재시작하기
ubuntu@jumpto:~/sbb$ ./stop.sh SBB is not running ubuntu@jumpto:~/sbb$ ./start.sh
4. 재시작하면 다음과 같이 logs 디렉터리가 생성되고 그 아래에 sbb.log라는 파일이 생성된 것을 확인할 수 있다.
사용자 로그 작성하기
위에서는 스프링 부트 프레임워크 자체가 출력하는 로그를 관리하는 방법을 말했다.
개발자가 코드를 작성하면서도 로그를 출력할 수 있다.
질문 목록 조회 시 GET방식으로 요청되는 매개변수의 입력값을 로그로 출력해보자.
question/QuestionController.java
@Slf4j
@RequestMapping("/question")
@RequiredArgsConstructor
@Controller
public class QuestionController {
private final QuestionService questionService;
private final UserService userService;
@GetMapping("/list")
public String list(Model model, @RequestParam(value = "page", defaultValue = "0")int page,
@RequestParam(value="kw", defaultValue = "") String keyword) {
log.info("page:{}, kw:{}", page, keyword);
Page<Question> paging = this.questionService.getList(page, keyword);
model.addAttribute("paging",paging);
model.addAttribute("keyword", keyword);
return "question_list";
}
먼저 컨트롤러에 롬복이 제공하는 @Slf4j 어노테이션을 추가하고 메서드 안에는 'log.info("page:{}, kw:{}", page, keyword);'를 추가했다.
* 로그 단계
- trace(1단계): 가장 낮은 로그 레벨, debug보다 정보를 훨씬 상세하게 기록. - debug(2단계): 디버깅 목적 - info(3단계): 주요 이벤트나 상태 등의 일반 정보를 출력할 목적 - warn(4단계): 문제가 발생할 가능성이 있는 상태나 상황 등 경고 정보 출력 - error(5단계): 심각한 문제나 예외 상황에 대한 오류 정보 출력 목적 - fatal(6단계): 가장 높은 로그 레벨, 프로그램 기능 일부가 실패하거나 오류 발생 등 심각한 문제 정보 출력
ubuntu@jumpto:~$ date Tue May 28 09:59:49 KST 2024
서버에 자바 설치하기
스프링 부트를 사용하려면 반드시 자바가 설치되어 있어야 한다.
1. 서버에 자바 설치 유무를 확인하기 위해 java 명령을 입력해보자.
ubuntu@jumpto:~$ java Command 'java' not found, but can be installed with: sudo apt install openjdk-11-jre-headless # version 11.0.20.1+1-0ubuntu1~22.04, or sudo apt install default-jre # version 2:1.11-72build2 sudo apt install openjdk-17-jre-headless # version 17.0.8.1+1~us1-0ubuntu1~22.04 sudo apt install openjdk-18-jre-headless # version 18.0.2+9-2~22.04 sudo apt install openjdk-19-jre-headless # version 19.0.2+7-0ubuntu3~22.04 sudo apt install openjdk-8-jre-headless # version 8u382-ga-1~22.04.1
자바가 설치되어 있지 않아 위와 같은 메시지가 출력된다.
2. 자바를 설치하기 전에 먼저 sudo apt update 명령을 수행해 우분투 패키지를 최신으로 업데이트해야한다.
설치 중간에 'Do you swant to continue?' 라는 문구가 나오면 y를 입력하면 된다.
4. 설치를 완료했다면 java -version을 실행해보자.
ubuntu@jumpto:~$ java -version openjdk version "19.0.2" 2023-01-17 OpenJDK Runtime Environment (build 19.0.2+7-Ubuntu-0ubuntu322.04) OpenJDK 64-Bit Server VM (build 19.0.2+7-Ubuntu-0ubuntu322.04, mixed mode, sharing)
19버전이 정상적으로 설치되었다.
프로젝트 디렉터리 생성하기
서버에 SBB 서비스를 적용하기 위해 홈 디렉터리 아래에 sbb디렉터리를 생성하자.
ubuntu@jumpto:~$ mkdir sbb //sbb 디렉터리 생성 ubuntu@jumpto:~$ ls //현재 디렉터리 내 확인 sbb
STS에서 SBB 배포 파일 생성하기
이제 서버에 올릴 SBB 배포 파일을 만들어보자. 배포 파일은 단 하나의 jar 파일로, 서버에서 이 파일을 구동시킨다.
1. jar 파일은 Gradle을 사용하여 생성한다. 그 전에 Gradle에 JDK 설정을 먼저 해야 한다. 그러기 위해 [Widow -> Preference -> Gradle] 클릭하고 'Advanced Options'의 'Java home'항목에 JDK의 디렉터리를 입력한다.
2.AWS 서버에는 JDK 19버전을 설치했으므로 build.gradle java항목을 아래와 같이 수정하자.
...
java {
sourceCompatibility = '19'
}
...
3. 서버에 적용할 SBB 배포파일인 jar파일을 만들기 위해 [Run -> Run Configurations]선택 후 'Gradle Task'를 선택하고 마우스 우클릭 후 [New Configuration]을 클릭하자.
4. 그럼 다음과 같은 화면이 나타나면 순서대로 각 항목을 입력해보자. 그리고 Apply 클릭 후 Run을 클릭하자.
5. 완성되면 하단에 Gradle Executions 창이 나타나고 진행결과나 나온다.
6. 오류가 없다면 프로젝트 워크스페이스 하단에 build 디렉터리가 생성되고 배포파일이 생성된 것을 확인할 수 있다.
SFTP로 SBB 배포 파일 전송하기
생성된 .jar파일을 파일질라를 이용하여 서버에 전송해보자.
1. 다음과 같이 sbb-0.0.1-SNAPSHOT.jar 파일을 찾아 서버의 /home/ubuntu/sbb 디렉터리로 드래그 앤 드롭해보자.
2.MobaXterm을 사용하여 다시 서버에 접속 후 아래와 같이 입력하여 배포 파일을 실행하자. 실행된 서비스를 종료하려면 Ctrl+C를 누른다.
ubuntu@jumpto:~$ cd sbb ubuntu@jumpto:~/sbb$ ls sbb-0.0.1-SNAPSHOT.jar ubuntu@jumpto:~/sbb$ java -jar sbb-0.0.1-SNAPSHOT.jar
3. 웹 브라우저를 실행하고 앞서 설정한 고정IP:8080을 입력하여 접속해보자.
http://15.164.187.123:8080 과 같이 입력하면 아래와 같은 화면을 볼 수 있다.
그런데 이전의 데이터들이 다 사라졌다. 왜냐하면 서버에서도 H2 DB의 데이터 파일인 local.mv.db 파일이 새로 만들어지기 때문이다.
1. AWS 라이트세일의 메인 화면 왼쪽에 있는 [Networking] -> [Create static IP] 클릭한다.
2. 다음과 같은 화면에서 'Attach to an instance'에서 [Ubuntu-1]을 선택하고 고정 IP명 확인한 후 [Create]을 클릭하여 고정 IP를 생성한다.
3. 다음과 같이 고정 IP주소를 얻게 된다. 개인마다 IP는 모두 다르며, 나의 경우 '15.164.187.123'이라는 고정 IP가 생성되었다.
방화벽 해제하기
그동안 로컬서버의 8080번 포트를 사용해왔다. 이제 AWS에 설치할 SBB서비스를 외부에서도 접속할 수 있어야 하기 때문에 이 8080포트의 방화벽을 해제해야 한다.
1. AWS 라이트세일의 메인화면 왼쪽 [Instance] -> [Ubuntu-1] 클릭
2. 이어서 상단 [Networking] -> [+Add rule] 클릭
3. 아래와 같이 포트번호는 '8080'으로 입력하고 [Create]를 클릭하여 생성한다. 이제 외부에서 고정IP의 8080포트로 쉽게 접근할 수 있다.
서버 접속 프로그램 설치하기
SBB서비스를 서버에 배포하기 위해 서버에 접속하기 위한 프로그램을 서치하고 환경설정을 해야 한다.
프라이빗 키 만들기
SSH또는 SFTP 프로그램으로 서버에 접속하기 위해서는 AWS 계정의 SSH 키가 필요하다.
SSH키는 프라이빗 키로 서버에 안전하게 접속할 수 있다.
SSH(Secure Shell)란? 네트워크상의 다른 컴퓨터에 로그인하거나 원격 서버의 명령을 수행하는 프로토콜이나 프로그램을 가리킨다. SFTP(SSH File Transfer Protocol)란? SSH프로토콜 위에서 파일을 안전하게 전송하고 관리하는 역할을 한다. 원격 서버간에 파일을 업로드하고 내려받을 때 사용한다.
1. AWS 라이트세일 메인화면 [계정] -> [Account] 클
2. [SSH keys] -> [다운로드] 클릭
그럼 아래와 같은 LightsailDefaultKey-ap-northeast-2.pem이라는 SSH키 파일을 내려받을 수 있다.
3. 이 키를 컴퓨터의 루트(C:/) 디렉토리에 붙여넣고 '이름 바꾸기'로 파일명은 'mysite.pem'으로 변경하자.
SSH 클라이언트 설치하기
SSH 클라이언트는 SSH 프로토콜을 사용하여 우너격 서버에 접속해 명령을 실행할 수 있는 프로그램이다.
보통 검색 대상으로는 질문 제목, 질문 내용, 질문 작성자, 답변 내용, 답변 작성자 정도가 있다.
만약 이러한 조건으로 검색하려면 아래와 같은 SQL 쿼리문이 필요하다.
select distinct
q.id,
q.author_id,
q.content,
q.create_date,
q.modify_date,
q.subject
from question q
left outer join site_user u1 on q.author_id = u1.id
left outer join answer a on q.id = a.question_id
left outer join site_user u2 on a.author_id = u2.id
where
q.subject like '%스프링%'
or q.content like '%스프링%'
or u1.username like '%스프링%'
or a.content like '%스프링%'
or u2.username like '%스프링%'
이 쿼리문은 question, answer, site_user 3개의 테이블을 대상으로 '스프링' 문자열이 포함된 데이터를 검색한다.
검색된 데이터는 중복되지 않아야 하므로 distinct를 사용하여 제거했다.
이 쿼리를 이용하여 JPA를 사용해 자바 코드로 만들 것이다.
JPA의 Specification 인터페이스 사용하기
Specification 인터페이스는 DB검색을 더 유연하게 하고, 복잡한 검색을 처리할 수 있다.
먼저 서비스에 search메서드를 추가하자.
/question/QuestionService.java
private Specification<Question> search(String keyword){
return new Specification<Question>() {
private static final long serialVersionUID = 1L;
@Override
public Predicate toPredicate(Root<Question> q, CriteriaQuery<?> query, CriteriaBuilder cb) {
query.distinct(true); //중복 제거
Join<Question, SiteUser> u1 = q.join("author", JoinType.LEFT);
Join<Question, Answer> a = q.join("answerList", JoinType.LEFT);
Join<Answer, SiteUser> u2 = a.join("author", JoinType.LEFT);
return cb.or(cb.like(q.get("subject"), "%"+keyword+"%"), //제목
cb.like(q.get("content"), "%"+keyword+"%"), //내용
cb.like(u1.get("username"), "%"+keyword+"%"), //질문 작성자
cb.like(a.get("content"), "%"+keyword+"%"), //답변 내용
cb.like(u2.get("username"), "%"+keyword+"%")); //답변 작성자
}
};
}
아래는 사용된 변수 설명이다. - q: Root자료형으로 기준이 되는 Question 엔티티의 객체를 말한다. - u1: Question 엔티티와 SiteUser 엔티티를 left outer join하여 만든 SiteUser 엔티티의 객체이다. - a: Question 엔티티와 Answer 앤티티를 left outer join하여 만든 Answer 엔티티의 객체이다. - u2: 앞에 작성한 답변 엔티티인 a와 다시 SiteUser 엔티티와 left outer join하여 만든 SiteUser엔티티이다.
그리고 검색어가 포함되어 있는지 like로 확인하기 위해 각각 cb.like를 사용하고 이들을 최종적으로 cb.or로 OR검색을 한다.
일단 검색어인 keyword를 매개변수에 추가했고, questionService.getList 메서드의 매개변수를 위에서 변경했으므로 전달값을 수정해준다. getList는 검색어가 있을수도, 없을수도 있기 때문에 기본값으로는 빈 문자열로 지정했다. 또 화면에서 검색 시 검색 키워드를 유지하기 위해 model.addAttribute("keyword", keyword)로 키워드 값을 저장했다.