# 로그 메시지 포맷, 필터링 및 스토리지 기법

#### ROS2 로그 메시지 계층 구조

ROS2에서 로그 메시지는 크게 다음과 같은 모듈을 통해 정의되고 전달된다.

* **rclcpp**: C++ 애플리케이션 레벨에서의 로깅 인터페이스
* **rclpy**: Python 애플리케이션 레벨에서의 로깅 인터페이스
* **rcutils**: 최하위 레벨의 공통 유틸리티 레이어로서, 실제 로그 포맷, 시간 스탬프, 로그 레벨 정의 등을 제공

각 레벨(C++/Python)에서 제공되는 로깅 API는 내부적으로 $rcutils$에 정의된 함수를 호출하는 구조를 이룬다. 이러한 계층 구조는 특정 실행 환경에서 로그 백엔드가 달라지더라도, 상위 레벨 코드의 변경 없이 로그 메시지를 일관적으로 처리할 수 있도록 한다.

#### 로그 레벨과 사용 방법

ROS2는 전통적인 로깅 레벨(TRACE, DEBUG, INFO, WARN, ERROR, FATAL)을 사용한다. 코드에서 로그를 남길 때는 아래와 같은 매크로(C++)나 함수(Python)를 활용한다.

```cpp
// C++ 예시 (rclcpp)
RCLCPP_INFO(node->get_logger(), "이것은 INFO 레벨 로그이다.");
RCLCPP_DEBUG(node->get_logger(), "디버그 정보를 출력한다. 변수 값: %d", value);
RCLCPP_ERROR(node->get_logger(), "에러가 발생하였다!");
# Python 예시 (rclpy)
self.get_logger().info('이것은 INFO 레벨 로그이다.')
self.get_logger().debug('디버그 정보를 출력한다. 변수 값: %d' % value)
self.get_logger().error('에러가 발생하였다!')
```

이때 각 로그 레벨에 따라 로그가 실제로 출력되거나 필터링될 수 있다. 예를 들어 ROS2 실행 시 아래와 같이 `$ROS_LOG_LEVEL` 환경 변수를 설정하거나, 파라미터 서버 등을 통해 동적으로 로그 레벨을 바꿀 수 있다.

```bash
export ROS_LOG_LEVEL=DEBUG
```

#### 로그 메시지 포맷

ROS2의 로그 메시지는 기본적으로 다음과 같은 요소로 구성된다.

* **타임 스탬프**: 메시지가 기록된 시간 정보
* **로깅 레벨**: DEBUG, INFO, WARN, ERROR, FATAL 등
* **노드 이름**: 로그를 발생시킨 노드의 식별자
* **소스 위치 정보**(선택적): 소스 코드 파일명, 라인 번호 등 (추적 목적으로 사용)
* **실제 로그 내용**: 사용자가 기록하고자 하는 문자열

기본적으로 ROS2는 다음과 같은 형태(예시)로 로그 메시지를 출력한다.

```
[DEBUG] [1658926731.123456789] [my_node_name]: 디버그 메시지
```

보다 복잡한 포맷을 지정하고 싶은 경우, `rcutils_logging_set_output_format()` 함수를 사용하거나, rclcpp/rclpy에서 특정 포맷 옵션을 변경하여 커스터마이징할 수 있다.

#### 수식과 로그 상관관계 예시

로그 과정에서 신호 처리, 행렬 연산 등과 같이 수학적 모델을 기반으로 디버깅해야 하는 경우도 있다. 예를 들어 $\mathbf{A} \in \mathbb{R}^{n \times n}$ 행렬에 대해 고유값(Eigenvalue) 계산을 수행하고, 그 결과를 로그로 남긴다고 하자. 간단히 표현하면 아래와 같은 수식이 가능하다.

$$
\lambda \text{는 } \mathbf{A}\mathbf{v} = \lambda \mathbf{v} \text{를 만족하는 고유값}
$$

해당 값을 로그로 남기고, 이를 통해 시스템 동작을 실시간으로 검증하거나, 오프라인에서 재분석할 때 유용하게 활용할 수 있다.

