# 로깅 레벨과 설정 방법

#### ROS2 로깅의 기본 개념

ROS2의 로깅 시스템은 각 노드(Node)가 수행하는 작업 과정을 기록하여, 이후 디버깅 또는 시스템 동작 분석에 활용할 수 있도록 한다. 로깅은 단순히 텍스트를 출력하는 데 그치지 않고, 상황에 맞춰 적절한 심각도(Level)를 부여해 메시지를 남기도록 제공된다. 이를 통해 사용자는 시스템 동작 중 발생하는 문제를 빠르게 파악하고, 디버깅 시간을 단축할 수 있다.

ROS2의 로깅은 크게 다음과 같은 특징을 가진다.

* 노드 단위로 로거(Logger)를 생성하여 메시지를 남긴다.
* 메시지마다 정해진 로그 레벨을 통해 필터링 및 출력 여부를 결정한다.
* C++기준으로 `rclcpp`에서 제공하는 매크로(예: `RCLCPP_INFO`)를 사용하면 편리하다.
* 로깅 백엔드는 `rcutils`라는 라이브러리를 통해 구성된다.

#### 로깅 레벨의 종류

ROS2에서 지원하는 로깅 레벨은 총 5단계다.

1. **DEBUG**
   * 개발 과정에서 상세한 정보를 확인하기 위한 레벨이다.
   * 변수가 어떤 값을 가지는지, 특정 로직이 어떻게 진행되는지를 살펴보기에 적합하다.
   * 예: `RCLCPP_DEBUG(logger, "현재 변수의 값: %d", x);`
2. **INFO**
   * 일반적인 정보성 메시지를 출력하기 위한 레벨이다.
   * 시스템 흐름을 간단히 파악하거나, 중요 이벤트를 기록할 때 사용한다.
   * 예: `RCLCPP_INFO(logger, "노드 초기화 완료");`
3. **WARN**
   * 주의가 필요한 상황을 알리는 레벨이다.
   * 에러까지는 아니지만, 잠재적 위험이나 비정상 동작이 예상될 때 사용한다.
   * 예: `RCLCPP_WARN(logger, "센서 데이터 이상. 점검 필요");`
4. **ERROR**
   * 명백한 오류가 발생했을 때 사용한다.
   * 예외 상황이 발생하여 정상적인 흐름이 중단되었지만, 노드의 동작이 완전히 중단되는 수준은 아니다.
   * 예: `RCLCPP_ERROR(logger, "파라미터 로드 실패. 기본값 사용");`
5. **FATAL**
   * 치명적인 오류 상황을 알리는 레벨이다.
   * 시스템 혹은 노드가 더 이상 정상적인 동작을 할 수 없을 정도의 심각한 문제에 사용한다.
   * 예: `RCLCPP_FATAL(logger, "메모리 부족으로 노드가 종료된다");`

이러한 레벨들은 사용자가 원하는 수준으로 메시지를 필터링하거나 출력하여 디버깅 효율을 높일 수 있도록 만든 것이다.

#### 로깅 레벨 설정 방법 개요

ROS2에서 로깅 레벨은 크게 세 가지 방법으로 설정할 수 있다.

1. **코드 내에서 매크로 사용**
   * `RCLCPP_DEBUG`, `RCLCPP_INFO` 등과 같이 직접 로그 레벨이 포함된 매크로를 사용한다.
   * 각 매크로는 본문에 기록된 메시지를 해당 레벨로 출력한다.
2. **ROS2 런치(Launch) 파일에서 설정**
   * 노드마다 로그 레벨을 지정할 수 있다.
   * 예: `LogLevel := 'DEBUG'` 와 같이 런치 파일에서 파라미터를 전달한다.
3. **환경 변수나 커맨드라인 옵션**
   * `$ export RCUTILS_LOG_SEVERITY=DEBUG` 와 같은 환경 변수를 사용한다.
   * 또는 `ros2 run` 명령에 `--ros-args --log-level DEBUG` 옵션을 붙여 실행한다.

