C++ Boost Log - MultiThread Log

2019. 8. 16. 15:57개발

서버 개발 필수 요소 중 하나인 Log 시스템을 구축해봤습니다.

 

나름대로 아래의 조건을 나열해두고 진행하였는데...생각보다 잘된 것도 있고.. 안된 것도 있어서 미묘한 감정..

 

  1. 날짜 바뀌는 것에 따라 새 로그 파일 생성
  2. 일정 용량에 따라 새 로그 파일 생성 ( 20190816_1.log , 20190816_2.log...)
  3. 서버 재시작시 기존 생성된 파일에 이어 로그 기입(20180816_1.log | 재시작 | 20180816_2.log...)

BOOST의 1.70 Document를 참고해서 개발하였습니다.

 

https://www.boost.org/doc/libs/1_70_0/libs/log/doc/html/log/detailed/sink_frontends.html#log.detailed.sink_frontends.async.ordering_log_records

 

Sink frontends - 1.70.0

Sink frontends are the part of sinks provided by the library, that implements the common functionality shared between all sinks. This includes support for filtering, exception handling and thread synchronization. Also, since formatting is typical for text-

www.boost.org

 

Boost Log 라이브러리 디자인

 

Boost Document의 Log 라이브러리 디자인

특징

- 3가지의 주요 계층 : 로그 데이터 수집 계층 / 수집 데이터 처리 계층 / 계층 간 상호 연결을 위한 중앙 허브 

- 스토리지를 통한 저장, 트레이 아이콘이나 경보 음을 통한 알림 등 클래식한 부분 외로도 사용 가능

 

Attribute(속성) / Attribute-Value(속성 값)

속성의 예 - 시간 

속성 값의 예 - 특정 시점

 

속성 세트에는 세 가지 종류가 존재

- 글로벌

- 스레드

- 소스

 

로깅을 시작하면 위의 세가지 속성의 세트가 모두 속성에서 속성 값을 얻습니다.

속성 값들은 지정된 이름으로 단일 세트를 형성하게 됩니다. 이 값들은 특정 로그 레코드에만 첨부되며, 동일한 이름의 속성이 여럿 나타날 경우, 우선 순위 기준으로 해결됩니다.(소스의 우선순위가 제일 높고, 글로벌 순위가 가장 낮음)

 

로깅 코어 및 필터링

속성 값 세트가 구성되면 로깅 코어는 이 로그 레코드의 처리 여부를 결정합니다. 이것을 필터링이라고 합니다.

 

- 글로벌 필터링 : 로깅 코어 자체에서 먼저 불필요한 레코드를 제거

- 싱크 별 필터링 :  각각의 싱크에서 레코드를 걸러서 자신에게 해당하는 것들만 저장

 

필터링은 로그 레코드에 각 한번만 수행, 로그 레코드 메시지와 같은 일부 속성 값은 일반적으로 필터링 이후에 레코드에 첨부됩니다.

 

싱크 및 포멧

싱크는 frontend와 backend 두 부분으로 구성됩니다. 필터링, 포멧 지정 및 스레드 동기화 같은 부분을 backend에서 처리하게 되므로 실질적으로 로그 처리를 위해 backend 위주로 구현하게 됩니다.

(ex - 네트워크를 통해 원격 로그 처리 노드로 전송하거나 기록 메시지를 툴팁 알림으로 넣는 싱크가 있을 수 있습니다.)

 

구현

Logger 소스

#pragma once
#include "stdafx.h"

#include <boost/ref.hpp>
#include <boost/bind.hpp>
#include <boost/smart_ptr/shared_ptr.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/log/expressions/formatters/date_time.hpp>
#include <boost/log/support/date_time.hpp>
#include <boost/thread/thread.hpp>
#include <boost/thread/barrier.hpp>

#include <boost/log/core.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/common.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/attributes.hpp>
#include <boost/log/sinks.hpp>
#include <boost/log/sources/logger.hpp>
#include <boost/log/utility/record_ordering.hpp>
#include <boost/log/utility/manipulators/add_value.hpp>
#include <boost/log/utility/setup/file.hpp>

#include <chrono>
#include <ctime>
#include <iomanip>
#include <string>
#include<sstream>
#include <string>
#include <fstream>

namespace attrs = boost::log::attributes;
namespace logging = boost::log;
namespace src = boost::log::sources;
namespace sinks = boost::log::sinks;
namespace keywords = boost::log::keywords;
namespace expr = boost::log::expressions;
using namespace logging::trivial;

//라인 넘버링과 해당 소스파일 출력을 첨가하기 위한 매크로
#define CUSTOM_LOG_TRACE(logger,sev) \
BOOST_LOG_SEV(logger, sev) <<"["<< sev <<"]" << "(" << __FILE__ << ", " << __LINE__ << ") " << ":"


