https://serverfault.com/questions/647283/cant-connect-to-local-mysql-server-through-socket-var-lib-mysql-mysql-sock

필자는 ELK 및 spring MSA를 활용해보기 위해 테스트 서버에 docker를 설치하기로 했다.

 

서버 OS 버전은 centos 7 이다.

 

1. yum 업데이트

# yum -y update

-> 를 수행하여 yum을 최신버전으로 맞춰 준다.

 

2. yum을 통한 docker 설치

# yum -y install docker docker-registry

 

3. 설치 완료 후 부팅 자동 실행 설정과 실행 및 실행 상태 확인

# systemctl enable docker.service -> 부팅 자동 실행 설정

# systemctl start docker.service -> 도커 실행

# systemctl status docker.service -> 실행 상태 확인

회사 프로젝트중에 모바일 푸시 서비스 관련 개발을 하다가 우연히 HTTP2 프로토콜에 대해 접하게 되었다.

HTTP2 프로토콜이란.. 결론적으로 HTTP/1.x 방식에 비해 빠르다는 것이다.

정확한 내용은 아래의 URL을 참고하자.

https://developers.google.com/web/fundamentals/performance/http2/?hl=ko

또한 HTTP2 프로토콜은 HTTP/1.x 을 대체하는 것이 아니라, 확장한다는 개념이기 때문에 HTTP2프로토콜로 사용한다고 해서 기존 어플리케이션을 코드를 수정할 필요는 없고 단순히 프로토콜을 사용하기 위한 설정만 해주면 된다.

먼저, HTTP2를 사용하기 위해서는 HTTPS를 설정해야 한다.

때문에, tomcat에서 SSL을 사용하기 위한 개발용 keystore 파일을 생성해보자.(pkcs12 포맷으로 생성)

sts terminal창에서 아래의 명령어를 실행한 후 아래와 같이 패스워드와 기타 정보들을 입력하면 생성이 된다.

keytool -genkey -alias javaboja -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore keystore.p12 -validity 4000

생성한 후에, application.properties에서 ssl 설정과 HTTP2 설정을 하면 끝이다.

<application.properties>

server.ssl.key-store=keystore.p12
server.ssl.key-store-type=PKCS12
server.ssl.key-store-password=
server.ssl.key-alias=javaboja
server.port=443
server.http2.enabled=true

실제 HTTP2프로토콜 사용을 확인하기 위해 test용 controller와 html을 생성하자.

 

<HomeController.java>

package com.javaboja.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {

	@GetMapping("/test")
	public String test() {
		return "test";
	}

}

<test.html>

<!DOCTYPE html>
<html>
<head>
<meta charset="EUC-KR">
<title>Insert title here</title>
</head>
<body>
	Hello!!
</body>
</html>

 

프로젝트를 실행한 후 크롬 콘솔창을 보면 Protocol에 h2라고 되어있는데 HTTP2 프로토콜을 사용하고 있다는 것을 의미한다.

- STS 4.2.0

- open jdk 1.8

 

Could not run build action using Gradle distribution 'https://services.gradle.org/distributions/gradle-5.4.1-bin.zip'. 

 

집에서 작업하던 spring boot 프로젝트를 github를 통해 회사 PC에 check out 했더니 위와 같은 에러가 발생하면서, gradle build가 제대로 되지 않았다. 회사에서 원래 사용하던 sts 3.9.3과 open jdk 9 버전을 사용하고 있던 부분 때문에 생긴 문제로 판단이 되었고, gradle 관련 설정을 수정해야 정상적으로 동작할 것 같았다.

 

프로젝트의 설정을 들어가보자.

설정을 들어가면 아래와 같이 화면이 나오는데,

기존 사용하던 툴(sts 3.9.3)로 인해 생성된 C:\Users\carrey\.gradle경로를 sts4.2.0이 동일하게 사용하려다 보니,

기존 버전 것 과의 충돌이 일어나거나, 버전 즉 싱크가 어긋나서 제대로 build되지 않았을 것이라고 판단하여, gradle user home 경로를 새로운 경로로 잡아 주었다.

또한 회사 PC에 있는 sts3.9.3은 jdk 9를 사용하였고, 집 PC에 있는 sts4.2.0은 jdk 8을 사용하였다. 이부분이 또 다르게 적용 될 수 있다고 판단이 되어, java home을 jdk8로 집 PC 환경과 동일하게 맞춰 주었다.