ROS2에서 로그 레벨은 융통성 있게 설정할 수 있으므로, 운영 환경 또는 디버깅 목적에 따라 적절히 선택하여 사용하면 된다.

#### 로깅 레벨 설정 방법 상세

ROS2에서 로깅 레벨을 동적으로 바꿔가며 디버깅하면 매우 편리하다. 여기서는 코드 내 설정, 환경 변수 및 커맨드라인 옵션을 사용하는 방법, 그리고 런치 파일을 통한 설정 방법을 좀 더 구체적으로 살펴본다.

#### 코드 내 매크로 사용

C++에서 주로 사용되는 로깅 매크로는 다음과 같다.

* `RCLCPP_DEBUG(logger, "메시지")`
* `RCLCPP_INFO(logger, "메시지")`
* `RCLCPP_WARN(logger, "메시지")`
* `RCLCPP_ERROR(logger, "메시지")`
* `RCLCPP_FATAL(logger, "메시지")`

여기서 `logger`는 일반적으로 노드 핸들(예: `this->get_logger()`)을 사용한다. 코드 예시는 다음과 같다.

```cpp
#include <rclcpp/rclcpp.hpp>

class MyNode : public rclcpp::Node
{
public:
  MyNode() : Node("my_node")
  {
    RCLCPP_INFO(this->get_logger(), "Node has started");
    int x = 42;
    RCLCPP_DEBUG(this->get_logger(), "The value of x is: %d", x);
  }
};

int main(int argc, char** argv)
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<MyNode>());
  rclcpp::shutdown();
  return 0;
}
```

위 코드에서 `RCLCPP_INFO`는 INFO 레벨 로깅을 출력하고, `RCLCPP_DEBUG`는 DEBUG 레벨 로깅을 출력한다. 이때 노드의 기본 로깅 레벨이 INFO 이상이라면 DEBUG 레벨 로그는 표시되지 않을 수 있다. 로깅 레벨을 높여서 DEBUG 메시지를 보고 싶다면, 실행 시점에 별도의 설정이 필요하다.

#### 커맨드라인 옵션을 통한 설정

ROS2 실행 명령에 로깅 레벨을 설정하면, 코드에 직접 변경을 가하지 않고도 로그 출력을 제어할 수 있다. 일반적인 예시는 다음과 같다.

```bash
ros2 run my_package my_node_executable --ros-args --log-level DEBUG
```

이 명령은 `my_node_executable`을 실행하되, 전역 로깅 레벨을 `DEBUG`로 설정한다. 이때 모든 노드의 로깅 레벨이 DEBUG로 설정되므로, DEBUG 이상의 메시지가 전부 표시된다.

* `--log-level WARN`
* `--log-level ERROR`
* `--log-level FATAL`

등을 지정할 수도 있다. 만약 노드마다 다른 레벨을 적용하고 싶다면, 노드 이름 혹은 로거 이름을 명시할 수 있는 구문을 활용해야 한다.

#### 환경 변수를 통한 설정

ROS2는 내부적으로 `rcutils` 라이브러리를 통해 로깅을 처리하므로, 해당 라이브러리에서 사용되는 환경 변수를 설정할 수 있다. 예를 들어:

```bash
export RCUTILS_LOG_SEVERITY=WARN
```

위와 같이 설정하면, WARN 이상의 메시지만 출력된다. 이 방법은 로컬 환경 설정 파일(예: `~/.bashrc` 등)에 선언해두면 모든 ROS2 세션에서 동일하게 적용할 수 있어 편리하다. 다만, 개별 노드 단위의 미세한 제어는 어렵다는 한계가 있다.

#### 런치 파일을 통한 설정

ROS2에서는 복수의 노드를 런치 파일로 한 번에 구동할 수 있다. 이때 특정 노드에만 다른 로그 레벨을 부여하려면, 런치 파일에서 `launch.actions.Node`에 대한 설정을 추가한다.

예를 들어, Python 런치 파일에서 다음과 같이 설정할 수 있다.

