C++ std::cout으로 콘솔 꾸미기

4 분 소요

뇌리에 스치는 불편함

본좌가 로봇 제어 프로그램을 개발할 때 뇌리에 스쳐 지나간 것이 있다. 메세지를 출력할 때 DEBUG, INFO, WARN, ERROR, CRITIAL 등을 출력하는데 모두 출력을 기본으로 했을때 너무 읽기 불편했다... 그래서 출력 시 색상, 필체, 배경 등을 내 맘대로 끄적끄적 바꾸면 마음이 편안해질 것만 같았다. 하던 작업을 모두 멈추고 구글링에 인터럽트가 걸렸다. 만약 각 로그 레벨을 다른 색상으로 찍어내고 싶을때는 어떻게 해야 할까?

색상, 포멧 변경

코드는 다음과 같다.
"\033[{FORMAT_ATTRIBUTE};{FORGROUND_COLOR};{BACKGROUND_COLOR}m{TEXT}\033[{RESET_FORMATE_ATTRIBUTE}m"
위 중괄호를 사용할 포멧에 맞춰서 잘 채워주면 이쁘게 출력할 수 있다.
중괄호에 들어갈 변수들은 ANSI_escape_code
를 참고하면 된다. 위 링크에 요롷게 색상 표? 가 있다.
예를 들어 흰색 배경에 화이트 컬러로 글자를 넣고 싶을 때는
"ESC[30;47m{TEXT}"
여기서 ESC 코드는 \033 이다. 예를들어 다음과 같은 출력이 있다고 해보자.
[DEBUG], From: [int main(int, char **)], Message Debug message
[INFO], From: [int main(int, char **)], Message Info message
[WARN], From: [int main(int, char **)], Message Warn message
[ERROR], From: [int main(int, char **)], Message Error message
[CRITICAL], From: [int main(int, char **)], Message Critical message
여기에 색상을 알흠답게 입혀주면 아주 예쁘게 만들 수 있다.
[DEBUG], From: [int main(int, char **)], Message Debug message
[INFO], From: [int main(int, char **)], Message Info message
[WARN], From: [int main(int, char **)], Message Warn message
[ERROR], From: [int main(int, char **)], Message Error message
[CRITICAL], From: [int main(int, char **)], Message Critical message

간단하게 예를 만들어 보았다.
/* Printer.h */
#ifndef __PRINTER__
#define __PRINTER__

#include <iostream>
#include <sstream>

/* FOREGROUND */
#define RST "\x1B[0m"
#define KRED "\x1B[31m"
#define KGRN "\x1B[32m"
#define KYEL "\x1B[33m"
#define KBLU "\x1B[34m"
#define KMAG "\x1B[35m"
#define KCYN "\x1B[36m"
#define KWHT "\x1B[37m"

#define FRED(x) KRED x RST
#define FGRN(x) KGRN x RST
#define FYEL(x) KYEL x RST
#define FBLU(x) KBLU x RST
#define FMAG(x) KMAG x RST
#define FCYN(x) KCYN x RST
#define FWHT(x) KWHT x RST

#define BOLD(x) "\x1B[1m" x RST
#define UNDL(x) "\x1B[4m" x RST

namespace utilties
{
enum LogLevel
{
    DEBUG = 0,
    INFO = 1,
    WARN = 2,
    ERROR = 3,
    CRITIAL = 4,
};

class Printer
{
  public:
    Printer(LogLevel level) : _level(level)
    {
        switch (level)
        {
        case DEBUG:
            _prefix << "[" << FGRN("DEBUG") << "]";
            break;
        case INFO:
            _prefix << "[" << FMAG("INFO") << "]";
            break;
        case WARN:
            _prefix << "[" << FYEL("WARN") << "]";
            break;
        case ERROR:
            _prefix << "[" << FRED("ERROR") << "]";
            break;
        case CRITIAL:
            _prefix << "[" << FCYN("CRITICAL") << "]";
            break;
        }
    }

  private:
    LogLevel _level;
    std::stringstream _prefix;

  public:
    void log(std::string whrere, std::stringstream stream)
    {
        std::cout << _prefix.str() << ", From: [" << whrere << "]"
                  << ", Message " << stream.str() << std::endl;
    }
};
} // namespace utilties
/* main.cpp */
#include <iostream>
#include <Printer.h>
#include <sstream>