그리고 다시 빌드 하니 정상 적으로 되더라.

 

정말 spring은 셋팅이 너무 힘들다..

 

'spring boot' 카테고리의 다른 글

spring boot에 HTTP2 방식을 적용해보자.  (0) 2019.11.04
lombok을 활용하여 보자  (0) 2019.06.19
sts4, spring boot(jar), gradle, jpa 시작  (0) 2019.06.15

먼저, lombok을 설치하자, 공식사이트를 들어가보면 설치하는 방법에 대해 설명되어 있다. 

https://objectcomputing.com/resources/publications/sett/january-2010-reducing-boilerplate-code-with-project-lombok

 

1. getter, setter 자동 생성

 

@Getter, @Setter : 각 전역변수에 선언해 주면 자동으로 getter, setter 메서드를 생성해 주지 않아도 된다.

public class User {
    @Getter
    @Setter(AccessLevel.PROTECTED)
    private String name;
}

또한 @Setter(AccessLevel.PROTECTED) 접근권한을 제어할 수 있다.(AccessLevel.PUBLIC, AccessLevel.PRIVATE 등등..)
AccessLevel.NONE 설정을 통해 메소드 생성을 제한시킬 수 도 있다. -> 잘 쓸 것 같지는 않음

 

2. NULL 예외처리

 

@NonNull 말 그대로 NonNull이다. 멤버필드에 선언해 주면, 해당 변수 Setter에 null값이 들어 올 때 NullPointException을 발생시킨다.

public class User {

    @NonNull
    private String name;
}

 

3. 현재 객체 정보 출력

 

@ToString -> 객체.toString() 메서드를 대체하는 어노테이션으로 callSuper값을 true로 할 경우 상속받은 클래스의 정보까지 출력되며,
exclude를 통해 제외하고자 하는 변수를 선택할 수 있다. 설정하지 않을 경우 @ToString만 선언하면 된다.

@ToString(callSuper=true,exclude="phoneNumber")
public class User extends Address{

    private String name;
    private String phoneNumber;

    /*
    @override
    public toString(){
        return "Address(super = " + super.toString() +
              ", name = " + name + ")";
    }
    */
}

 

4. 객체 비교

 

@EqualsAndHashCode -> 객체 비교에 사용되는 메소드를 대체하는 어노테이션

 

5. setter+getter+toString+nunNull+equalsAndHashCode 모두 사용할 경우

 

@Data -> 위의 어노테이션을 합체시킨 어노테이션, 그러나 각 어노테이션에서 설정할 수 있는 세분성까지는 제공하지 못한다. 위에 중요 따로 설정해서 사용할 어노테이션을 설정한 후, 추가적으로 사용하는 것을 권장한다.

@Data(staticConstructor="of") 매개 변수의 값을 원하는 메소드 이름(of)으로 설정하면 생성자를 private로 만들어 주고, 주어진 이름의 static factory method (new를 사용하지 않고 객체를 만들 수 있음)를 만들어준다.

@Data(staticConstructor="of")
public class User {

    private String name;
}


public class Addredss {

    static public void main(String []args){
        User.of().getName();
    }
}

 

6. 자동 자원 close

 

@cleanup -> try finally에서 close()를 대신 호출해주는 어노테이션

public class User {

