일을 잘하려면 먼저 도구를 연마해야합니다.
많은 프로그래머는 응용 프로그램의 동작을 기록하는 것이 얼마나 중요한지 잊을 수 있습니다. 다중 스레드 환경에서 고압으로 인한 동시 버그가 발생할 때 로그 기록의 중요성을 이해할 수 있습니다.
어떤 사람들은이 문장을 코드에 추가하는 것을 매우 기뻤습니다.
log.info ( "행복하고 평온한 로깅");
그는 유지 보수, 튜닝 및 결함 식별에서 응용 프로그램 로그의 중요성을 인식하지 못할 수도 있습니다. SLF4J는 주로 스키마를 주입하는 좋은 방법을 지원하기 때문에 가장 좋은 로깅 API라고 생각합니다.
log.debug ( "발견 된 {} 레코드 일치 필터 : '{}'", 레코드, 필터);
log4J의 경우 다음을 수행 할 수 있습니다.
log.debug ( "found" + records + "레코드 매칭 필터 : ' + 필터 +"' ");
이 글쓰기는 더욱 명백하고 가독성이 좋지 않을뿐만 아니라 효율성에 영향을 미치는 문자열 스 플라이 싱도 있습니다 (이 레벨이 출력이 필요하지 않은 경우).
SLF4J는 {} 주입 기능을 소개하며, 매번 문자열 스 플라이 싱을 피하기 때문에 TOSTRING 방법은 호출되지 않으며 ISDEBUGENABLED를 추가 할 필요가 없습니다. SLF4J는 외관 모드의 적용이며 단지 외관 일뿐입니다. 특정 구현의 경우 로그백 프레임 워크를 권장합니다. 이미 완전한 log4J 대신 이미 한 번 광고했습니다. 많은 흥미로운 기능이 있습니다. Log4J와 달리 여전히 활발한 개발 및 개선 중입니다.
권장 할 또 다른 도구는 perf4j입니다.
perf4j는 system.currenttimeMillis ()로 log4J가 system.out.println ()입니다.
Log4J가 System.out.println에 대한 더 나은 대안과 마찬가지로 Perf4J는 System.CurrentTimeMillis ()를 대체하는 것과 비슷합니다. 프로젝트에서 Perf4J를 소개하고 높은 하중에서 어떻게 작동하는지 관찰했습니다. 관리자와 비즈니스 사용자는이 가제트가 제공하는 아름다운 차트에 기절합니다. 우리는 언제든지 성능 문제를 볼 수 있습니다. Perf4J는 이야기 할 특별한 기사 여야합니다. 이제 먼저 개발자 가이드를 볼 수 있습니다. Ceki Gülcü (Log4J, SLF4J 및 Logback Project의 제작자)도있어 공통점 로깅에 대한 종속성을 제거 할 수있는 쉬운 방법을 제공합니다.
로그 레벨을 잊지 마십시오
로그 라인을 추가 할 때마다 여기에서 어떤 로그 레벨을 사용해야합니까? 프로그래머의 약 90% 가이 문제에 많은 관심을 기울이지 않습니다. 그들은 하나의 레벨을 사용하여 로그를 기록합니다 (일반적으로 정보 또는 디버그). 왜?
로그 프레임 워크에는 System.out와 비교하여 두 가지 주요 장점이 있습니다 : 분류 및 레벨. 둘 다 문제를 문제 해결 오류를 영구적으로 또는 영구적으로 필터링 할 수 있습니다.
오류 : 심각한 오류가 발생했으며 즉시 처리해야합니다. 이 수준의 오류는 모든 시스템에 참을 수 없습니다. 예를 들어, NULL 포인터 예외, 데이터베이스를 사용할 수 없으며 중요한 경로의 사용 사례를 계속 실행할 수 없습니다.
경고 : 후속 과정은 계속 될 것이지만 심각하게 받아 들여야합니다. 실제로 여기에는 두 가지 레벨이 있기를 바랍니다. 하나는 솔루션의 명백한 문제 (예 : "현재 데이터를 사용할 수없고, 캐시 된 데이터를 사용하지 않습니다"), 다른 하나는 잠재적 인 문제 및 제안 (예 : "프로그램 콘솔에서 실행된다"또는 "관리 콘솔의 비밀번호는 충분히 안전하지 않습니다"와 같은 제안입니다. 응용 프로그램은이 정보를 견딜 수 있지만 확인하고 수정해야합니다.
디버그 : 개발자가 걱정하는 것. 나중에 나는이 수준에서 어떤 것들을 기록 해야하는지 이야기 할 것입니다.
추적 : 개발 단계에서만 사용되는 자세한 정보. 제품이 출시 된 후 짧은 시간 내에이 정보에주의를 기울여야 할 수도 있지만 이러한 로그 레코드는 일시적이므로 결국 꺼져야합니다. 디버그와 트레이스의 차이를 구별하기는 어렵지만 로그 라인을 추가하고 개발 및 테스트 후 삭제하면 로그는 트레이스 레벨에 있어야합니다.
위의 목록은 단지 제안 일뿐입니다. 자신의 규칙에 따라 기록 할 수 있지만 특정 규칙을 갖는 것이 가장 좋습니다. 내 개인적인 경험은 다음과 같습니다. 코드 레벨에서 로그를 필터링하지 말고 올바른 로그 레벨을 사용하여 원하는 정보를 빠르게 필터링하여 많은 시간을 절약 할 수 있습니다.
마지막으로 말해야 할 것은이 악명 높은 조건부 명세서입니다. 어떤 사람들은 각 로그 전에 이것을 추가하는 것을 좋아합니다.
if (log.isdebugenabled ()) log.debug ( "상업용 장소");
개인적으로, 나는 당신이 코드 에이 지저분한 것을 추가하지 않아야한다고 생각합니다. 성능이 크게 향상되지 않은 것 같습니다 (특히 SLF4J를 사용한 후)은 조기 최적화와 비슷합니다. 또한 조금 중복되지 않습니까? 드물게,이 명시 적 판단 진술은 로그 메시지 자체 구성이 너무 비싸다는 것을 증명하지 않는 한 명시 적으로 필요합니다. 그렇지 않으면, 당신은 당신이해야 할만 큼 많은 것을 기억하고 로그 프레임 워크가 이것에 대해 걱정하게 할 수 있습니다.
당신이 무엇을 녹음하고 있는지 알고 있습니까?
로그 라인을 작성할 때마다 로그 파일에서 인쇄하는 내용을 잠시 시간을 내십시오. 로그를 읽고 예외가 어디에 있는지 알아보십시오. 먼저, 최소한 널 포인터 예외를 피하십시오.
log.debug ( "id가있는 처리 요청 : {}", request.getId ());
요청이 널이 아니라고 확인 했습니까?
녹음 컬렉션도 큰 구덩이입니다. 최대 절전 모드를 사용하여 데이터베이스에서 도메인 객체 모음을 얻는 경우 실수로 다음과 같이 씁니다. log.debug ( "반환 사용자 : {}", 사용자);
SLF4J는이 진술이 실제로 인쇄 될 때만 Tostring 메소드를 호출합니다. 물론 이것은 멋지다. 그러나 메모리 오버 플로우, N+1 선택 문제, 스레드가 죽음에 굶주림, 초기화 지연 예외, 로그 스토리지 공간이 소진 될 수 있습니다. 가장 좋은 방법은 객체의 ID (또는 컬렉션의 크기 만) 만 기록하는 것입니다. 그러나 ID를 수집하려면 각 객체에 대한 getID 메소드를 호출 해야하는데, 이는 Java에서는 쉬운 일이 아닙니다. Groovy에는 훌륭한 확장 연산자가 있습니다 (사용자*.id). Java에서는 Commons Beanutils 라이브러리를 사용하여 시뮬레이션 할 수 있습니다.
log.debug ( "반환 사용자 ID : {}", collect (user, "id"));
이것은 아마도 수집 방법이 구현되는 방법 일 것입니다.
Public STATIC Collection Collect (Collection Collection, String PropertyName) {return CollectionUtils.collect (Collection, New BeanTopropertyValuetransformer (PropertyName));}마지막으로, Tostring 방법은 올바르게 구현되거나 사용되지 않을 수 있습니다.
먼저, 로그를 기록하기 위해 각 클래스에 대한 토스트 링을 만드는 방법에는 여러 가지가 있습니다. TostringBuilder를 사용하여 생성하는 것이 가장 좋습니다 (반사 구현의 버전은 아닙니다).
둘째, 배열 및 비정형 세트에주의를 기울이십시오. 배열의 Tostring 구현 및 일부 대체 컬렉션은 각 요소의 Tostring 방법을 하나씩 호출 할 수 없습니다. JDK가 제공하는 배열#Deeptostring 방법을 사용할 수 있습니다. 형식 예외에 대한 정보가 있는지 확인하려면 직접 인쇄 한 로그를 확인하십시오.
부작용을 피하십시오
로그 인쇄는 일반적으로 프로그램 성능에 큰 영향을 미치지 않습니다. 최근에, 내 친구는 일부 특수 플랫폼에서 실행되는 시스템에서 최대 절전 모드 LazyinitializationException 예외를 던졌습니다. 이로부터 추측 한 바와 같이, 일부 로그 인쇄는 세션이 연결될 때 초기화 컬렉션이 지연됩니다. 이 경우 로그 레벨이 증가하면 컬렉션이 더 이상 초기화되지 않습니다. 이 맥락 정보를 모른다면,이 버그를 발견하는 데 얼마나 오래 걸릴까요?
또 다른 부작용은 프로그램의 실행 속도에 영향을 미친다는 것입니다. 이 질문에 대한 빠른 답변 : 로그가 너무 많이 인쇄되거나 Tostring 및 String 스 플라이 싱이 올바르게 사용되지 않으면 로그 인쇄는 성능에 부정적인 영향을 미칩니다. 얼마나 큰가요? 음, 나는 너무 많은 로그로 스레드가 굶어 죽기 때문에 15 분마다 프로그램이 다시 시작되는 것을 보았습니다. 이것은 부작용입니다! 내 경험상 1 시간 안에 100 메가 바이트를 인쇄하는 것은 거의 상한입니다.
물론 로그 인쇄 예외로 인해 비즈니스 프로세스가 중단되면이 부작용이 좋을 것입니다. 나는 종종 사람들이 이것을 피하기 위해 이것을 쓰는 것을 본다 :
{log.trace ( "id =" + request.getUser (). getId () + "ac이것은 진정한 코드이지만 세상을보다 정화시키기 위해서는 이렇게 쓰지 마십시오.
명확하게 설명하십시오
각 로그 레코드에는 데이터 및 설명이 포함되어 있습니다. 이 예를 살펴보십시오.
log.debug ( "메시지 처리"); log.debug (message.getjmsmessageid ()); log.debug ( "id '{}'처리 된 메시지", message.getJmsMessageId ()); 익숙하지 않은 시스템에서 오류 문제를 해결할 때 어떤 종류의 로그를 선호합니까? 저를 믿으십시오.이 예는 일반적입니다. 부정적인 모드도 있습니다.
if (TextMessage의 메시지 인스턴스)
// ...
또 다른
log.warn ( "알 수없는 메시지 유형");
이 경고 로그에 메시지 유형, 메시지 ID 등을 추가하기가 어렵습니까? 오류가 발생했다는 것을 알고 있지만 무엇입니까? 컨텍스트 정보는 무엇입니까?
세 번째 부정적인 예는 "매직 로그"입니다. 실제 예 : 팀의 많은 프로그래머는 3 ≥ 숫자가 따른다는 것을 알고 있습니다! 숫자 뒤에 # 번호가 뒤 따른 다음 의사-랜덤 번호 로그가 이어집니다. 아무도이 로그를 변경하고 싶지 않습니다. 누군가 키보드를 입력하고 고유 한 "&&&!#"문자열을 선택했다면 원하는 정보를 빠르게 찾을 수 있습니다.
결과적으로 전체 로그 파일은 큰 임의 문자 문자열처럼 보입니다. 어떤 사람들은 이것이 PERL 프로그램인지 궁금해 할 수는 없습니다.
로그 파일은 읽을 수 있고 명확하며 자체적으로 설명해야합니다. 마법 번호, 레코드 값, 숫자, ID 및 컨텍스트를 사용하지 마십시오. 처리 된 데이터와 그 의미를 기록하십시오. 프로그램이 무엇을하고 있는지 기록하십시오. 좋은 로그는 프로그램 코드의 좋은 문서 여야합니다.
비밀번호를 인쇄하지 말고 개인 정보가 있다고 언급 했습니까? 나는 그런 어리석은 프로그래머가 없다고 생각합니다.
형식을 조정하십시오
로그 형식은 매우 유용한 도구로, 로그에 귀중한 컨텍스트 정보를 보이지 않게 추가합니다. 그러나 어떤 종류의 정보가 형식에 포함되어 있는지 명확하게 생각해야합니다. 예를 들어, 로그 이름에 이미이 정보가 포함되어 있기 때문에 시간별주기마다 기록 된 로그로 날짜를 기록하는 것은 의미가 없습니다. 반대로, 스레드 이름을 기록하지 않으면 두 개의 스레드가 병렬로 작동하면 로그를 통해 스레드를 추적 할 수 없습니다. 로그는 함께 겹쳤습니다. 단일 스레드 응용 프로그램에서는이 작업을 수행해도 괜찮지 만 이미 과거의 일입니다.
내 경험에서 이상적인 로그 형식에는 (로그 정보 자체 제외) 현재 시간 (날짜 없음, 밀리 초 정밀도), 로그 레벨, 스레드 이름, 단순 로그 이름 (전체 이름이 포함되지 않음) 및 메시지가 포함되어야합니다. 로그백에서는 다음과 같습니다.
<appender name = "stdout"> <encoder> <pattern>%d {hh : mm : ss.sss}%-5level [%스레드] [%logger {0}]%m%n </pattern </encoder> </applender>파일 이름, 클래스 이름, 줄 번호는 유용 해 보이지만 나열 될 필요는 없습니다. 또한 코드에서 빈 로깅을 보았습니다.
log.info ( ""); 프로그래머는 줄 번호가 로그 형식의 일부가 될 것이라고 믿고, 빈 로그 메시지가 파일의 67 행에 나타나면 사용자가 인증되었음을 의미합니다. 뿐만 아니라 클래스 이름 메소드 이름 또는 줄 번호를 기록하면 성능에 큰 영향을 미칩니다.
로깅 프레임 워크의보다 진보 된 기능은 매핑 된 진단 컨텍스트입니다. MDC는 로컬 스레드 인 맵 일뿐입니다. 이 스레드의 모든 로그 레코드가 출력 형식의 일부로이 맵에서 해당 정보를 얻을 수 있도록 키 값 쌍을이 맵에 넣을 수 있습니다.
메소드의 매개 변수와 반환 값을 기록하십시오
개발 단계에서 버그를 찾으면 일반적으로 디버거를 사용하여 특정 이유를 추적합니다. 이제 더 이상 디버거를 사용하지 않을 것이라고 가정 해 봅시다. 예를 들어,이 버그는 며칠 전에 사용자의 환경에 나타나기 때문에 몇 가지 로그를 얻을 수 있습니다. 이것에서 무엇을 알 수 있습니까?
각 방법에 대한 인쇄 및 출력 매개 변수의 간단한 원리를 따르는 경우 디버거가 전혀 필요하지 않습니다. 물론 각 방법은 외부 시스템에 액세스하고, 블록, 대기 등을 대기 할 수 있으며,이를 고려해야합니다. 다음 형식 만 참조하십시오.
public String printDocument (문서 문서, 모드 모드) {log.debug ( "printDocument enther (doc = {}, mode = {})", doc, mode); 문자열 ID = ...; // 긴 인쇄 작업 log.debug ( "leave intrindDocument () : {}", id); 반환 ID;}메소드의 시작과 끝에서 로그를 기록하기 때문에 비효율적 인 코드를 수동으로 찾아 교착 상태와 굶주림을 유발할 수있는 원인을 감지 할 수도 있습니다. 메소드 이름의 의미가 명확하다면 로그를 지우는 것이 유쾌합니다. 마찬가지로, 각 단계에서 무엇을하고 있는지 알기 때문에 예외를 분석하는 것이 더 쉽습니다. 코드에 기록해야 할 많은 방법이있는 경우 AOP 섹션을 사용하여 완료 할 수 있습니다. 이렇게하면 중복 코드가 줄어들면 사용하면 매우 조심해야하며 조심하지 않으면 많은 양의 로그 출력이 발생할 수 있습니다.
이러한 종류의 로그에 가장 적합한 레벨은 디버그 및 트레이스입니다. 메소드가 너무 자주 호출되고 로그를 기록하는 것이 성능에 영향을 줄 수 있다면 로그 레벨을 낮추거나 로그를 직접 삭제하면 (또는 전체 메소드 호출 중 하나만 삭제하면 너무 많은 로그가 적은 로그보다 낫습니다. 로깅을 단위 테스트로 생각하면 단위 테스트가 어디에나있는 것처럼 코드는 로그로 덮여 있어야합니다. 시스템의 일부는 로그를 전혀 필요로하지 않습니다. 때로는 시스템이 제대로 작동하는지 여부를 알아야하며 화면에 지속적으로 범람하는 로그 만 볼 수 있습니다.
외부 시스템을 관찰하십시오
이 제안은 이전의 제안과 약간 다릅니다. 외부 시스템과 통신하는 경우 시스템의 나가는 및 읽기 데이터를 기록해야합니다. 시스템 통합은 집안일이며 두 응용 프로그램 (다른 회사, 환경, 기술 팀) 간의 문제를 진단하는 것은 특히 어렵습니다. 최근에 우리는 Apache CXF의 SOAP 및 HTTP 헤더를 포함한 완전한 메시지 컨텐츠를 녹화하는 것이 시스템의 통합 및 테스트 단계에서 매우 효과적이라는 것을 발견했습니다.
이것은 비싸고 성능에 영향을 미치는 경우 로그 만 꺼질 수 있습니다. 그러나 이런 식으로 시스템은 매우 빠르게 실행되어 빨리 매달릴 수 있으므로 아무것도 할 수 없습니까? 외부 시스템과 통합 할 때는 추가로주의를 기울이고 약간의 오버 헤드를 희생 할 수 있습니다. 운이 좋고 시스템 통합이 ESB에 의해 처리되면 버스에 요청과 응답을 기록하는 것이 가장 좋습니다. 뮬 의이 로그 구성 요소를 참조 할 수 있습니다.
때로는 외부 시스템과 교환 된 데이터의 양에 따라 모든 것을 적어 두는 것이 불가능하다고 결정합니다. 반면에 테스트 단계와 초기 릴리스 단계에서 모든 것을 로그에 유지하고 성능을 희생 할 준비를하는 것이 가장 좋습니다. 로그 레벨을 조정하여 수행 할 수 있습니다. 다음 팁을 살펴보십시오.
Collection <integer> requestIds = // ... if (log.isdebugenabled ()) log.debug ( "Processing Ids : {}", requestIds); else log.info ( "Processing Ids size : {}", requestIds.size ()); 이 로거가 디버그 레벨로 구성되면 요청 ID의 전체 모음을 인쇄합니다. 정보 정보를 인쇄하도록 구성된 경우 세트의 크기 만 출력합니다. ISinfoenabled 조건을 잊어 버린지 물어볼 수 있습니다. 두 번째 제안을 참조하십시오. 여기서 주목할만한 또 다른 것은 ID 세트가 무효가 될 수 없다는 것입니다. Debug에서 NULL로 일반적으로 인쇄 할 수 있지만 정보로 구성된 경우 큰 NULL 포인터입니다. 4 번째 제안에서 언급 된 부작용을 기억하십니까?
올바른 로깅 예외
먼저 예외를 기록하지 말고 프레임 워크 나 컨테이너가이를 수행하도록하십시오. 물론 예외가 있습니다. 원격 서비스에서 예외 (RMI, EJB 등)를 던지면 예외는 직렬화되어 클라이언트 (API의 일부)로 반환 할 수 있습니다. 그렇지 않으면 클라이언트는 실제 오류 메시지 대신 NoclassDeffoundError 또는 기타 이상한 예외를받습니다.
예외 녹화는 로깅의 가장 중요한 책임 중 하나이지만 많은 프로그래머는 예외를 처리하는 방법으로 로깅을 사용하는 경향이 있습니다. 일반적으로 기본값 (일반적으로 NULL, 0 또는 빈 문자열)을 반환하고 아무 일도 일어나지 않는 척합니다. 때때로 그들은 먼저 예외를 기록한 다음 예외를 마무리 한 다음 버리게됩니다.
log.error ( "io Exception", e); 새로운 customexception (e)을 던지십시오.
이런 식으로 스택 정보는 일반적으로 두 번 인쇄됩니다. 기록을 기록하거나 포장하여 던지고 동시에 사용하지 마십시오. 그렇지 않으면 로그가 혼란스러워 보입니다.
우리가 정말로 기록하고 싶다면 어떻게해야합니까? 어떤 이유로 든 (아마도 API와 문서를 읽지 않습니까?) 로깅의 약 절반은 잘못되었다고 생각합니다. 작은 테스트, 다음 로그 명령문 중 NULL 포인터 예외를 올바르게 인쇄 할 수있는 것은 무엇입니까?
{integer x = null; ++ x;} catch (예외 e) {log.error (e); // log.error (e, e); // b log.error ( "" + e); // c log.error (e.toString ()); // d log.error (e.getMessage ()); // e log.error (null, e); // f log.error ( "", e); // g log.error ( "{}", e); // h log.error ( "{}", e.getMessage ()); // i log.error ( "오류 읽기 구성 파일 :" + e); // j log.error ( "오류 읽기 구성 파일 :" + e.getMessage ()); // k log.error ( "오류 읽기 구성 파일", e); //엘}G와 L (이것은 더 낫다)만이 옳다는 것은 이상합니다! A와 B는 SLF4J에 따라 컴파일되지 않습니다. 다른 사람들은 스택 추적 정보를 버리거나 잘못된 정보를 인쇄합니다. 예를 들어, NULL 포인터 예외 자체가 예외 정보를 제공하지 않고 스택 정보가 인쇄되지 않기 때문에 E는 아무것도 인쇄하지 않습니다. 첫 번째 매개 변수는 일반적으로 오류 자체에 대한 텍스트 정보입니다. 예외 정보를 작성하지 마십시오. 스택 정보 앞에서 로그를 인쇄 한 후 자동으로 나옵니다. 그러나 이것을 인쇄하려면 물론 예외를 두 번째 매개 변수로 전달해야합니다.
로그는 읽을 수 있고 구문 분석하기 쉬워야합니다
이제 귀하의 로그에 관심이있는 두 가지 사용자 그룹이 있습니다. 우리 인간 (동의 여부에 관계없이 코더가 여기 있습니다)과 컴퓨터 (일반적으로 시스템 관리자가 작성한 쉘 스크립트). 로그는 두 사용자 모두 이해하기에 적합해야합니다. 누군가가 당신의 프로그램의 통나무를보고 이것을 본다면 :
그런 다음 당신은 확실히 내 조언을 따르지 않았습니다. 로그는 코드만큼 읽고 이해하기 쉬워야합니다.
반면에, 프로그램이 매시간 절반의 GB 로그를 생성하면 하나 또는 그래픽 텍스트 편집기를 완성 할 수 없습니다. 현재, 우리의 노인 Grep, Sed 및 Awk는 그들이 들판에 왔을 때 왔습니다. 가능하면 기록 기록이 모두 컴퓨터를 분명히하는 것이 가장 좋습니다. 숫자를 형식화하지 말고 정기적으로 일치 할 수있는 일부 형식을 사용하십시오. 가능하지 않으면 두 가지 형식으로 데이터를 인쇄하십시오.
log.debug ( "ttl 요청 ttl 설정 : {} ({})", 새 날짜 (TTL), TTL); // TTL 요청 ttl to : wed apr 28 20:14:12 Cest 2010 (12724784452437) 최종 문자열 duration = duration smatutils. {} ms ({}) ", durationmillis, duration); // 가져 오기 : 123456789ms (1 일 10 시간 10 시간 17 분 36 초)컴퓨터는 "1970 년 이후의 MS"를보고, 그러한 시간 형식은 당신에게 감사 할 것이며, 사람들은 "1 일 10 시간 17 분 36 초"와 같은 것을 보게되어 기쁩니다.
요컨대, 저널은 당신이 그것에 대해 기꺼이 생각한다면시처럼 우아하게 쓸 수 있습니다.
위의 것은 Java Log 조정 출력 형식에 대한 정보 모음입니다. 관심있는 친구들은 그것을 언급 할 수 있습니다.