편집자 - 이 블로그는 NGINX 및 NGINX Plus를 사용한 로깅을 논의하는 여러 블로그 중 하나입니다. 다음도 참조하세요.
또한 NGINX JavaScript 모듈의 사용 사례에 대한 많은 블로그 중 하나이기도 합니다. 전체 목록은 NGINX JavaScript 모듈의 사용 사례를 참조하세요.
NGINX는 모든 규모의 조직이 미션 크리티컬 웹사이트, 애플리케이션, API를 실행하는 데 도움을 줍니다. 규모와 배포 인프라 선택에 관계없이 프로덕션에서 실행하는 것은 쉽지 않습니다. 이 글에서는 프로덕션 배포의 어려운 점 중 하나인 로깅에 대해 이야기해보겠습니다. 더 구체적으로, 불필요한 데이터에 압도당하지 않으면서도 문제 해결을 위해 적절한 양의 자세한 로그를 수집하는 균형 잡힌 방법에 대해 논의합니다.
NGINX는 클라이언트 요청에 대한 액세스 로깅 과 문제가 발생할 경우를 위한 오류 로깅이라는 두 가지 로깅 메커니즘을 제공합니다. 이러한 메커니즘은 HTTP와 스트림(TCP/UDP) 모듈에서 모두 사용할 수 있지만, 여기에서는 HTTP 트래픽에 초점을 맞춥니다. ( 디버그 심각도 수준을 사용하는 세 번째 로깅 메커니즘도 있지만, 여기서는 다루지 않겠습니다.)
일반적인 기본 NGINX 로깅 구성은 다음과 같습니다.
http {
log_format main
'$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main; # 'main' 형식을 사용하여 기록
error_log /var/log/nginx/error.log warn; # 'warn' 심각도 수준까지 기록
...
}
log_format
지시어는 access_log
지시어가 구성에 포함될 때 생성되는 로그 항목의 내용과 구조를 설명합니다. 위의 예는 많은 웹 서버에서 사용하는 CLF( Common Log Format )의 확장입니다. error_log
지시어를 사용하면 기록할 메시지의 심각도 수준을 지정하지만, 고정된 항목의 내용이나 형식은 지정하지 않습니다. 자세한 내용은 다음 섹션에서 설명하겠습니다.
NGINX 로깅의 다른 주목할 만한 측면은 다음과 같습니다.
http
컨텍스트의 access_log
지시문은 모든 server{}
블록에 적용됩니다.access_log
지시어를 사용하여 표준 CLF 로그 파일과 더 자세한 두 번째 로그를 모두 생성할 수 있습니다.일반적으로 액세스 로그를 사용하여 분석 및 사용 통계를 제공하고, 오류 로그를 사용하여 장애 감지 및 문제 해결을 시도합니다. 하지만 생산 시스템을 운영하는 것은 그렇게 간단한 일이 아닙니다. 다음은 몇 가지 일반적인 문제입니다.
정보
심각도 수준에서 세부 정보를 잘 공개하지만 일반적인 작업에는 너무 자세합니다.게다가, 프로덕션 환경에서 로깅 세부 정보를 추가하거나 제거하기 위해 NGINX 구성을 변경하려면 변경 제어 프로세스를 거쳐 구성을 다시 배포해야 할 수도 있습니다. 완전히 안전하지만 "왜 급증이 나타나는가"와 같은 라이브 문제를 해결할 때는 다소 번거롭습니다. 4엑스
/ 5xx
오류?”. 물론, 클러스터 전반에서 동일한 트래픽을 처리하는 NGINX 인스턴스가 여러 개 있는 경우 이러한 문제는 더욱 커집니다.
각 요청에 대해 수집된 데이터를 풍부하게 하기 위해 액세스 로그의 형식을 사용자 지정하는 것은 향상된 분석을 위한 일반적인 접근 방식이지만 진단이나 문제 해결에는 확장성이 좋지 않습니다. 기본 액세스 로그에 두 가지 작업을 요청하는 것은 인위적인 해결책입니다. 왜냐하면 일반적인 분석보다 문제 해결을 위해 필요한 정보가 훨씬 더 많기 때문입니다. 주요 액세스 로그에 수많은 변수를 추가하면 가끔만 유용한 데이터로 인해 로그 볼륨이 크게 늘어날 수 있습니다.
대신 두 번째 액세스 로그를 사용하여 디버깅이 필요한 오류가 발생할 때만 로그에 기록할 수 있습니다. access_log
지시문은 if
매개변수를 사용하여 조건부 로깅을 지원합니다. 즉, 지정된 변수가 0이 아니고 비어 있지 않은 값으로 평가되는 경우에만 요청이 로깅됩니다.
map $status $is_error { 400 1; # 잘못된 요청, 만료된 클라이언트 인증서 포함 495 1; # 클라이언트 인증서 오류 502 1; # 잘못된 게이트웨이(업스트림 서버를 선택할 수 없음) 504 1; # 게이트웨이 시간 초과(선택한 업스트림에 연결할 수 없음) default 0; } access_log /var/log/nginx/access_debug.log access_debug if=$is_error; # 진단 로깅 access_log /var/log/nginx/access.log main;
이 구성을 사용하면 $status
변수를 맵
블록을 통해 전달하여 $is_error
변수의 값을 확인한 다음, 이 값을 access_log
지시문의 if
매개변수로 평가합니다. $is_error가
다음과 같이 평가되는 경우1
access_debug.log 파일에 특별한 로그 항목을 기록합니다.
그러나 이 구성은 궁극적으로 해결되어 상태가 되는 요청 처리 중에 발생한 오류를 감지하지 못합니다.200
좋아요
. 이러한 예 중 하나는 NGINX가 여러 개의 업스트림 서버 간에 로드 밸런싱을 수행하는 경우입니다. 선택된 서버에서 오류가 발생하면 NGINX는 proxy_next_upstream
지시문에서 구성된 조건에 따라 요청을 다음 서버로 전달합니다. 업스트림 서버 중 하나가 성공적으로 응답하는 한 클라이언트는 상태가 기록되는 성공적인 응답을 받습니다.200
. 그러나 재시도로 인해 사용자 경험이 좋지 않을 수 있으며, 업스트림 서버가 정상적이지 않다는 사실을 바로 알아채지 못할 수도 있습니다. 결국 우리는 로그인했습니다200
.
NGINX가 여러 개의 업스트림 서버로 프록시를 시도하는 경우, 해당 주소는 모두 $upstream_addr
변수에 캡처됩니다. 다른 $upstream_*
변수에도 동일한 것이 적용되는데, 예를 들어 $upstream_status
는 시도한 각 서버의 응답 코드를 캡처합니다. 따라서 이러한 변수에 여러 항목이 있으면 뭔가 나쁜 일이 일어났다는 것을 알 수 있습니다. 즉, 적어도 업스트림 서버 중 하나에 문제가 있을 가능성이 큽니다.
요청이 여러 개의 상류 서버로 프록시된 경우에도 access_debug.log 에 기록하는 건 어떨까요?
map $upstream_status $multi_upstreams { "~," 1; # 쉼표가 있음 default 0; } map $status $is_error { 400 1; # 잘못된 요청, 만료된 클라이언트 인증서 포함 495 1; # 클라이언트 인증서 오류 502 1; # 잘못된 게이트웨이(업스트림 서버를 선택할 수 없음) 504 1; # 게이트웨이 시간 초과(선택한 업스트림에 연결할 수 없음) default $multi_upstreams; # 두 개 이상의 업스트림 서버를 시도한 경우 } access_log /var/log/nginx/access_debug.log access_debug if=$is_error; # 진단 로깅 access_log /var/log/nginx/access.log main; # 일반 로깅
여기서는 또 다른 맵
블록을 사용하여 $multi_upstreams 를
생성합니다. 이 변수의 값은 $upstream_status
에 쉼표( ,
)가 있는지 여부에 따라 달라집니다. 쉼표는 상태 코드가 두 개 이상 있고, 따라서 두 개 이상의 업스트림 서버가 발견되었음을 의미합니다. 이 새로운 변수는 $status가
나열된 오류 코드 중 하나가 아닐 때 $is_error
의 값을 결정합니다.
지금은 일반 액세스 로그와 오류가 있는 요청을 담은 특수 access_debug.log 파일이 있지만, 아직 access_debug
로그 형식은 정의하지 않았습니다. 이제 문제를 진단하는 데 필요한 모든 데이터가 access_debug.log 파일에 있는지 확인해 보겠습니다.
진단 데이터를 access_debug.log 에 입력하는 것은 어렵지 않습니다. NGINX는 HTTP 처리와 관련된 100개 이상의 변수를 제공하며, 필요한 만큼의 변수를 캡처하는 특수한 log_format
지시문을 정의할 수 있습니다. 하지만 이러한 목적에 맞춰 단순한 로그 형식을 구축하는 데는 몇 가지 단점이 있습니다.
NGINX JavaScript 모듈<.htmla> (njs)을 사용하여 JSON과 같은 구조화된 형식으로 로그 항목을 작성하면 이러한 과제를 해결할 수 있습니다. JSON 형식은 다음과 같은 로그 처리 시스템에서도 널리 지원됩니다. 스플렁크, 로그스태시, 그레이로그, 그리고 로글리. log_format
구문을 JavaScript 함수로 오프로드하면 기본 JSON 구문의 이점을 누릴 수 있으며, 모든 NGINX 변수와 njs ' r
' 객체 의 추가 데이터에 접근할 수 있습니다.
js_import conf.d/json_log.js;js_set $json_debug_log json_log.debugLog;
log_format access_debug escape=none $json_debug_log; # njs로 오프로드
access_log /var/log/nginx/access_debug.log access_debug if=$is_error;
js_import
지시어는 JavaScript 코드가 포함된 파일을 지정하고 이를 모듈로 가져옵니다. 코드 자체는 여기에서 찾을 수 있습니다. access_debug
로그 형식을 사용하여 액세스 로그 항목을 작성할 때마다 $json_debug_log
변수가 평가됩니다. 이 변수는 js_set
지시문에서 정의한 debugLog
JavaScript 함수를 실행하여 평가됩니다.
JavaScript 코드와 NGINX 구성을 결합하면 다음과 같은 진단 로그가 생성됩니다.
$ tail --lines=1 /var/log/nginx/access_debug.log | jq { "타임스탬프": "2020-09-21T11:25:55+00:00", "연결": { "요청_수": 1, "경과 시간": 0.555, "파이프라인": false, "ssl": { "프로토콜": "TLSv1.2", "암호": "ECDHE-RSA-AES256-GCM-SHA384", "세션_ID": "b302f76a70dfec92f6bd72de5732692481ebecbbc69a4d81c900ae4dc928485c", "세션_재사용": 거짓, "클라이언트_인증서": { "상태": "없음" } } }, "요청": { "클라이언트": "127.0.0.1", "포트": 443, "호스트": "foo.example.com", "메서드": "GET", "uri": "/one", "http_version": 1.1, "수신된 바이트": 87, "헤더": { "호스트": "foo.example.com:443", "사용자 에이전트": "curl/7.64.1", "수락": "*/*" } }, "업스트림": [ { "서버 주소": "10.37.0.71", "서버 포트": 443, "connect_time": null, "header_time": null, "response_time": 0.551, "전송된 바이트": 0, "수신된 바이트": 0, "상태": 504 }, { "서버 주소": "10.37.0.72", "서버 포트": 443, "연결_시간": 0.004, "헤더_타임": 0.004, "응답 시간": 0.004, "전송된 바이트": 92, "수신된 바이트": 4161, "상태": 200 } ], "응답": { "상태": 200, "전송된 바이트": 186, "헤더": { "콘텐츠 유형": "텍스트/html", "콘텐츠 길이": "4161" } } }
JSON 형식을 사용하면 전반적인 HTTP 연결(SSL/TLS 포함), 요청, 업스트림, 응답과 관련된 정보에 대해 별도의 객체를 가질 수 있습니다. 첫 번째 업스트림(10.37.0.71)이 상태를 반환한 방식에 주목하세요. 504
(게이트웨이
타임아웃)
NGINX가 성공적으로 응답한 다음 업스트림(10.37.0.72)을 시도하기 전에. 0.5초 시간 초과( 업스트림
객체의 첫 번째 요소에서 response_time
으로 보고됨)는 이 성공적인 응답에 대한 전체 지연 시간( 연결
객체에서 elapsed_time
으로 보고됨)의 대부분을 차지합니다.
다음은 만료된 클라이언트 인증서로 인해 발생한 클라이언트 오류에 대한 (잘린) 로그 항목의 또 다른 예입니다.
{
"타임스탬프": "2020-09-22T10:20:50+00:00",
"연결": {
"ssl": {
"프로토콜": "TLSv1.2",
"암호": "ECDHE-RSA-AES256-GCM-SHA384",
"세션_ID": "30711efbe047c38a98c2209cc4b5f196988dcf2d7f1f2c269fde7269c370432e",
"세션_재사용": 거짓,
"클라이언트_인증서": {
"상태": "실패: 인증서가 만료되었습니다",
"serial": "1006",
"지문": "0c47cc4bd0fefbc2ac6363345cfbbf295594fe8d",
"제목": "이메일 주소=liam@nginx.com,CN=test01,OU=데모 CA,O=nginx,ST=CA,C=US",
"발급자": "CN=데모 중간 CA, OU=데모 CA, O=nginx, ST=CA, C=US",
"시작": "9월 20일 12:00:11 2019 GMT",
"만료": "9월 20일 12:00:11 2020 GMT",
"만료됨": true,
...
"응답": {
"상태": 400,
"바이트_전송": 283,
"헤더": {
"콘텐츠 유형": "텍스트/html",
"콘텐츠 길이": "215"
}
}
오류가 발생할 때만 풍부한 진단 데이터를 생성함으로써 재구성을 수행하지 않고도 실시간으로 문제를 해결할 수 있습니다. 마지막 바이트가 클라이언트로 전송된 후 로깅 단계에서 오류가 감지될 때만 JavaScript 코드가 실행되므로 요청이 성공하더라도 영향을 받지 않습니다.
전체 구성은 GitHub 에서 제공됩니다. 여러분 환경에서 시도해보시는 건 어떨까요? 아직 NGINX Plus를 사용하고 있지 않다면 오늘 무료 30일 체험판을 시작하거나 당사에 문의하여 사용 사례에 대해 논의해 보세요 .
"이 블로그 게시물에는 더 이상 사용할 수 없거나 더 이상 지원되지 않는 제품이 참조될 수 있습니다. 사용 가능한 F5 NGINX 제품과 솔루션에 대한 최신 정보를 보려면 NGINX 제품군을 살펴보세요. NGINX는 이제 F5의 일부가 되었습니다. 이전의 모든 NGINX.com 링크는 F5.com의 유사한 NGINX 콘텐츠로 리디렉션됩니다."