    static public void main(String [] args){
        try {
            @Cleanup ByteArrayOutputStream baos = new ByteArrayOutputStream();
                baos.write(new byte[] {'Y','e','s'});
               System.out.println(baos.toString());
           } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 

7. 동기화

 

java의 synchronized를 사용하면 객체 레벨에서 락이 걸려 여러가지 문제가 발생할 수 있다.
@Synchronized 어노테이션을 사용하면 가상의 필드 레벨에서 보다 더 안전하게 락을 걸어 준다.

@Synchronized
public class User {
    public void ok(){
        System.out.println("ok");
    }
}

 

8. 불변 객체 클래스

class에 @Value 어노테이션을 선언하면, 불변 객체를 만들 수 있다.

@Value
public class User {
    public String name; //setter 메소드 사용 불가능
}

 

9. 로그를 편하게

 

class에 @Slf4j, @Log 등 해당 어노테이션을 선언하면, log관련 static 메소드를 클래스 별로 선언할 필요가 없다.

@Controller
@Slf4j
public class HomeController {

    @GetMapping(path="/")
    public @responseBody String hello(){
        log.info("hello!!!");
        return "hello!!";
    }
}

 

10. Builder 사용

 

class에 @Builder 어노테이션을 선언하면 생성자 대신에 빌더를 사용할 수 있다.
또한 @Singular 어노테이션을 collection 타입에 선언하게 되면 파라미터를 하나씩 추가할 수 있다.

@Builder
public class User {

      private Long id;
      private String name;
      private String password;

      @Singular
      private List<String> habby;

    @Singular
     private Map<String, String> maps;

    /* Example
        User user = User.builder().id(0)
                                  .name("javaboja")
                                  .password("java123")
                                  .habby("football")
                                  .habby("Mountain climbing")
                                  .maps("abc","abc")
                                  .maps("def","def")
                                  .build();
                                  .
    */
}

 

사실상 업무 시 사용되는 어노테이션은 @Getter, @Setter, @Slf4j 등이 사용되며, 나머지 어노테이션은 가급적이면

사용하지 않는 것을 권장한다.(필자는 @Builder까지 사용할 생각이다.)

spring framework를 계속 사용하다 보니 회의감에 느껴 기존에 미니 프로젝트로 시작하려고 했던

spring boot, security, jpa, mysql(mariadb)를 사용한 웹 개발 프로젝트를 시작하려고 한다.

프로젝트를 시작하기에 앞서, 기본 셋팅을 해보자.

먼저 tool은 sts 4.2.0

java는 open jdk1.8이다.

이번 페이지에서는 security를 제외하고 jpa까지만 진행할 것이다.

자, sts를 실행시킨 후 spring starter project로 프로젝트를 생성해보자.

spring starter project 생성 화면-1

- name : 프로젝트명

- Type : Gradle -> 필자는 3.x버전 선택

- Packaging : 프로젝트 빌드 타입 -> 필자는 jar로 선택

- Java Version : 8

나머지는 선호하는 형식으로 넣어준 후 Next>

boot 버전 및 의존 설정

- Spring Boot Version : spring boot 버전 선택 -> 필자는 2.1.5로 선택하였다.

- Frequently Used : 최근 사용했던 dependencies를 표출해준다.

- Available : 사용할 dependencies 선택

              -> SQL에서 Spring Data JPA 선택

              -> Web에서 Spring Web Starter 선택

Finish를 선택하여 프로젝트 생성을 끝낸다.

다음으로 mariadb와 연결하기 위해 gradle 의존 설정 및 application.properties 설정을 해야한다.

먼저 gradle에 의존을 추가하기 위해 build.gradle을 열어보자.

<build.gradle>

build.gradle

pom.xml에 추가하는 것과 거의 동일하다.

dependencies에 아래의 내용을 추가해주자.

compile group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: '2.4.1'  
compile group: 'mysql', name: 'mysql-connector-java', version: '8.0.16'

maridb 관련 의존 추가

<application.properties 경로 : src/main/resources/>

application.properties에 datasource 및 jpa 기본 설정 추가

(mariadb 서버 만드는 방법은 추후에 필요하면 게시하도록 하겠다.)

기존 mybatis설정하는 것 처럼 url, driverClassName, username, password를 추가해주고

jpa.hibernate.ddl-auto=create를 추가해 준다.(jpa및 datasource관련 설정은 추후에 추가적으로 설명하겠다.)

기본 설정이 끝났다.

이제 class들을 만들어 보자.

먼저, 패키지와 클래스 구성은 다음과 같이 해보았다.

패키지 및 클래스구성

패키지를 구성하는 과정에서, 한가지 문제점이 있었는데 springboot시 자동 생성되는 application.java이

배치되어 있는 패키지(com.example.demo)를 base-package로 잡고, 나머지 패키지를 생성해야 MVC관련

어노테이션들이 인식이 되었다. @componentScan을 사용하여 base package를 변경하려고 시도했으나,

인식이 되지 않았다. (혹시 방법 알고 있으신 분들으 댓글 부탁 바랍니다.)

<Application.java (BootSecurityJpa1Application.java) 소스코드>

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class BootSecurityJpa1Application {

    public static void main(String[] args) {
        SpringApplication.run(BootSecurityJpa1Application.class, args);
    }

}

-> 기본으로 생성되는 클래스로 현 페이지에서는 아무런 작업을 하지 않았다.

<MainController.java 소스코드>

package com.example.demo.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import com.example.demo.service.UserService;
import com.example.demo.vo.User;

@Controller    // This means that this class is a Controller
public class MainController {

    @Autowired
    private UserService userService;

    @GetMapping(path="/add") // Map ONLY GET Requests
    public @ResponseBody String addNewUser (@RequestParam String name
            , @RequestParam String email) {
        // @ResponseBody means the returned String is the response, not a view name
        // @RequestParam means it is a parameter from the GET or POST request

        User n = new User();
        n.setName(name);
        n.setEmail(email);
        userService.userInsert(n);
        return "Saved";
    }

    @GetMapping(path="/all")
    public @ResponseBody Iterable<User> getAllUsers() {
        // This returns a JSON or XML with the users
        return userService.userSelect();
    }
}

-> MainController.java의 소스코드 일부는 spring boot 공식사이트에서 발췌한 부분으로
쿼리를 수행하는 userService 객체를 주입받기 위해 @Autowired를 사용했다.
또한 /add는 사용자를 추가하기위함이고, /all은 추가한 사용자를 확인하기 위한 url이다.

<UserRepository.java 소스코드>

package com.example.demo.repository;

import org.springframework.data.repository.CrudRepository;
import com.example.demo.vo.User;


// This will be AUTO IMPLEMENTED by Spring into a Bean called userRepository
// CRUD refers Create, Read, Update, Delete

public interface UserRepository extends CrudRepository<User, Integer> {

}

->하나의 테이블이라고 생각하면 이해가 쉬울 것 같다. CrudRepository 인터페이스를 상속받으며
제네릭 타입중 첫번째는 테이블의 컬럼이 매핑되어있는 클래스(User.java)를 넣고, 두번째는 User테이블의 기본키 타입을 넣는다.

<UserService.java>

package com.example.demo.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.example.demo.repository.UserRepository;
import com.example.demo.vo.User;

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    public void userInsert(User user) {
        userRepository.save(user);
    }

    public Iterable<User> userSelect() {
        return userRepository.findAll();
    }
}

-> UserService.java는 데이터를 컨트롤러에서 받아 받은 데이터로 쿼리를 수행하여 나온 결과를 다시
컨트롤러에 전달해주는 역할을 한다.
UserRepository 객체를 주입받으며, sava()는 Insert쿼리를 수행하고,
findAll()은 select쿼리를 수행한다.

<User.java 소스코드>

package com.example.demo.vo;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity // This tells Hibernate to make a table out of this class
public class User {
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private Integer id;

