Do not log
원본 URL: https://sobolevn.me/2020/03/do-not-log
sobolevn's personal blog
거의 매주 나는 우연히 이 logging 논쟁에 들어갔습니다. 문제가 있습니다. 사람들은 서로 다른 것을 기록하고 이를 모범 사례라고 부르는 경향이 있습니다. 왜 그런지 잘 모르겠습니다. 다른 사람들과 이것을 논의하기 시작하면 항상 같은 아이디어를 반복해서 반복합니다.
그래서. 오늘 저는 전체 logging 문화를 비판하고 여러 대안을 제공하고 싶습니다.
가장 중요한 것으로 시작합시다. 로깅은 의미가 없습니다!
인터넷에서 찾을 수있는 인기있는 코드 샘플을 검토해 보겠습니다.
try:
do_something_complex(*args, **kwargs)
except MyException as ex:
logger.log(ex)
그래서 여기서 무슨 일이 일어나고 있습니까? 복잡한 계산이 실패하고 기록합니다. 좋은 일인 것 같지 않습니까? 이 상황에서는 대개 몇 가지 중요한 질문을 합니다.
첫 번째 질문 :
우리는 나쁜 상태에 do_something_complex() 도달 할 수 없게 만들 수 있습니까?
그렇다면 코드를 타입 안전하도록 리팩터링하십시오. 그리고 여기서 mypy 을 가지고 발생할 수있는 모든 예외를 제거하십시오. 때때로 이것은 도움이 됩니다. 이 경우 예외 처리 및 로깅이 전혀 없습니다.
두 번째 질문 :
do_something_complex() 가 실패한 것이 중요 합니까? 이런 케이스는 우리가 신경 쓰지 않는 경우가 많이 있습니다. 재시도, 대기열 또는 생략 할 수있는 선택적 단계 일 수 있습니다. 이 실패가 중요하지 않은 경우 잊어 버리는 것이 좋습니다. 그러나 이 실패가 중요한 경우-나는 왜 그리고 언제 실패했는지 정확히 모든 것을 알고 싶습니다. 전체 스택 추적, 로컬 변수 값, 실행 컨텍스트, 총 실패 수 및 영향을 받는 사용자 수를 알고 싶습니다. 또한 이 중요한 실패에 대해 즉시 통보 받기를 원합니다. 한 번의 클릭으로이 실패로 버그 티켓을 만들 수 있습니다.
그리고 맞습니다, 당신은 그것을 올바르게 얻었습니다 : 그것은 로깅이 아닌 Sentry의 기능처럼 들립니다.
나는 모든 내부의 중대한 오류에 대한 빠른 알림을 원하거나 아무것도 원하지 않습니다 : 커피와 youtube 비디오가 있는 평화로운 아침만 원합니다. 로깅 사이에는 아무것도 없습니다.
세 번째 질문은 앱을 작동시키기 위해 비즈니스 모니터링을 적용 할 수 있습니까? 우리는 실제로 예외와 처리 방법에 관심이 없습니다. 우리는 앱과 함께 제공하는 비즈니스 가치에 관심을 갖습니다. 때로는 앱이 센트리가 발생하도록 예외를 제기하지 않습니다. 다른 방법으로 고장날 수 있습니다. 예를 들어, 양식 유효성 검사는 정상적으로 수행되지 않아야 할 때 오류를 반환 할 수 있습니다. 그리고 당신은 예외는 없지만 기능 장애 응용 프로그램이 있습니다. 비즈니스 모니터링이 빛나는 곳입니다!
다양한 비즈니스 측정 항목을 추적하고 새로운 주문, 새로운 의견, 새로운 사용자 등이 있는지 확인할 수 있습니다. 그렇지 않은 경우 긴급 알림을 원합니다. 화난 고객이 전화하거나 문자를 보낸 후 로깅 정보를 읽는 데 추가 비용을 낭비하고 싶지 않습니다. 사용자를 모니터링 서비스로 취급하지 마십시오!
마지막 질문은 :
일반적으로 do_something_complex() 으로 실패 할 것으로 예상 됩니까? HTTP 호출 또는 데이터베이스 액세스와 같습니다. 그렇다면 cexceptions https://sobolevn.me/2019/02/python-exceptions-considered-an-antipattern 을 사용하지 말고 result monal https://github.com/dry-python/returns 을 사용해야 합니다. 이렇게 하면 무언가 실패 할 것임을 명확하게 나타낼 수 있습니다. 자신감있게 하시면 됩니다. 그리고 아무것도 기록하지 마십시오. 그냥 실패하도록 놓아두어야 합니다.
monad-related 주제 중 하나는 logger 호출이 있는 함수의 순수성입니다. 두 가지 유사한 기능을 비교해 봅시다.
def compute(arg: int) -> float:
return (arg / PI) * MAGIC_NUMBER
def compute(arg: int):
result = (arg / PI) * MAGIC_NUMBER
logger.debug('Computation is:', result)
return result
이 두 함수의 주요 차이점은 첫 번째 함수는 완전한 순수 함수이고 두 번째 함수는 불완전한 함수 IO 입니다.
어떤 결과가 있습니까?
IOResult[float]로깅이 불완전하고 실패 할 수 있기 때문에 반환 유형을 변경해야합니다 (예 : 로거가 실패하고 앱을 중단시킬 수 있음)
이 부작용을 테스트해야합니다. 그리고 우리는 그렇게해야한다는 것을 기억해야합니다! 이 부작용이 반환 유형에 명시되지 않는 한 말입니다.
현명한 프로그래머는 특수 Writer Monad http://learnhaskellforgood.narod.ru/learnyouahaskell.com/for-a-few-monads-more.html 을 사용하여 로깅을 순수하고 명시 적으로 만듭니다. 그리고이 기능을 중심으로 전체 아키텍처를 크게 변경해야합니다.
또한 올바른 logger 인스턴스를 전달하고 싶을 수도 있는데, 아마도 모나드를 기반으로 의존성 주입RequiresContext https://sobolevn.me/2020/02/typed-functional-dependency-injection 을 사용해야 함을 의미합니다.
이 모든 추상화로 말하고 싶은 것은 적절한 로깅 아키텍처가 어렵다는 것입니다.
여기저기서 logger.log() 을 쓰는 것만이 아닙니다. 적절한 추상화를 작성하고, 작성하고, 순수하고 불순한 코드의 엄격한 계층을 유지하는 복잡한 프로세스입니다.
당신의 팀이 이를 감당할 준비가 되셨습니까?
우리는 하나의 파일에 코딩을 기록했습니다. 재미 있었다! 이 간단한 설정조차도 주기적 로그 파일 회전을 수행해야했습니다. 그러나 여전히 쉬웠습니다. 우리는 또한 grep 으로 파일에서 log를 찾곤 했습니다. 그러나 그때까지도 우리에게는 문제가 있었습니다. 당신의 시스템은 서버가 여러 대 있습니까? 이 모든 파일과 ssh 연결 에서 필요한 정보를 찾아야 합니다.
그러나 이제는 충분하지 않습니다! 이러한 모든 마이크로 서비스, 클라우드 네이티브 및 기타 2020 년 도구를 사용하면 로깅 작업에 복잡한 서브 시스템이 필요합니다. 다음을 포함합니다 (단, 이에 한하지 않음).
ELK : ElasticSearch + Logstash + Kibana https://www.elastic.co/what-is/elk-stack
Graphana + Loki https://grafana.com/oss/loki/
그리고 아래 소스는 ELK 스택이 일을 설정하는 데 걸리는 것에 대한 예제 입니다. https://github.com/deviantony/docker-elk
version: '3.2'
services:
elasticsearch:
build:
context: elasticsearch/
args:
ELK_VERSION: $ELK_VERSION
volumes:
- type: bind
source: ./elasticsearch/config/elasticsearch.yml
target: /usr/share/elasticsearch/config/elasticsearch.yml
read_only: true
- type: volume
source: elasticsearch
target: /usr/share/elasticsearch/data
ports:
- "9200:9200"
- "9300:9300"
environment:
ES_JAVA_OPTS: "-Xmx256m -Xms256m"
ELASTIC_PASSWORD: changeme
discovery.type: single-node
networks:
- elk
logstash:
build:
context: logstash/
args:
ELK_VERSION: $ELK_VERSION
volumes:
- type: bind
source: ./logstash/config/logstash.yml
target: /usr/share/logstash/config/logstash.yml
read_only: true
- type: bind
source: ./logstash/pipeline
target: /usr/share/logstash/pipeline
read_only: true
ports:
- "5000:5000/tcp"
- "5000:5000/udp"
- "9600:9600"
environment:
LS_JAVA_OPTS: "-Xmx256m -Xms256m"
networks:
- elk
depends_on:
- elasticsearch
kibana:
build:
context: kibana/
args:
ELK_VERSION: $ELK_VERSION
volumes:
- type: bind
source: ./kibana/config/kibana.yml
target: /usr/share/kibana/config/kibana.yml
read_only: true
ports:
- "5601:5601"
networks:
- elk
depends_on:
- elasticsearch
networks:
elk:
driver: bridge
volumes:
elasticsearch:
너무 힘들지 않나요?! 봐, 난 그냥 stdout 로 문자열을 쓰고 싶습니다. 그런 다음 어딘가에 보관하십시오.
하지만, 데이터베이스가 필요합니다. 그리고 별도의 웹 서비스. 로깅 서브 시스템도 모니터해야합니다. 정기적으로 업데이트하면 보안이 유지되는지 확인해야합니다. 그리고 충분한 자원이 있습니다. 그리고 모든 사람들이 그것에 접근 할 수 있습니다. 그리고 등등.
물론 로깅을 위한 클라우드 제공 업체가 있습니다. 그것들을 사용하는 것이 좋습니다.
이전의 모든 인수 후에도 여전히 로깅을 사용하는 경우 많은 훈련과 도구가 필요하다는 것을 알 수 있습니다. 몇 가지 잘 알려진 문제가 있습니다.
로깅은 형식에 대해 매우 엄격해야합니다. 로그를 NoSQL 데이터베이스에 저장한다는 것을 기억하십니까? 로그는 색인을 생성 할 수 있어야합니다. 아마도 structlog https://github.com/hynek/structlog 비슷한 솔루션을 사용하게 될 것입니다 . 제 생각에는 이것이 기본값이어야합니다.
다음으로 싸울 것은 레벨입니다. 모든 개발자는 중요하고 중요하지 않은 것에 대해 자신의 아이디어를 사용합니다. 당신이 (Architecturer 로서) 대부분의 경우를 다루는 정책( https://stackoverflow.com/questions/7839565/logging-levels-logback-rule-of-thumb-to-assign-log-levels ) 을 쓰지 않는 한 힘들게 됩니다. 신중하게 검토해야 할 수도 있습니다. 그렇지 않으면, 로깅 데이터베이스가 쓸모없는 데이터가 될 수 있습니다.
로깅 사용법이 일관되어야합니다! 모든 사람들은 자신의 스타일로 쓰는 경향이 있습니다.그것을 위해 linter https://github.com/globality-corp/flake8-logging-format 가 있습니다.
logger.info(
'Hello {world}',
extra={'world': 'Earth'},
)
대신에:
logger.info(
'Hello {world}'.format(world='Earth'),
)
그리고 다른 많은 경우들이 있습니다.
로깅은 비즈니스 지향적이어야합니다. 나는 보통 사람들이 최소한의 유용한 정보로 로깅을 사용하는 것을 본다. 예를 들어, 유효하지 않은 현재 오브젝트 상태를 로깅하는 경우 충분하지 않습니다! 추가 작업이 필요합니다.이 개체가 어떻게이 유효하지 않은 상태가되었는지 보여 주어야합니다. 이 문제에 대한 다른 접근 방식이 있습니다. 일부는 버전 기록 과 같은 간단한 솔루션을 사용하고 일부는 EventSourcing 을 사용 하여 변경 사항에서 객체를 구성합니다. 또한 일부 라이브러리는 전체 실행 컨텍스트, 수행 된 논리적 단계 및 개체 변경 사항을 기록합니다. 마찬가지로 dry-python/stories( 로그 기록에 대한 문서 ). 컨텍스트는 다음과 같습니다.
ApplyPromoCode.apply
find_category
find_promo_code
check_expiration
calculate_discount (errored: TypeError)
Context:
category_id = 1024 # Story argument
category = <example.Category> # Set by ApplyPromoCode.find_category
promo_code = <example.PromoCode> # Set by ApplyPromoCode.find_promo_code
보셨습니까? 발생한 상황과 이 오류를 재현하는 방법에 대한 전체 표현이 포함되어 있습니다. 여기저기서 임의의 상태 정보 만이 아닙니다. 또한 로거에게 직접 전화 할 필요도 없습니다. 당신을 위해 처리됩니다. 그건 그렇고, 그것은 Sentry 통합 https://stories.readthedocs.io/en/latest/contrib/sentry/ 을 가지고 있기 때문에 제 생각에는 더 좋습니다.
기록한 내용에 주의를 기울여야합니다. 있다 로깅에 GDPR 규칙 https://logdna.com/best-practices-for-gdpr-logging/ 당신의 로그 및 전문 보안 감사는. 상식은 암호, 신용 카드, 이메일 등을 기록하는 것이 안전하지 않다는 것을 나타냅니다. 그러나 슬프게도 상식으로는 충분하지 않습니다. 이것은 복잡한 과정입니다.
그리고 관리해야 할 다른 문제도 있습니다. 여기서 요점은 정책을 만들고 프로세스를 작성하고 로깅 툴체인을 설정하여 고위직 직원이 그 일을해야한다는 것을 보여주는 것입니다.
빠른 recap 을 하자 :
로깅은 모니터링 및 오류 추적에서 의미가 없습니다. 경고를 통한 오류 및 비즈니스 모니터링과 같은 더 나은 도구를 대신 사용하십시오.
로깅은 아키텍처에 상당한 복잡성을 추가합니다. 그리고 더 많은 테스트가 필요합니다. 계약의 명시적인 부분을 로깅 할 수있는 아키텍처 패턴 사용
로깅은 자체적으로 전체 인프라 하위 시스템입니다. 그리고 꽤 복잡한 것입니다. 유지 관리하거나이 작업을 기존 로깅 서비스에 아웃소싱해야합니다.
로깅은 올바르게 수행되어야합니다. 그리고 어렵다. 많은 툴링을 사용해야합니다. 방금 논의한 문제를 모르는 개발자에게 멘토링을해야합니다
로깅 가치가 있습니까? 이 지식과 프로젝트 요구 사항에 따라 정보에 근거한 결정을 내려야합니다. 내 의견으로는, 대부분의 일반 웹 앱에는 필요하지 않습니다.
제발 이걸 가져와 로깅은 실제로 유용 할 수 있으며 때로는 유용한 정보의 유일한 소스 일 수도 있습니다. 예를 들어 온 프레미스 소프트웨어와 유사합니다. 또는 앱이 아직 완전히 작동하지 않는 초기 단계. 로깅 없이는 무슨 일이 일어나고 있는지 이해하기 어려울 것입니다. 나는“과도한”문화와 싸우고 있습니다. 정당한 이유없이 로그를 사용하는 경우 개발자는 비용과 장단점을 분석하지 않고 수행하기 때문입니다.
당신은 내 편에 합류하고 있습니까?
(아래 URL 은 광고 입니다 도움이 되셨다고 생각하시면 클릭해주세요)
손세정제 추천 https://coupa.ng/bwI56b