본문 바로가기

Programing/Node.js

일주일이 지난 로그파일 정리 하기

로그 파일

서버를 운영하다보면 로그파일이 지속적으로 쌓입니다.

트래픽이 많은 로그 파일이라면 로그파일 역시 용량이 클 것이고 Disk 자원에 부담이 될 것입니다.

로그 파일을 정리하는 수행하는 간단한 애플리케이션을 만들어보겠습니다.
만약 cron 등 스케쥴링을 할 수 있는 시스템을 같이 이용하면 배치처리에 범주로 볼 수 있을 것 같습니다. (우겨봅....세)

첫 번째 코드

스프링 배치 같은 것을 사용할 수 있겠지만 단순히 하기 위해 node.js 를 사용합니다.

JavaScript의 date 라이브러리는 Java Date 처럼 악명이 높기 때문에 moment.js 라는 라이브러리를 사용하겠습니다.

앞으로 나오는 코드를 실행하려면 먼저 아래 명령으로 moment.js 를 추가가 필요합니다.

npm install moment

파일: gcA.js

const fs = require('fs');
const now = require('moment');

function cleanUpLogFile(targetDate) {
    const filename = `logs/${targetDate}.log`;
    fs.unlink(filename, (err) => {
        if (!err) {
            console.log(`${filename}: Deleted`);
            process.exit(0);
        }
        if (err && err.errno === -2) {
            console.error(`${filename}: No such file or directory`);
        } else {
            console.error(err.message);
        }
        process.exit(err.errno);
      });
}

const targetDate = now().subtract(7, 'day').format('YYYY-MM-DD');
cleanUpLogFile(targetDate);

아래와 같이 로그파일이 쌓여있다고 생각하고 해당 스크립트를 실행합니다.

글을 작성하는 시점은 10월 3일이고, 일주일 전은 9월 26일이다.

node gcA.js
logs/2019-09-26.log: Deleted
2019-09-26.log 가 지워졌다.

 

10분후 다시 실행하면 어떻게 될까요?

$ node gcA.js
logs/2019-09-26.log: No such file or directory

이미 파일이 지워졌으므로 "No such file or directory"라는 메시지가 나옵니다.

fs.unlink 에서는 파일이 없을 경우 errno 로 -2를 돌려준다. 위의 코드에 보면 해당 상황일 때 메시지를 찍게 되어 있습니다.

서버의 로그파일은 변함이 없습니다.

아마 컴퓨터의 날짜가 다음 날로 바뀌기 전까지는 node gcA.js 라는 동작은 변함이 없을 것입니다.

혹시 이런 가정이 벗어난다면(현실 세계에서는 가능한 일)

- 예를 들면, 컴퓨터의 시간을 동기화하는 타임서버가 잘못되었거나

- 어떤 누군가가 컴퓨터 시간을 임의로 바꾸면

현실 세상의 시간을 넘어가기 전이라도 다른 파일이 지워질 수는 있겠습니다.

time.asia.apple.com 이라는 서버를 이용해서 컴퓨터의 시간을 동기화 하고 있다.

멱등 or NOT 멱등?

얼마전 어떤 블로그에서 이런 유사한 사례가 멱등성(Idempotent) 이 깨진 경우라며 멱등성을 해결하겠다고 올라온 글을 읽었습니다.

과연 사실일까? 위의 링크의 블로그를 참고하여 해결책을 흉내내어 보겠습니다.

날짜를 파라미터로 받는(입력) 방법으로 애플리케이션의 변경을 하겠습니다.

두 번째 코드

파일: gcB.js

const fs = require('fs');

function cleanUpLogFile(targetDate) {
    // 동일
}

var myArgs = process.argv.slice(2);
if (myArgs.length < 1) {
    const path = require("path");
    const program = path.basename(process.argv[0])
    const sourcename = path.basename(process.argv[1])
    console.error(`error: need target date argument.\n eg. "${program} ${sourcename} 2019-10-03"`);
    process.exit(1);
}
cleanUpLogFile(myArgs[0]);

일단 코드가 21라인에서 27라인으로 6라인 늘어났습니다.

대신, moment.js에 대한 의존성이 없어졌습니다. 더 이상 애플리케이션 내부에서 일주일 전 날짜를 구할 필요가 없어졌기 때문입니다.

인자를 처리하는 부분이 추가 되었지만 cleanUpLogFile 부분은 동일해서 위의 코드에서는 주석으로 생략했습니다.
(마지막에 소스코드 다운로드 있습니다. 전체가 궁금하면 다운로드 해서 보세요.)

 

아까 없어진 로그파일을 휴지통에서 찾아서 복원하고... (쓰르륵)

프로그램에 날짜 인자(2019-09-26)를 추가해서 수행하면 동일하게 2019-09-26.log 파일이 삭제가 됩니다.

node gcB.js 2019-09-26
logs/2019-09-26.log: Deleted

다시 수행해보면 첫 번째 코드를 수행했을 때와 마찬가지로 파일이 없다는 메시지가 나옵니다.

$ node gcB.js 2019-09-26
logs/2019-09-26.log: No such file or directory

한가지 다른점이 있다면, 이 명령으로는 내일이 지나도 모레가 지나도 파일은 더 이상 지워지지 않을 것입니다.

왜냐하면 이미 파일이 지워져버렸기 때문이죠.

 