    private String name;

    private String email;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    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;
    }


}

-> User.java는 하나의 테이블을 의미하며 @Entity 어노테이션을 사용하여 테이블임을 명시하고,
테이블에 해당하는 컬럼들을 변수로 지정하여 Getter, Setter를 구현한다.
또한 @Id 어노테이션을 사용하여 기본키임을 id변수에 명시를 하며, @GeneratedValue(strategy=GenerationType.AUTO)
어노테이션을 사용하여 기본키값을 자동으로 증가하도록 설정하였다.'

 

자 이제, 제대로 동작하는지 확인해보자. 

로컬환경에서 실행하기 위해서는 Run As에서 Spring Boot App를 클릭하자.

(기존 spring 프로젝트 수행시 tomcat에 추가할 필요가 없다.)

 

<수행 시 화면>

Spring Boot App 수행 화면

위와 같이 콘솔에 해당 내용들이 출력되었다면, Spring Boot App가 정상적으로 수행된 것이다.

자 이제, 웹 브라우저 또는 HTTP를 호출할 수 있는 tool을 사용하여 사용자를 추가하여 보자.

http://127.0.0.1:8080/add?name=javaboja&email=javaboja@javaboja.com -> 호출!!

사용자 추가

Saved가 출력되었다면, 쿼리가 제대로 수행된 것이다.

이제, 추가된 사용자를 출력하여 보자.

