개발을 하다보면 몇몇 설정 파일을 수정한 채로 개발을 하는 경우가 있습니다. 예를 들면 서버 주소를 개발용 서버로 변경해서 작업을 할 수 있겠죠. 만약 개발용 서버가 명확히 정해져 있다면, 디버그 모드와 출시 모드를 구분하는 식으로 고정된 설정 파일을 만들 수 있겠지만, 개발용 서버가 개발자 개인 컴퓨터인 경우는 이것도 쉽지 않습니다.

이런 용도로 변경된 파일들은 당연히 주 저장소에 올라가면 안 되겠죠. (하지만 이 파일 자체는 주 저장소에 포함되어야 하기에 .gitignore를 쓸 수는 없습니다) 저 같이 'git commit -a'을 실행하는데 익숙한 사람은 매번 커밋시 이 파일이 포함되지 않도록 신경써주는것이 꽤 큰 비용입니다.

다행히 git는 이를 위한 해결책이 있습니다. 다음 명령을 실행하면 해당 파일에 대한 변경을 무시합니다.

git update-index --assume-unchanged <file>

다시 변경된 내역을 표시하려면 다음 명령을 실행합니다.

git update-index --no-assume-unchanged <file>

변경이 무시되고 있는 파일 목록은 다음 명령으로 확인할 수 있습니다.

git ls-files -v | grep '^h'

참고

Nginx 로그 분석을 위해 시간별로 나누고 싶었습니다. 스크립트를 짜도 오래 걸리는 일은 아니겠지만, 쉘에서 하는 방법을 한번 찾아봤습니다.

Nginx 로그는 다음과 같은 형태를 가집니다.

xx.xx.xx.xx - - [25/Sep/2014:06:54:29 +0000] "GET /blahblah HTTP/1.1" 200 130 "-" "User-Agent"

다음 명령은 공백으로 나눠진 토큰 중 4번째, 즉 시간을 표시합니다.

awk '{ print $4 }' access.log
awk '{ print substr($4,2) }' access.log # 앞의 [ 제거

여기에 split 함수를 써서 날짜와 시간을 얻을 수 있습니다.

awk '{ split(substr($4,2),array,"[/:]"); print array[1], array[4] }' access.log

필요한 정보를 알았으니 각 줄을 적절한 파일에 쓰도록 하면 됩니다.

awk '{ split(substr($4,2),array,"[/:]"); print > (array[1] "-" array[4] ".log") }' access.log

갱신:

좀 더 큰 데이터에 해보니 too many open files 에러가 발생합니다. awk가 좀 오래된 도구다 보니 열린 파일 수 제한이 낮은 듯 합니다. (MacOSX 기준으로 채 20개를 넘지 못하네요.)

이 경우 파일을 계속 닫아주면 됩니다. 대신 '>>'를 써줘야 정상적으로 추출됩니다.

awk '{ split(substr($4,2),array,"[/:]"); fn = array[1] "-" array[4] ".log"; print >> fn; close(fn) }' access.log

참고

요약: logrotate를 통해 로그를 분할하기 위해서는, 서버 어플리케이션이 적당한 시그널(주로 SIGUSR1을 사용. Node.js에서는 디버그 모드 진입용으로 사용하고 있으니 SIGUSR2 사용)을 받았을때 로그 파일을 다시 열도록 하면 됩니다.

회사에서 서비스를 하는 중에 로그 파일이 너무 커져서, 적당한 크기에서 나눌 방법이 필요해졌습니다. 이런 경우 리눅스에서 주로 쓰는 방법은 logrotate입니다. 그리고 이를 서비스에 적용하기 위해 필요한 것을 찾아봤습니다.

logrotate 설정에 나눌 로그 파일을 추가하면 설정된 상황(매일 또는 매주, 무조건 또는 주어진 크기를 넘었을 때등)에서 로그 파일을 나눕니다. 이때 하는 동작은 기존 로그 파일의 이름을 변경하고, 같은 이름으로 새 로그 파일을 만드는 것입니다.

만약 잠깐 실행되고 마는 프로그램이면 이것으로 잘 동작합니다. 매번 실행할 때마다 주어진 이름의 로그 파일을 새로 열어서 쓰니까요. 하지만 프로그램이 계속 떠 있는 경우 새 로그 파일에 쓰지 않고 계속 기존 로그 파일에 데이터가 쌓입니다.

간단하게 이 동작을 확인할 수 있습니다. shell에서 node -e "setInterval(function () { console.log('hello'); }, 1000)" > log를 실행한 후 다른 창에서 log 파일의 이름을 변경하고 새로 생성해도 계속 기존 파일에 로그가 추가됩니다. 이것은 한번 파일을 열면 그 파일의 이름이 아닌 descriptor에 의해서 접근하기 때문입니다.

이 문제를 해결하기 위해 logrotate는 copytruncate라는 옵션을 제공합니다. 이 옵션을 사용하면 파일의 이름을 변경하는 대신 새 파일로 내용을 복사하고 기존 파일의 크기를 0으로 만들어버립니다. 하지만 이 경우 복사하는 동안 추가된 로그가 사라지는 문제가 있습니다.

다른 방법으로는 logrotate가 파일 이름을 변경한 후에 프로그램이 로그 파일을 새로 열도록 하는 것이 있습니다. 이미 떠 있는 프로그램에 명령을 내리기 위해서는 주로 시그널을 사용합니다. 예를 들어 nginx는 SIGUSR1을 받으면 로그 파일을 다시 열도록 되어 있습니다. (Stopping or Restarting Nginx) logrotate에서는 postrotate 옵션으로 이를 표현합니다. 다음은 Ubuntu에 적용된 nginx를 위한 logrotate 설정입니다.

postrotate
  [ ! -f /run/nginx.pid ] || kill -USR1 `cat /run/nginx.pid`
endscript

다음은 이 방법이 적용된 서버 코드입니다. Node.js를 사용하고 CoffeeScript로 작성되어 있습니다.

logstream = undefined
openLogFile = ->
  logstream.close() if logstream
  logstream = fs.createWriteStream "log", flags: 'a+', mode: '0644', encoding: 'utf8'
openLogFile()

# 시그널을 받으면 로그 파일을 다시 연다
process.on 'SIGUSR2', ->
  openLogFile()

SIGUSR1은 Node.js가 디버그 모드 진입용으로 사용하고 있습니다. 그래서 대신 SIGUSR2를 사용했습니다.