```python
from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='my_package',
            executable='my_node_executable',
            output='screen',
            name='my_node',
            arguments=['--ros-args', '--log-level', 'DEBUG']
        ),
        Node(
            package='another_package',
            executable='another_node_executable',
            output='screen',
            name='another_node',
            arguments=['--ros-args', '--log-level', 'WARN']
        )
    ])
```

이렇게 작성하면, `my_node`의 로깅 레벨은 `DEBUG`, `another_node`의 로깅 레벨은 `WARN`으로 각각 설정된다.

#### 별도의 로거 생성 및 사용

ROS2는 기본적으로 노드 이름과 동일한 로거 이름을 사용하도록 되어 있다. 그러나 특정 상황에서는 노드 로거와는 별도로 추가 로거를 생성하여, 좀 더 세부적이고 목적에 맞게 로그를 남기고 싶을 수 있다. 예를 들어, 네트워크 통신과 센서 처리를 같은 노드에서 수행하지만, 각각 다른 로거를 사용하여 로그 레벨을 독립적으로 관리하고 싶을 수 있다.

```cpp
#include <rclcpp/rclcpp.hpp>

class MyNode : public rclcpp::Node
{
public:
  MyNode() : Node("my_node")
  {
    // 노드의 로거와 별도로, 로거명을 지정해 새로운 로거를 생성
    auto custom_logger = rclcpp::get_logger("network_logger");

    RCLCPP_INFO(this->get_logger(), "기본 노드 로거 시작");
    RCLCPP_INFO(custom_logger, "네트워크 로거 시작");

    // 별도 로거에 대한 DEBUG 로그 호출
    RCLCPP_DEBUG(custom_logger, "패킷 전송: 데이터 사이즈 1024바이트");
  }
};

int main(int argc, char** argv)
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<MyNode>());
  rclcpp::shutdown();
  return 0;
}
```

* `rclcpp::get_logger("로거이름")` 함수를 사용하면, 임의의 이름을 갖는 로거 인스턴스를 생성할 수 있다.
* 이렇게 로거를 분리해두면, 특정 로거만 별도로 로깅 레벨을 조절하거나, 특정 로거의 로그만 필터링하여 저장할 수도 있다.

#### 로깅 메시지 포맷 지정

ROS2에서 기본적으로 출력되는 로그 메시지 포맷은 다음과 같다.

```
[DEBUG] [timestamp] [logger_name]: 메시지
```

이 포맷을 바꾸고 싶다면 ROS2가 내부적으로 사용하는 `rcutils` 또는 `rcl_logging_spdlog` 등의 설정을 수정해야 한다. 환경 변수를 사용하거나, 별도의 파라미터를 통해 사용자 정의 로그 포맷을 적용할 수 있는 방법이 있다.

예를 들어, `rcl_logging_spdlog`를 사용하는 경우 환경 변수 `RCUTILS_LOGGING_USE_STDOUT` 등을 활용하거나, `RCUTILS_CONSOLE_OUTPUT_FORMAT`으로 직접 포맷을 지정할 수 있다. 자세한 문법은 ROS2 문서나 `rcl_logging_spdlog` 구현체를 참고해야 한다.

```bash
export RCUTILS_CONSOLE_OUTPUT_FORMAT="[{severity}] [{time}] [{name}] -> {message}"
```

이렇게 설정하면, 로그 메시지가 다음과 같은 형태로 출력될 수 있다.

```
[DEBUG] [1640995200.123456789] [my_node] -> 패킷 전송: 데이터 사이즈 1024바이트
```

#### 파일 출력 설정

디버깅 시에는 터미널에서만 로그가 출력되는 것으로는 한계가 있고, 로그를 파일로 기록해두면 추후 분석이 더욱 용이하다. 이를 위해 ROS2는 표준 출력 대신 파일에 로그를 남길 수 있는 설정을 제공한다.

**커맨드라인에서 파일로 리다이렉트**:

* 가장 간단한 방법은 표준 출력 자체를 파일로 리다이렉트하는 것이다.