#### 로그 필터링 기법

로그가 많아지면, 성능 저하나 디스크 사용량 증가를 야기할 수 있다. 이러한 문제를 줄이기 위해 ROS2는 다음과 같은 로그 필터링 방식을 제공한다.

1. **환경 변수 또는 파라미터를 통한 레벨 기반 필터링**
   * `$ROS_LOG_LEVEL` 환경 변수를 설정하거나, 명령줄 인자를 통해 특정 노드/패키지의 로그 레벨을 조정한다.
   * 예: DEBUG 이하 로그만 필요하다면 INFO, WARN, ERROR 레벨 로그만 필터링해서 보거나 저장할 수도 있다.
2. **로거 이름별 개별 설정**
   * 동일한 노드 내부에서도 여러 로거를 사용하여 부분적으로 로그 레벨을 다르게 지정할 수 있다. 예를 들어 하드웨어 제어 부문은 DEBUG, UI 부문은 WARN 수준으로 설정할 수 있다.
3. **동적 리콘피규레이션(Dynamic Reconfigure) 또는 파라미터 서버 사용**
   * 런타임 시 파라미터를 바꿔서 로그 레벨을 변경한다. 예: `rclcpp::Logger logger = rclcpp::get_logger("my_logger").set_level(rclcpp::Logger::Level::Debug)`

이와 같이 로그 필터링을 효과적으로 활용하면, 디버깅 때만 상세 로그를 활성화하고 정상 운용 시에는 간단한 로그만 보이도록 구성할 수 있다.

#### 로그 저장 기법 및 전략

ROS2 환경에서 생성되는 로그는 주로 콘솔 출력이나 파일, 혹은 특정 로깅 백엔드(예: syslog, ELK Stack 등)를 통해 저장할 수 있다. 로그가 어디에 기록되느냐는 로깅 라이브러리 설정과 환경 변수에 의해 결정되며, 운용 환경에 맞추어 설계할 수 있다.

1. **콘솔 로그(표준 출력)**
   * 개발 편의를 위해 가장 일반적으로 쓰이는 형태이며, 즉각적인 피드백을 얻을 수 있다.
   * 로깅 레벨별 컬러링이 적용될 수 있어 가시성이 좋다.
   * 단, 시스템이 무정지로 오래 동작해야 하는 환경에서는 콘솔 로그가 너무 많이 쌓일 경우 성능에 부담을 줄 수 있다.
2. **파일 로깅**
   * 장기 보관 및 추후 분석을 위해 로그를 파일로 저장할 수 있다.
   * 파일이 일정 크기를 넘으면 회전(rotate)시키거나 압축하여 저장 공간을 절약하는 전략이 일반적이다.
   * 예: “log\_20241230\_123045.txt”와 같이 날짜-시간 기반 파일명을 사용해 주기적으로 로그 파일을 생성한다.
3. **원격 로깅 및 로그 집계 시스템**
   * ELK Stack(Elasticsearch, Logstash, Kibana), Graylog 등과 연동하여 분산 환경에서 중앙집중형 로그 수집 및 검색이 가능하다.
   * 대규모 로봇 시스템이나 클라우드 로보틱스 환경에서는 필수적인 구성 요소가 될 수 있다.
4. **syslog**
   * 리눅스 환경에서 기본적으로 제공되는 syslog 데몬을 통해 로그를 수집, 필터링, 전달한다.
   * 기존 서버 인프라와의 호환성이 높아 엔터프라이즈 시스템과 통합하기 쉽다.

#### 로그 회전(로테이션) 기법

장시간 동작하는 노드에서 로그가 무한정 쌓이면 디스크를 가득 채우는 문제가 발생할 수 있다. 이를 막기 위해 다음과 같은 로테이션(회전) 기법을 사용한다.

1. **파일 크기 기반 회전**
   * 예: 각 로그 파일이 100MB를 넘으면 자동으로 새로운 로그 파일을 만들고 구 로그 파일을 압축하거나 삭제한다.