http://127.0.0.1:8080/all -> 호출!!

사용자 조회

추가된 사용자가 제대로 출력되는 것을 확인할 수 있다.

Spring Boot, JPA, Mysql 기본 셋팅이 끝났다.

얼마전 특정 사이트의 was에서 oome가 발생하였다. 처음보는 에러에다가 특정 소스의 line이 명확하게 나오지 않아 바로 해결할 수 가 없었다.

맨 처음 발생했을 때 의 oome 메시지는

Exception in thread “main”: java.lang.OutOfMemoryError: Java heap space

구글링 해보니 heap영역의 메모리를 더이상 확보할 수 없다는 의미로 해석이 된다.

그렇다면 heap영역의 크기를 늘리면 되지 않을까? 라는 판단을 하여 heap영역의 크기를 늘리기로 하였다.

heap영역을 늘리기 위해선 tomcat에서 설정을 해야 하는데, $CATALINA_HOME의 bin으로 가면 setenv.sh라는 파일이 있다.(없으며 생성하자.)

<setenv.sh>

#!/bin/sh
export JAVA_OPTS="-Djava.library.path=/usr/local/lib -XX:PermSize=256m -XX:MaxPermSize=1024m -Xms1536m -Xmx1536m -Djava.awt.headless=true"
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$CATALINA_HOME/lib:/usr/local/lib"

Xms는 heap의 최소 메모리 즉, 처음 jvm이 실행되었을 때 할당되는 메모리 size이다.

Xmx는 heap이 최대로 사용할 수 있는 메모리 size이다.

32bit의 운영체제에서 jvm은 최대 1.2 ~ 1.5GB 까지만 메모리를 사용할 수 있었기 때문에 1536이라는 수치로 옵션을 설정하게 되었다.

Xms와 Xmx을 동일값으로 부여한 이유는 크기의 동적인 변경에 의한 오버 헤드를 최소화 하기 위함이다.(Sun HotSpt JVM에서 권장)

해당 설정한 이후 약 20일 이후에 다시 oome가 발생하였다.

소스상에 문제가 있는건 아닌가 의문이 들었고 세밀한 분석을 위해, jvm옵션을 더 추가하게 되었다.

#!/bin/sh
export JAVA_OPTS="-Djava.library.path=/usr/local/lib -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/cmuc/tomcat.hprof -XX:PermSize=256m -XX:MaxPermSize=1024m -Xms1536m -Xmx1536m -Djava.awt.headless=true"
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$CATALINA_HOME/lib:/usr/local/lib"

추가한 옵션은 -XX:+HeapDumpOnOutOfMemoryError와 -XX:HeapDumpPath=/home/cmuc/tomcat.hprof 

-XX:+HeapDumpOnOutOfMemoryError 옵션은 oome가 발생했을 때 heapdump를 생성하도록 하는 옵션으로

-XX:HeapDumpPath에서 설정한 경로에 heapdump파일을 생성한다.

 

그렇게 생성된 heapdump파일을 이클립스의 MAT로 분석할 수 있는데 소스상에 특별한 문제점은 찾을 수 없었다.

 

그렇다면 GC에 문제가 있는 것일까?라는 의문점을 갖게 되었고,

GC에 대한 정보를 찾다가 네이버 개발자들이 만든 https://d2.naver.com/helloworld/ 의 블로그에서 GC 튜닝관련 정보들을 얻을 수 있었다.

GC튜닝을 하기 전에 먼저 GC가 정상적으로 이루어지는지 확인하기 위해

jvm 명령어인 jstat -gcutil(가비지 컬럭센 통계 데이터의 개요)을 통해 GC 수행 횟수 및 GC 수행하는게 걸리는 시간을 분석할 수 있었다.

<jstat -gcutil의 컬럼 설명>

컬럼명 설명
S0 Young영역의 Survivor 영역 0의 사용률
S1 Young영역의 Survivor 영역 1의 사용률
E Young영역의 Eden영역의 사용률
O Old영역의 사용률
P Permanent 영역의 사용률
YGC Young 영역의 Minor GC 총 수행 횟수
YGCT Young 영역의 Minor GC 총 수행 시간
FGC Old 영역의 Full GC 총 수행 횟수
FGCT Old 영역의 Full GC 총 수행 시간
GCT 전체 GC 총 수행 시간