결국 이 상태는 멱등성 보존(?)이라는 미션으로 원래 프로그램이 수행하는 동작 자체를 바꾸었습니다. (나의 프로그램은 원래 그렇지 않아...)

본래의 프로그램이라면 '내일'이 되면 '내일 기준의 일주일 전 파일'을 지웠을 것이기 때문이다.

두 번째 코드의 실행 변경

다행히도 유닉스는 이를 쉽게 해결할 수 있는 방법이 있습니다.

아래에서 2번 모든 프로그램의 출력은 다른 프로그램의 입력으로 쓸 수 있다에 주목해봅시다.

<아래는 유닉스 철학>

더보기

아래는 1978년에 기술된 유닉스 철학입니다.

1. 각 프로그램이 한 가지 일만 하도록 작성하라. 새 작업을 하려면 기존 프로그램을 고쳐서 새로운 "기능"을 추가해 프로그램을 복잡하게 만들기보다는 새로운 프로그램을 작성하라.
2. 모든 프로그램의 출력은 아직 알려지지 않은 다른 프로그램의 입력으로 쓰일 수 있다고 생각하라.
3. 소프트웨어를 빠르게 써볼 수 있게 설계하고 구축하라. 심지어 운영체제도 마찬가지다. 수 주 안에 끝내는 것이 이상적이다. 거슬리는 부분은 과감히 버리고 새로 구축하라.
4. 프로그래밍 작업을 줄이려면 미숙한 도움보단 도구를 사용하라. 도구를 빌드하기 위해 한참 둘러가야 하고 게다가 사용 후 바로 버린다고 할지라도 도구를 써라.

날짜를 구하는 프로그램: date

유닉스에는 date라는 명령을 가지고 있습니다. 윈도우에도 있지만 훨씬 많은 기능을 합니다. (요즘은 달라졌으려나??)

일주일 전 날짜를 yyyy-mm-dd 형태로 가져오는 것은 아래의 명령으로 할 수 있습니다. (리눅스는 명령의 형태가 조금 다를 수 있다.)

date -v-7d +"%Y-%m-%d"
2019-09-26

자 이제 결합시켜보겠습니다. 유닉스에는 파이프를 이용해서 결합을 할 수 있지만 이 경우 입력을 stdin 으로 받는 것이 아니고 프로그램의 인자로 받기에 다른 형태로 사용해야 합니다.

날짜를 애플리케이션에 주입하기

node gcB.js $(date -v-7d +"%Y-%m-%d")

이미 파일이 지워졌으니 없다고 나옵니다. (위에서 경험한 적 있죠)

아마 내일이 되면 09-27.2019-09-27.log 파일이 지워 질 것입니다.

 

뭐.라.고.요?! 내일은 다른 파일이 지워진다고요??

자,, 다시 원점으로 돌아왔습니다.

의의는?

버틀러 램슨(Butler Lampson)은 다음과 같은 말을 남겼습니다.

전산학의 모든 문제는 다른 차원의 간접성(indirection)으로 해결할 있다.
All problems in computer science can be solved by another level of indirection.

결국 첫 번째 코드에서 두번째 코드로의 변화는 시간을 구하는 문제를 다른 차원(date 라는 명령어)으로 옮기는 인다이렉션이라고 할 수 있다. 좀 더 고상하게 표현하자면 'Temporal Decoupling(시간의 의존성 제거)' 이라고 할 수도 있겠습니다.

하지만 시간의 의존성을 제거하더라도 어디에서는 시간을 획득해서 애플리케이션에 제공을 해야 애플리케이션의 기능이 올바르게 될 것입니다.

의의1. 유닉스 철학 1번

moment.js 라이브러리에 대한 의존성이 없어졌습니다.
어떻게 보면 유닉스 철학 1번 해당 프로그램은 한가지의 일만 하고 있는 것이 아니었다.

1) 시간을 구하고, 2) 일주일 전으로 계산을 하고, 3) 파일 이름을 만들어, 4) 삭제를 하는 무려 4가지 일을 하고 있었다.

이것은 객체지향의 원칙 SOLID 중 즉 단일책임의 원칙(Single Responsibility Principle)과도 같은 맥락을 하고 있습니다.

응집성이 생기도록

"시간"을 구해서 일주일 전으로 계산을 하는 것은 date에 위임을 하고,
파일이름을 만들어 "삭제"를 하는 것은 애플리케이션을 하는 것으로 변경이 되었고 볼 수 있습니다.

어떻게 보면 리팩터링(예. extract method)을 했다고 볼 수도 있겠을까나요?

의의2. 유닉스 철학 2번 (입력)

기존의 애플리케이션은 외부로 부터의 입력이 없었습니다.
따라서 혹시라도 프로그램 수행이 안되어 로그 파일이 지워지지 않았다면 프로그램에 파라미터를 입력하여, 임의로 수행하는 것이 가능해졌습니다.

이 철학은 테스트가능성(Testability), 의존성 주입(DI)이라는 개념과도 연결되어 있는 것 같습니다.

 

30년이 지난 개념이 아직까지 유효한 것을 보면 컴퓨터의 세계는 계속 돌고도는 윤회(輪廻)인 것 같습니다.

- 유행(fashion, trend, vogue)

파일 첨부

참고로, server.js 는 node.js로 돌아가는 서버가 아니고 테스트를 위해 log 파일을 만드는 프로그램입니다.

server.js
0.00MB
gcA.js
0.00MB
gcB.js
0.00MB