using namespace utilties;
int main(int argc, char *argv[])
{
    Printer(LogLevel::DEBUG).log(__PRETTY_FUNCTION__, std::stringstream() << "Debug message");
    Printer(LogLevel::INFO).log(__PRETTY_FUNCTION__, std::stringstream() << "Info message");
    Printer(LogLevel::WARN).log(__PRETTY_FUNCTION__, std::stringstream() << "Warn message");
    Printer(LogLevel::ERROR).log(__PRETTY_FUNCTION__, std::stringstream() << "Error message");
    Printer(LogLevel::CRITIAL).log(__PRETTY_FUNCTION__, std::stringstream() << "Critical message");

    return 0;
}

글자 형태 변경

italic이나 bold로 표현하고 싶을 때는
"\033[{FORMAT_ATTRIBUTE};{FORGROUND_COLOR};{BACKGROUND_COLOR}m{TEXT}\033[{RESET_FORMATE_ATTRIBUTE}m"
여기에 {FORMAT_ATTRIBUTE}부분을 채워 넣으면 된다. 만약 흰색 배경 + 검정 글자 + bold로 설정하려면
"ESC[1;30;47m{TEXT}"
이렇게 설정하면 된다.

출력 형태 변경

출력 형태는 std::oct, std::dec, std::hex등으로 변경한다. 출력 연산자(>>)와 입력 연산자(<<)와 함께 사용될 수 있다.
#include <iostream>
#include <sstream>

int main(int argc, char *argv[])
{

    std::cout << std::hex << 10 << std::endl;
    std::cout << std::dec << 0x0C << std::endl;
    std::cout << std::oct << 9 << std::endl;

    /* 스트림 진법 형식이 바뀌어지면 계속 유지된다. */
    std::cout << std::hex << 10 << ", " << std::dec << 13 << std::endl;

    int in;
    /* 2A를 hex 로 읽어들이자 */
    std::istringstream("2A") >> std::hex >> in;
    std::cout << std::dec << in << ", " << std::hex << in << ", " << std::oct << in << std::endl;

    return 0;
}
NOTE_조작자에 의해 한 번 스트림의 진법 형식이 바뀌면 계속 유지된다. 즉 한변 변경하면 다시 변경해야 한다. ex) std::cout >> std::hex >> 13 >> std::dec >> 13 >> std::endl;

공백 자릿수 표현

std::setwstd::setfill를 쓰거나 boost라이브러리를 사용한다. boost 라이브러리를 사용하려고 간단하게 CMakeLists.txt에 추가해야할 라인이 있다. 패키지를 찾고 타겟에 링크를 걸어주면 된다.
find_package(Boost COMPONENTS system filesystem REQUIRED)
add_executable(${PROJECT_NAME}_ex3 ex3.cpp)
target_link_libraries(${PROJECT_NAME}_ex3 PUBLIC ${Boost_LIBRARIES})
그렇다면 0xA를 0x0A로 표현하고 싶다면 어떻게 해야 할까?
#include <boost/format.hpp>
#include <iomanip>
#include <ios>
#include <iostream>

int main(int argc, char *argv[])
{
    /* Using setw, setfill */
    std::cout << "0x" << std::setw(2) << std::setfill('0') << std::hex << 10 << std::endl;
    std::cout << std::showbase << std::setw(2) << std::hex << 10 << std::endl;
    /* Using boost */
    std::cout << boost::format("0x%02x\n") % 10;
    return 0;
}
std::setw로 출력할 width를 조절하고 빈 공간에는 0을 추가해주면 된다. std::showbase를 사용하면 "0x"를 사용하지 않아도 된다. 그리고 boost::format 라이브러리로 출력할 수있는데, 이때 iostream으로 문자 와 숫자를 << 로 번갈아 사용했을 때 가독성 문제와 불편함 문제를 해결할 수 있다. iostream은 형식이나 인자에 대해서 안전하고 같은 방식의 출력을 사용하는 장점이 있고 이런 기능을 확장 가능한 printf함수를 필요하여 만들어진 것이 format 라이브러리다.

참고

태그:

카테고리:

업데이트: