급한 문제가 생겨 access.log에서 특정 IP를 제외하고 실시간 접속상황을 분석해야 할 경우가 있다고 가정하자.

아래처럼 명령어를 조합해서 나오는 결과값을 분석하려 할 것이고, 원하는 결과값이 잘 나올 것이다.

[root@localhost]# tail -f /var/log/apache/access.log | grep -v 'MY IP'

그러다가 갑자기 IP를 2개 제외해야 하는 상황이 벌어졌다고 하자.

급한대로 grep -v 를 하나 더 추가해 파이프로 연결한다. (다른 방법도 많지만 여기선 그게 문제가 아니므로 잠시 잊자...)

[root@localhost]# tail -f /var/log/apache/access.log | grep -v 'MY IP' | grep -v 'MY FRIEND IP'

근데 여기서부터 필터링을 거친 로그가 미적미적거리면서 나오거나, 접속이 빈번하지 않은 경우라면 아예 안나오는 등, 결과값이 좀 이상하게 나오기 시작할 것이다.

결론부터 말하자면 원인은 libc를 이용하는 stdio가 출력쪽이 tty가 아닌 경우 효율적인 I/O를 위해 버퍼링을 한다는 것이다. (버퍼 크기는 일반적으로 4096)

tail -f | grep -v | grep -v 로 이루어진 위의 예에서는 첫번째 grep -v 가 두번째 grep -v 에게 넘겨주기 전에 버퍼링을 한다.

이 문제를 해결하기 위해선 여러가지 방법이 있다.

온갖 기상천외한 방법이 많지만, 여기선 2가지만 적겠다.

우선 각 명령어마다 버퍼링을 하지 않도록 설정하는 옵션들이 있다.

tail : -f 가 붙으면 바로 flush

tcpdump : -l

grep : --line-buffered

awk : fflush() 함수사용

sed : -u, --unbuffered

위의 예에서 사용한 명령어를 제대로 돌아가게 하려면 아래처럼 해주면 된다.

[root@localhost\]# tail -f /var/log/apache/access.log | grep --line-buffered -v 'MY IP' | grep -v 'MY FRIEND IP'

하지만 명령어마다 옵션을 외우기가 귀찮다면 GNU coreutils에 포함되어 있는 stdbuf를 사용하면 해결된다.

표준입출력 관련해서 여러가지 옵션이 있지만 stdbuf -oL (line buffered = new line을 만나면 flush) 정도만 기억해둬도 된다.

위의 예에서 사용한 명령어를 stdbuf를 이용해 제대로 돌아가게 하려면 아래처럼 해주면 된다.

[root@localhost]# tail -f /var/log/apache/access.log | stdbuf -oL grep -v 'MY IP' | grep -v 'MY FRIEND IP'

아래는 stdio 버퍼링이 과연 그런지 테스트.

// 0.1초마다 1~3 중 하나의 숫자를 랜덤하게 출력 
[root@localhost]# while sleep 0.1; do echo $((1 + RANDOM % 3)); done 
1 
3 
2 
1 
1 
3 
1 
... ... 

// "2" 만 제외. 여기까진 잘됨 
[root@localhost]# while sleep 0.1; do echo $((1 + RANDOM % 3)); done | grep -v "2" 
3 
3 
3 
1 
3 
1 
1 
... ... 

// "1" 도 제외하려고 함. 갑자기 아무것도 안나옴. 
[root@localhost]# while sleep 0.1; do echo $((1 + RANDOM % 3)); done | grep -v "2" | grep -v "1" 

// 위에서 알아본 내용대로 첫번째 grep의 버퍼링 때문인지 확인. 잘나옴. 
[root@localhost]# while sleep 0.1; do echo $((1 + RANDOM % 3)); done | grep --line-buffered -v "2" | grep -v "1" 
3 
3 
3 
3 
... ... 

// stdbuf도 테스트. 잘나옴. 
[root@localhost]# while sleep 0.1; do echo $((1 + RANDOM % 3)); done | stdbuf -oL grep -v "2" | grep -v "1" 
3 
3 
3 
3 
... ...

.

Posted by bloodguy
,