Minor GC의 평균 수행 시간이 (YGCT/YGC)로 0.5초 미만이면 양호한 상태이다.

Full GC의 평균 수행 시간이 (FGCT/FGC)로 1초 미만이면 양호한 상태이다.

Full GC의 평균 수행 시간이 길 경우 Stop the World 시간이 길어지게 되므로 GC튜닝이나 heap메모리 튜닝이 필요할 것이다. 현재 운용되는 프로세스에서는 모두 양호한 상태이기 때문에 따로 튜닝을 하지 않았다.

 

자.. 그러면 어째서 OOME가 발생하는 것일까..

-----------------------------------------------------------------------------------------------------------------------------------

두 번째 발생했을 때의 로그를 다시 확인해보니

Exception in thread “main”: java.lang.OutOfMemoryError: (Native method) 으로 나오는 것이 확인되었다.

해당 에러는 JVM에 설정된 것 보다 큰 native 메모리가 호출 될 때 발생된다.

 

일단 jvm 메모리 구조를 보자.(출처 http://www.nextree.co.kr/p3878/)

Native Heap Area OOME는 위에 설명처럼 Native Object들이 거주하는 공간에 더 이상 가용할 수 있는 메모리가 없기 때문에 발생하는 에러이다. 또한 이번 에러 로그에는 특정 소스의 몇 번째 라인에서 발생하는지 출력이 되었다.

해당 라인은 클라이언트에서 특정 url이 요청 되면 사진파일을 ServletOutputStream 객체에 flush()메소드를 호출하는 부분이었는데, 소스를 보니 flush()를 여러번 호출하도록 되어있었다.

여러 구글링을 해보니 OutputStream의 close()메소드가 마지막에 모두 flush를 한다고 명시되어있으며 flush()를 많이 사용할 경우 문제가 발생할 수 있다고 설명이 되어있었다.

flush()메소드를 모두 제거한 후 jmeter로 해당 url을 사이트 환경과 비슷하게 맞춘 후 테스트한 결과 oome는 발생하지 않았다.

추후 사이트 적용하여 모니터링 할 예정이다.

tomcat에 일부 로그들이 계속 쌓이게 되어 일정 주기를 설정하여 자동으로 삭제하도록 하는 프로세스를 개발하였다.

catalina.out 로그는 리눅스에서 logrotate.d에 등록하여 쉽게 관리할 수 있으나, localhost.*.log, localhost_access_log.*.txt, manager ... , 특정 was 로그 등 log4j나 logback에서 maxhistory로 최대 파일갯수를 설정하였는데 삭제가 안되는 이슈가 있었기 때문에 이러한 프로세스를 개발하게 되었다.

 

1. 패키지 구성

패키지는 main 메소드가 있는 LogRotate.java 와 FilenameFilter interface를 상속받는 MyFilenameFilter.java로 구성하였다.

 

먼저 MyFilenameFilter.java의 소스를 확인하자.

<MyFilenameFilter.java 소스>

package com.logrotate;

import java.io.File;
import java.io.FilenameFilter;

public class MyFilenameFilter implements FilenameFilter{

	private String file_name;
	
	public MyFilenameFilter(String file_name) {
		this.file_name=file_name;
	}
	
	@Override
	public boolean accept(File dir, String name) {
		// TODO Auto-generated method stub
		if(name.contains("ucex")) {
			return name.split("_")[0].equals(file_name);
		}else {
			return name.split("\\.")[0].equals(file_name) && !name.contains("out");
		}
	}

}

FilenameFilter interface를 상속받은 class에서 마지막 남은 로그의 갯수를 확인하기 위해 override한 accept 메소드를 위와 같이 구현하였다.

ucex는 특정 was 프로젝트의 이름으로 해당 프로젝트에서 ucex_2019-xx-xx_x.log 형태로 로그를 생성하여 split("_")을 통해 ucex가 포함된 로그가 filter되도록 정의하였다.

else문에서는 localhost.2019-xx-xx.log, localhost_access_log.2019-xx-xx.txt, 등 해당 로그들에 대한 filter를 정의해 주었으며, out이 포함된 로그는 제외하도록 하였다.(catalina.out로그는 리눅스의 logrotate.d에서 관리)

<LogRotate.java 소스>

package com.logrotate;

import java.io.File;
import java.io.FilenameFilter;
import java.util.Arrays;
import java.util.Date;

public class LogRotate{
	
	public String[] filenameFilter(File file) {

		String file_name="";
		if(file.getName().contains("ucex")) {
			file_name=file.getName().split("_")[0];
		}else {
			file_name=file.getName().split("\\.")[0];
		}
		
		return new File(file.getParent()).list(new MyFilenameFilter(file_name));
	}
	
	//first arg : 파일 경로, second arg : 삭제 기준일
	public static void main(String [] args) {
		LogRotate dt = new LogRotate();
		long todayMil = new Date().getTime();
		//1일을  milliseconds로 환산
		long oneDayMil = 24*60*60*1000;
		File path = null;
		int delete_standard_day = Integer.parseInt(args[args.length-1]);
		System.out.println("File Delete Standard Day : "+delete_standard_day);
		for(int j=0;j<args.length-1;j++) {
			String file_path = args[j];
			
			System.out.println("File Delete Dir Path : "+file_path);
			
			path = new File(file_path);
			//catalina.out 로그는 제외
			File[] file_list = path.listFiles(new FilenameFilter() {
				
				@Override
				public boolean accept(File dir, String name) {
					// TODO Auto-generated method stub
					return !name.contains("out");
				}
			});
			Arrays.sort(file_list);
			for(int i=0;i<file_list.length;i++) {
				//파일의 마지막 수정 시간 가져오기
				long fileDayMil = todayMil - file_list[i].lastModified();
				int fileDay = (int)(fileDayMil/oneDayMil);
				int len = dt.filenameFilter(file_list[i]).length;
				if(len>1 && fileDay>delete_standard_day) {
					file_list[i].delete();
					System.out.println("Delete to File Name : "+file_list[i].getName());
				}					
			}
		}	
		System.exit(0);
	}
}

LogRotate.java에서는 main()과 filenameFilter()를 정의하였다.

main()는 파일경로와 삭제 기준일을 arguments값으로 받으며, 마지막 arguments값은 무조건 삭제 기준일이여야 한다.

Date()를 이용하여 현재 시간값을 구하고 각 파일들의 시간값들과 비교하여 main()에 arguments값으로 넘어온 삭제 기준일과 비교하여 삭제하도록 구현하였다.

또한 filenameFilter()에서 해당 MyFilenameFilter생성자를 사용하여 각 파일에 대한 갯수를 가져올 수 있도록 하여 각 로그파일들이 1개씩 남아있을 경우 지우지 않도록 구현하였다.

파일 삭제가 종료될 시에 해당 프로세스를 자동으로 종료하도록 System.exit(0)를 넣어주었다.

(jar파일로 실행할 경우 종료해주지 않으면 프로세스가 계속 살아있어 추후에 크론이 주기적으로 실행시킬 경우 해당 프로세스가 계속 쌓이게됨)

 

<logrotate_start.sh 소스>

#!/bin/sh

logrotate_path=/home/cmuc/logrotation
tomcat_log_path=/home/cmuc/tomcat/apache-tomcat-7.0.47/logs
ucex_log_path=/home/cmuc/ucex/logs
logrotate_log_path=/home/cmuc/logrotation
delete_day=30

sleep 1

#LAST argumetns is delete_day
java -jar ${logrotate_path}/LogRotate.jar ${tomcat_log_path} ${ucex_log_path} ${delete_day} > ${logrotate_log_path}/logrotate.log

java소스들을 jar파일로 컴파일한 후 위의 쉘스크립트를 리눅스의 crontab에 등록하여 매일 새벽 3시에 동작하도록 하였다.

<crontab 등록 예시> 0 3 * * * cmuc /home/cmuc/logrotation/logrotate_start.sh

모든 경로는 절대 경로로 지정해야 crontab에서 정상적으로 동작한다. 위의 경로들은 프로젝트에 맞게 변경해주면 되겠다.

 

ps. 티스토리 패치 후에 소스코드 안에 <code></code>값이 계속 들어가 사라지지 않네요..

'java' 카테고리의 다른 글

OutOfMemory(OOME) 분석 및 java tomcat 튜닝  (0) 2019.04.05

+ Recent posts