2. **시간 기반 회전**
   * 예: 1시간마다 로그 파일을 롤오버(rollover)하여 “log\_20241230\_13xxxx.txt”와 같이 시간 스탬프를 포함해 생성한다.
3. **최대 개수 제한**
   * 일정 개수의 로그 파일만 유지하고, 가장 오래된 로그 파일부터 삭제하거나 압축한다.

이러한 회전 방식을 적절히 혼합하여 운영 환경에 맞는 로그 보관 정책을 수립할 수 있다.

#### 로그 메타데이터 관리

로그 분석 시, 단순히 메시지 내용 외에도 로그가 어느 노드에서, 어떤 스레드에서, 어떤 시간에 발생했는지 등 여러 메타데이터가 필요하다. ROS2에서 제공하는 기본 메타데이터는 다음과 같다.

* **타임 스탬프** (초 단위, 나노초 단위 등 세분화 가능)
* **노드 이름** 또는 **로거 이름**
* **로깅 레벨**
* **파일명, 라인 번호** (선택적)
* **함수 이름** (선택적)

필요에 따라 추가적인 메타데이터(예: 메시지 시퀀스 번호, 특정 토픽명, TF 프레임 등)를 로그 문자열에 포함시킬 수도 있다. 이를 통해, 디버깅 시점에 더 풍부한 정보를 활용할 수 있다.

#### 고급 로그 필터링 기법

대규모 노드에서 로그가 넘쳐날 때, 간단한 레벨 필터링만으로는 부족할 수 있다. 로거를 세분화하거나 확장 필터링 기법을 사용하면 유연하게 로그를 다룰 수 있다.

1. **스레드/프로세스별 필터링**
   * 멀티 스레드 환경에서 스레드 ID를 기반으로 로그 레벨을 다르게 설정하거나, 특정 스레드에서만 디버그 로그를 활성화하여 성능 부담을 줄일 수 있다.
2. **메시지 내용 기반 필터링**
   * 예: “SensorID=3”이라는 메시지가 들어간 로그만 추출
   * rclcpp/rclpy 자체에는 기본적으로 내용 기반 필터링 기능이 널리 제공되지 않으나, 로그 파이프라인에서 별도 파싱을 통해 구현 가능하다.
3. **토픽/핸들러 단위의 로그 레벨 제어**
   * 노드 내에서도 서로 다른 콜백 함수나 토픽에 대해 로그 레벨을 상이하게 설정하여, 중요도에 맞게 로그를 남길 수 있다.

#### 로그 파이프라인 구조

ROS2 노드에서 생성된 로그 메시지가 실제로 콘솔이나 파일, 원격 서버 등에 기록되기까지는 내부적으로 여러 단계를 거친다. 이를 시각적으로 표현하면 다음과 같다:

{% @mermaid/diagram content="flowchart LR
A\[ROS2 Node] --> B\["로깅 API<br>(rclcpp / rclpy)"]
B --> C\[rcutils Logging]
C --> D\["Console<br>(stdout)"]
C --> E\[File Logging]
C --> F\["syslog / Remote Logging<br>(ELK, Graylog 등)"]" %}

1. **ROS2 Node**: 사용자 코드에서 로그 함수(예: `RCLCPP_INFO`)를 호출한다.
2. **로깅 API (rclcpp / rclpy)**: C++ 및 Python 레벨에서 제공되는 로깅 함수를 통해 메시지와 로그 레벨 등이 전달된다.
3. **rcutils Logging**: ROS2 공통 유틸리티 레이어로, 실제 로그 포맷 구성, 레벨 필터링, 출력 경로 결정 등 로깅 핵심 로직이 구현되어 있다.
4. **Console / File / Remote**: 최종적으로 콘솔(표준 출력), 로컬 파일, 원격 로깅 시스템 등에 로그를 기록한다.

#### 퍼포먼스 고려사항

로깅은 필연적으로 성능 오버헤드를 유발한다. 특히 다량의 센서 데이터를 처리하거나, 실시간 성능이 중요한 로봇 시스템에서는 로그가 병목(bottleneck)이 되지 않도록 주의해야 한다.

1. **비동기 로깅**
   * 동기 로깅은 로그 함수가 호출될 때마다 I/O를 수행하므로, 자주 호출되는 경우 성능 저하가 뚜렷할 수 있다.
   * 비동기 로깅 라이브러리(spdlog 등)를 이용하여 로그를 버퍼에 모았다가 일정 시점에 일괄 기록함으로써, 메인 쓰레드의 부하를 줄일 수 있다.
2. **필터링으로 로그 양 줄이기**
   * DEBUG 레벨 로그는 많은 문자열 처리가 필요하므로, 실제로 필요하지 않다면 INFO 수준 이하만 남기는 것이 좋다.
   * 런타임 시 로그 레벨을 낮춰서 시스템의 부하를 제어하는 것도 한 방법이다.
3. **로거 구분**
   * 노드별 또는 기능별로 로거를 세분화하여, 부분적으로만 DEBUG 레벨을 활성화하도록 한다.
   * 트러블슈팅이 필요한 모듈에만 상세 로그를 활성화하고, 나머지는 최소화하면 성능 저하를 줄일 수 있다.

#### 도커(Container) 환경에서의 로깅

최근에는 로봇 소프트웨어 역시 도커 컨테이너 내에서 구동하는 경우가 많다. 이때 로깅을 어떻게 관리하느냐가 중요한 이슈가 될 수 있다.

1. **콘솔 출력 연계**
   * 컨테이너 내에서 stdout/stderr로 출력되는 로그를 호스트 머신의 도커 로깅 드라이버가 수집하여 파일, syslog, 혹은 다른 로깅 백엔드로 전송한다.
   * 도커를 실행할 때 로깅 드라이버와 옵션(`--log-driver`, `--log-opt`)을 설정할 수 있다.
2. **마운트된 파일 시스템 사용**
   * 호스트 시스템의 특정 디렉터리를 컨테이너 내부로 마운트하여, 컨테이너에서 생성된 로그 파일이 호스트 디렉터리에 직접 쌓이도록 할 수 있다.
   * 예:

     ```bash
     docker run -v /host/logs:/container/logs ...
     ```
3. **원격 로깅 시스템 연동**
   * 컨테이너 내부에 syslog 에이전트를 함께 설치해두고, 원격 로깅 서버로 전송하도록 설정할 수 있다.
   * 대규모 로보틱스 시스템에서 컨테이너 기반으로 노드를 배포하는 경우, 각 로거 설정이 일관되도록 이미지 빌드 시점부터 공통 설정을 적용하는 것이 좋다.

#### 오프라인 로그 분석

실시간 모니터링과 별개로, 파일로 쌓인 로그를 나중에 종합 분석하는 과정이 필요하다. 이를 통해 시스템 성능, 장애 발생 시점, 메시지 통계 등을 파악할 수 있다.

1. **텍스트 로그 파싱**
   * 간단하게 grep, awk, sed 같은 유닉스 도구를 사용하거나, 로그 분석용 Python 스크립트를 만들어 필요한 정보를 추출한다.
   * 시간 스탬프 기준으로 특정 구간의 로그만 모아서 보는 식의 분석이 자주 활용된다.
2. **DB나 Elasticsearch에 적재**
   * 로그가 많아지면 파일 기반 파싱이 어려우므로, NoSQL DB(예: Elasticsearch)에 적재해놓고 Kibana, Grafana 등 시각화 도구를 통해 조회한다.
   * 장애 지점, 에러 발생 패턴, WARN 이상 로그 빈도 등을 한눈에 파악할 수 있다.
3. **프로파일링 툴과 연계**
   * 프로파일링 툴(예: perf, valgrind, ros2\_tracing)에서 얻은 데이터와 로그를 동기화하여 분석할 수도 있다.
   * 특정 함수가 실행되는 시점에 발생한 로그를 매핑함으로써, 성능 병목 구간을 파악하거나 예외 상황 발생 원인을 추적한다.