//글로벌 로거 생성을 위한 매크로 - thread-safe를 위해 글로벌 로거 사용
BOOST_LOG_INLINE_GLOBAL_LOGGER_DEFAULT(global_lg, src::logger_mt)

class BoostLogger {
 
public: 
	
	typedef sinks::text_ostream_backend backend_t;
	typedef sinks::text_file_backend file_sink;
    //비동기 싱크 자료형 선언 - ostream, Queueing 방식, 레코드 순서, 속성 값 유형, 선택적 속성 값 비교(기본적으로 std::less 사용)
	typedef sinks::asynchronous_sink<sinks::text_file_backend, sinks::unbounded_ordering_queue
		<logging::attribute_value_ordering<unsigned int, std::less<unsigned int>>>> sink_t;
	boost::shared_ptr<sink_t> sink;
	boost::shared_ptr<logging::core> core;
	BoostLogger()
	{
		try 
		{
			core = logging::core::get();
			//위에서 선언한 sink_t에 맞춰 text_file_backend, RecordID 속성 값에 따른 ordering 설정
			sink = boost::make_shared<sink_t>(boost::make_shared<file_sink>(), 
				keywords::order = logging::make_attr_ordering("RecordID", std::less<unsigned int>()));
			
			//sink backend 설정
			sink->locked_backend()->set_file_collector(sinks::file::make_collector(keywords::target = "logs"));
			sink->locked_backend()->set_rotation_size(1024*10*10);
			sink->locked_backend()->set_time_based_rotation(sinks::file::rotation_at_time_point(0, 0, 0));
			sink->locked_backend()->set_open_mode(std::ios_base::out | std::ios_base::app);
			sink->locked_backend()->set_file_name_pattern("boost_srv_%Y%m%d_%N.log");
			sink->locked_backend()->scan_for_files();
	
			//출력 포멧 설정
			sink->set_formatter(
				expr::stream
				<< "<" << expr::attr<boost::thread::id>("ThreadID") << ">"
				<< "[" << expr::attr<unsigned int>("RecordID") << "]"
				<< "[" 
				<< expr::format_date_time< boost::posix_time::ptime >("TimeStamp", "%Y-%m-%d %H:%M:%S")
				<< "]"
				<< expr::smessage
			);


			
		}
		catch (std::exception& e)
		{
			std::cout << "FAILURE:" << e.what() << std::endl;
		}
		
	}

	~BoostLogger()
	{
		core->remove_sink(sink);
		
		sink->stop();
		sink->flush();
		sink.reset();
		
	}
		


	
	boost::shared_ptr<sink_t> getSink()
	{
		return sink;
	}


};

Main 실행 소스

enum
{
	LOG_RECORDS_TO_WRITE = 10000,
	THREAD_COUNT = 2
};

void thread_fun(boost::barrier& bar)
{
	// 모든 스레드 생성을 기다림
	bar.wait();

	// 스레드 아이디 등록
	BOOST_LOG_SCOPED_THREAD_TAG("ThreadID", boost::this_thread::get_id());

	// 설정한 수 만큼 로깅 실행
	for (unsigned int i = 0; i < LOG_RECORDS_TO_WRITE; ++i)
	{
		CUSTOM_LOG_TRACE(global_lg::get(), warning)  << "Log record " << i;
	}
}
int main()
{
	//구현 로거
    BoostLogger boostLogger;
    
	
	using namespace logging::trivial;

	//구현한 로거의 sink를 core에 추가
	logging::core::get()->add_sink(boostLogger.getSink());

	// 글로벌 속성을 core로 가져옴(동일 이름-> 우선순위에 따라 레코드됨)
	logging::core::get()->add_global_attribute("TimeStamp", attrs::local_clock());
	logging::core::get()->add_global_attribute("RecordID", attrs::counter< unsigned int >());

	// 로깅을 수행하는 스레드 생성
	boost::barrier bar(THREAD_COUNT);
	boost::thread_group threads;
	for (unsigned int i = 0; i < THREAD_COUNT; ++i)
		threads.create_thread(boost::bind(&thread_fun, boost::ref(bar)));

	
	threads.join_all();
	

}

테스트

-2개의 스레드 상황에서 로깅(스레드 아이디 : 53bc / 5f9c)

-날짜 변경으로 인한 파일 날짜 변경

 

 

참고 예제

 

https://www.boost.org/doc/libs/1_70_0/libs/log/example/async_log/main.cpp

 

libs/log/example/async_log/main.cpp - 1.70.0

 

www.boost.org