```bash
ros2 run my_package my_node_executable --ros-args --log-level DEBUG > debug_log.txt 2>&1
```

* 이 경우 모든 표준 출력 및 표준 에러가 `debug_log.txt`에 기록된다.

**`RCUTILS_LOGGING_BUFFERED_STREAM` 등 활용**:

* ROS2의 로깅 백엔드는 다양한 환경 변수를 제공하므로, 이를 통해 메시지를 특정 파일에 기록하는 방법도 있다.
* 다만, ROS2 버전별로 설정 방법이 조금씩 다를 수 있으므로, 사용 중인 ROS2 버전(예: Humble, Iron)에 맞는 설정을 확인해야 한다.

**별도 로거 라이브러리 연동**:

* `log4cxx`, `spdlog` 등 다른 로거를 사용할 수 있다.
* 예를 들어 `rcl_logging_spdlog`를 사용하면, spdlog 형식으로 파일 로깅을 설정할 수 있다.

#### 동적 로깅 레벨 조절

ROS2에서는 노드를 실행한 이후에도, 동적으로 로깅 레벨을 바꿀 수 있는 기능을 갖추고 있다. 다만, 이를 위한 별도 파라미터 서버나 동적 파라미터(dynamic\_reconfigure 유사 기능)를 사용해야 할 수도 있다.

**파라미터를 이용한 로깅 레벨 변경**:

* 노드 안에서 로깅 레벨을 파라미터로 만들고, 해당 파라미터가 변경될 때 콜백에서 `rcutils_logging_set_logger_level()` 같은 함수를 호출해 갱신한다.
* 이를 통해 무거운 코드를 다시 빌드하지 않고도, 노드 실행 중에 레벨을 조정할 수 있다.

**rclcpp 라이브러리를 통한 로커 레벨 변경**:

```cpp
#include <rclcpp/logging.hpp>

// 특정 로거의 레벨을 동적으로 바꾼다.
rcutils_ret_t ret = rclcpp::Logger("network_logger").set_level(rclcpp::Logger::Level::Debug);
if (ret != RCUTILS_RET_OK) {
  // 에러 처리
}
```

* 이 방식은 ROS2 버전에 따라 지원 여부가 다를 수 있으므로, 사용하는 ROS2 릴리스 노트나 소스 코드를 확인해야 한다.

#### 로거별 출력 레벨 필터링

ROS2의 로깅 메커니즘을 활용하면, 여러 로거가 동시에 존재하더라도 각각의 로거에 대해 서로 다른 로깅 레벨을 설정할 수 있다. 이를 ‘로거별 출력 레벨 필터링’이라고 하며, 실무에서 대규모 시스템을 디버깅할 때 유용하다.

**로거 이름과 로깅 레벨을 구분한다**:

* 각 노드는 기본적으로 “노드이름”을 로거 이름으로 사용하지만, 필요에 따라 `rclcpp::get_logger("my_custom_logger")` 등을 이용해 추가 로거를 만든다.
* 여러 로거를 운용할 때, 로거 이름별로 레벨을 지정해두면 특정 분야(예: 네트워크, 센서, UI 등)만 선택적으로 로그를 남길 수 있다.

**커맨드라인에서 로거별 레벨 설정**:

* `ros2 run` 시에 `--ros-args --log-level my_custom_logger:=DEBUG` 와 같이 지정하면, `my_custom_logger` 로거만 DEBUG 레벨을 사용한다. 나머지 로거는 기본 레벨이 적용된다. 구문 예시:

```bash
ros2 run my_package my_node_executable \
  --ros-args \
  --log-level my_custom_logger:=DEBUG \
  --log-level my_other_logger:=WARN
```

* 이처럼 콜론 앞에는 로거 이름을 쓰고, 뒤에는 원하는 로깅 레벨을 명시한다.

**런치 파일에서 로거별 설정**:

* 런치 파일 내부에서도 “로거이름=레벨” 형태로 각 노드의 인자에 지정할 수 있다. 예시:

  ```python
  from launch import LaunchDescription
  from launch_ros.actions import Node

  def generate_launch_description():
      return LaunchDescription([
          Node(
              package='my_package',
              executable='my_node_executable',
              output='screen',
              name='my_node',
              arguments=[
                  '--ros-args',
                  '--log-level', 'my_custom_logger:=DEBUG',
                  '--log-level', 'my_other_logger:=WARN'
              ]
          )
      ])
  ```
* 이를 통해 하나의 노드 안에 존재하는 여러 로거를 개별적으로 제어 가능하다.

#### 로깅과 성능

로깅은 디버깅에 큰 도움이 되지만, 지나친 로깅은 성능 저하를 유발할 수 있다. 특히, DEBUG나 INFO 수준에서 많은 데이터를 출력할 때 주의해야 한다.

* **DEBUG 레벨 주의**
  * 센서 데이터를 매 주기마다 DEBUG로 찍으면, CPU 사용량이 크게 증가할 수 있다.
  * 데이터가 매우 빈번하게 생성되는 경우, 파일 입출력이나 터미널 출력이 병목이 될 수 있다.
* **비동기 로깅 사용 고려**
  * 로깅 라이브러리에 따라 비동기로 메시지를 큐에 쌓아두고, 별도의 스레드에서 출력하도록 하는 방식을 지원할 수 있다.
  * 이러한 기법을 사용하면, 로깅에 의한 메인 스레드의 성능 저하를 완화할 수 있다.
* **필수 로그와 선택 로그 분리**
  * 꼭 필요한 ERROR, WARN 로그만 남기고, 분석용 DEBUG/INFO 로그는 플래그나 파라미터를 통해 켜고 끌 수 있게 만드는 것이 권장된다.
  * 대규모 로봇 시스템에서는, 운영 단계(Production)와 디버깅 단계(Development)를 분리해, 운영 단계에서는 최소한의 로그만 남기는 식으로 설계하기도 한다.

#### 고급 활용: 로깅 메시지에 태그 부여

로거 이름 외에도, 로그 메시지 자체에 태그를 추가하여 메시지 타입을 세분화할 수 있다. 이를 위해 단순 문자열 가공 방식을 쓰거나, 원하는 포맷으로 메시지를 구성하면 된다.

```cpp
RCLCPP_INFO(
  this->get_logger(),
  "[NETWORK] [%s:%d] 수신된 패킷 사이즈: %d bytes",
  __FILE__, __LINE__, packet_size
);
```

* `[NETWORK]` 라는 태그를 붙여 해당 로그가 네트워크 관련임을 명시.
* `__FILE__`, `__LINE__` 매크로를 활용해 어느 파일, 몇 번째 줄에서 찍혔는지 기록.
* 이후 로그 분석기나 텍스트 필터(예: `grep`)를 사용해 태그나 파일명을 기준으로 로그를 분류할 수 있다.

이처럼 로깅 메시지를 유의미한 형태로 정리해두면, 수많은 로그가 발생해도 빠르게 필요한 정보를 검색하고 분석할 수 있다.

#### 로깅과 모니터링 툴 연동

* **ROS2 CLI 툴**
  * `ros2 node list`, `ros2 topic echo`, `ros2 service list` 등으로 노드나 토픽 정보를 조회하면서, 로그를 병행 확인해 디버깅 범위를 좁힐 수 있다.
* **RQt(ROS Qt) 플러그인**
  * RQt에서 지원하는 콘솔 출력 플러그인을 사용하면, GUI 상에서 로그 메시지를 확인하고 필터링할 수 있다.
* **프로메테우스(Prometheus), ELK(Elasticsearch, Logstash, Kibana)**
  * 대규모 시스템에서는 로봇에서 생성된 로그를 수집하고 시각화하기 위해, 일반 서버 모니터링 툴을 연동하기도 한다.
  * `logstash` 또는 `fluentd`를 사용하여 로깅 데이터를 중앙 서버로 전송하면, 다수의 로봇 로그를 한꺼번에 관리할 수 있다.
