# Launch 관련 디버깅 기법

ROS2에서 Launch 시스템은 여러 노드와 프로세스를 복합적으로 실행하고 제어할 수 있도록 설계되었다. 하지만 실제 프로젝트에서 Launch 파일을 작성하다 보면, 특정 노드가 실행되지 않는다거나, 파라미터가 잘못 전달되는 문제 등이 자주 발생한다. 이러한 문제의 원인을 파악하기 위해서는 Launch 시스템에 대한 충분한 이해와 함께, 다양한 디버깅 기법을 적절히 활용할 수 있어야 한다. 아래에서는 ROS2 Humble 기준으로 Launch 관련 디버깅 기법을 소개한다.

#### Launch 파일 구조 점검

ROS2의 Launch 파일은 Python 스크립트(.py) 기반 또는 XML(.launch.xml), YAML(.launch.yaml) 기반 등 여러 방식으로 작성할 수 있다. 특히 Python 기반 Launch 파일은 일반 파이썬 문법이 그대로 적용되므로, 오류가 발생했을 때 일반적인 파이썬 디버깅 기법을 활용할 수도 있다.

**문법 오류 확인**: Launch 파일이 Python 스크립트인 경우, 간단히 `python` 인터프리터로 문법을 확인해 볼 수 있다. 예:

```bash
python my_launch.py
```

만약 문법 오류가 있다면, 어느 라인에서 어떤 에러가 발생했는지 출력된다.

**테스트 실행**: Launch 파일을 작성하고 난 후, 바로 `$ ros2 launch my_package my_launch.py`로 테스트를 실행해서 크게 문제가 없는지 먼저 확인한다. 이때 오류 메시지가 터미널에 나타나면, 해당 메시지를 꼼꼼히 확인한다.

#### Launch 디버그 로깅 수준 조정

ROS2 Launch 내부에서 실행되는 로직이나 이벤트를 더 자세히 파악하기 위해 로깅 수준(logging level)을 조정할 수 있다. ROS2에서 사용하는 로깅 매커니즘은 rclcpp/rclpy가 제공하는 로깅 인터페이스를 기반으로 하며, Launch 시스템에서도 이를 받아서 활용한다.

**launch\_ros.actions.Node의 로깅 옵션**: Python 기반 Launch 파일에서 노드를 실행할 때, 다음과 같이 로깅 옵션을 설정할 수 있다.

```python
from launch_ros.actions import Node

node_example = Node(
    package='my_package',
    executable='my_node',
    name='my_node_name',
    output='screen',
    arguments=['--ros-args', '--log-level', 'DEBUG']
)
```

위와 같이 `'--ros-args', '--log-level', 'DEBUG'`를 인자로 추가하면, 해당 노드가 DEBUG 수준의 메시지도 출력하도록 로거를 설정한다.

**Launch 시스템 자체 로깅**: Launch 시스템의 내부 상태를 좀 더 자세히 확인하고 싶다면, LaunchService 초기화 시점에 로깅 수준을 설정할 수도 있다. 예시:

```python
import launch
import launch.logging
from launch_ros.utilities import evaluate_parameters

def generate_launch_description():
    launch.logging.get_logger().set_level(launch.logging.LoggingLevel.DEBUG)
    # 이 아래에 launch description 정의 ...
    return launch.LaunchDescription([...])
```

이렇게 하면 Launch 단계에서 DEBUG 메시지까지 볼 수 있다.

#### LaunchIntrospector를 통한 구조 확인

ROS2의 Launch API에는 LaunchIntrospector라는 기능이 있어서, LaunchDescription 객체에 정의된 액션들을 문자열 형태로 “어떻게 실행될 예정인지” 시각적으로 확인할 수 있다. 이 기능을 이용하면 어떤 노드가 먼저 실행되는지, 혹은 어떤 조건에서 실행되는지 등을 디버깅하기에 편리하다. 예시:

```python
import launch
from launch.actions import LogInfo
from launch_ros.actions import Node
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.launch_introspector import LaunchIntrospector

def generate_launch_description():
    ld = launch.LaunchDescription()
    ld.add_action(LogInfo(msg='Launching example'))
    ld.add_action(Node(
        package='demo_nodes_cpp',
        executable='talker',
        name='talker_node'
    ))
    # LaunchDescription 인스턴스 끝에 액션들이 정의됨
    return ld

if __name__ == '__main__':
    ld = generate_launch_description()
    print(launch.LaunchIntrospector().format_launch_description(ld))
```

위 코드처럼 Launch 파일을 직접 실행하면, LaunchDescription 객체 내부의 계층구조 정보를 터미널에서 확인할 수 있다.

#### Launch 이벤트 핸들러(LogEventHandler, OnProcessExit 등) 활용

ROS2 Launch에서는 특정 이벤트(예: 프로세스 종료, 파라미터 설정 완료, 토픽 메시지 수신 등)가 발생했을 때 호출되는 다양한 이벤트 핸들러를 제공한다. 이를 활용하여 디버깅 정보를 로그로 찍게 하거나, 디버거를 연결하는 등 여러 기법을 적용할 수 있다.

**`OnProcessExit` 예시**: 특정 노드가 종료되는 시점에 로그를 찍고자 할 때:

```python
from launch.actions import RegisterEventHandler
from launch.event_handlers import OnProcessExit
from launch.actions import LogInfo

exit_event_handler = RegisterEventHandler(
    event_handler=OnProcessExit(
        target_action=node_example,
        on_exit=[LogInfo(msg='Node has exited.')]
    )
)
```

위와 같이 하면, `node_example`이 종료되는 순간에 “Node has exited.” 메시지가 출력된다. 예상치 못한 종료가 왜 일어나는지 상황을 파악하기 위해 간단한 디버깅 로깅을 삽입할 수 있다.

#### 파라미터 전달 경로 디버깅

ROS2 Launch에서 노드에 파라미터를 전달하는 흐름은 여러 단계를 거치므로, 어디에서 파라미터가 누락되거나 잘못 전달되는지 확인해야 할 때가 많다.

**ROS2 Launch에서 파라미터 전달 방식**:

1. 명시적인 파라미터 파일(.yaml)로부터 로드
2. Python 스크립트 내에서 `Node(parameters=[...])` 형태로 직접 전달
3. CLI 인자로 `--ros-args --params-file` 등을 통해 전달

각 방법이 혼합될 때, 실제 노드가 최종적으로 어떤 파라미터를 받는지 확인하는 것이 중요하다.

**파라미터 로깅**:

Launch 파일에서 파라미터를 불러오는 과정에 문제가 없는지 점검하기 위해, 파라미터를 로드한 직후 아래와 같이 확인 메시지를 찍어 보는 방법이 있다:

```python
import yaml

with open('config/my_params.yaml', 'r') as f:
    params_data = yaml.safe_load(f)
print(params_data)
```

이렇게 하면 Launch가 실행되기 전에 파라미터 파일을 제대로 읽었는지, 어떤 내용이 들어있는지 알 수 있다.

#### 파이썬 디버거(pdb) 활용

ROS2 Launch는 Python 스크립트로 작성되기 때문에, 파이썬 기본 디버거인 pdb 모듈을 이용해 Launch 파일 내부 로직을 추적할 수 있다. 노드를 생성하기 직전이나 파라미터를 불러오는 지점에 브레이크포인트를 걸어 두고, 실제 변수 값이나 실행 흐름을 확인하면 디버깅에 큰 도움이 된다. 예시 코드:

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

def generate_launch_description():
    # 브레이크포인트 설정
    pdb.set_trace()
    
    # LaunchDescription 생성
    ld = launch.LaunchDescription()
    
    # Node 액션 추가
    node_example = Node(
        package='my_package',
        executable='my_node',
        name='my_node_name',
        output='screen'
    )
    ld.add_action(node_example)
    
    return ld
```

위 코드에서 `$ ros2 launch my_package my_launch.py` 명령을 실행하면, pdb 디버거가 첫 줄부터 동작하지는 않는다. Launch 시스템이 실행될 때, `generate_launch_description()` 함수가 호출되는 과정에서 `pdb.set_trace()`가 발동하여 디버거 셸로 진입하게 된다. 이 상태에서 함수 내에서 사용되는 로컬 변수, 파라미터 파일 로딩 등 로직을 단계별로 확인 가능하다.

#### GDB, LLDB 등의 시스템 디버거 활용

C++로 구현된 노드를 디버깅해야 할 때는, Launch 파일에서 직접 GDB나 LLDB를 호출하도록 구성할 수 있다. 이 경우 노드 프로세스를 디버거가 감싸도록 하여, 런치 실행 시에 자동으로 디버깅 세션을 시작하도록 할 수 있다.

**GDB 예시:**

```python
from launch.actions import ExecuteProcess

def generate_launch_description():
    launch_gdb = ExecuteProcess(
        cmd=[
            'gdb',
            '--ex=run',
            '--args',
            'path/to/my_executable'
        ],
        output='screen'
    )
    return launch.LaunchDescription([
        launch_gdb
    ])
```

위 예시처럼 `ExecuteProcess` 액션을 사용하여 명령어(cmd) 목록을 직접 넘길 수 있다. `--ex=run` 옵션으로 GDB를 실행한 뒤 자동으로 `run` 명령을 수행하게 하였으며, `my_executable`이 실제 C++ 노드의 경로 및 인자로 바뀌어야 한다. 실제로는 `gdb`를 실행한 후에 런치 시스템이 이어서 노드를 구동하게 하는 방식을 채택할 수도 있다. 이를 위해서는 노드를 Node 액션으로 실행하기보다, GDB 프로세스가 Node를 감싸서 동작하도록 구성해야 한다.

**LLDB 예시**: macOS나 LLDB를 선호하는 환경에서는 유사하게 `lldb` 명령을 호출하도록 코드를 작성하면 된다.

```python
from launch.actions import ExecuteProcess

def generate_launch_description():
    launch_lldb = ExecuteProcess(
        cmd=[
            'lldb',
            '--',
            'path/to/my_executable'
        ],
        output='screen'
    )
    return launch.LaunchDescription([
        launch_lldb
    ])
```

LLDB에서도 실행 후 `run` 명령을 쳐야 프로세스가 시작된다. 위 예시에서는 기본적으로 LLDB 인터랙티브 모드로 들어간 뒤, 사용자가 수동으로 `run`을 입력해 디버깅을 진행하게 된다.

#### Launch 조건 분기(IfCondition, UnlessCondition) 활용

디버깅 과정에서, 노드를 특정 조건 하에서만 실행하고 싶거나, 여러 버전의 노드를 교차 테스트해야 할 때가 있다. 이때 Launch에서 제공하는 `IfCondition`, `UnlessCondition` 등을 활용하면, 같은 Launch 파일 내에서 상황에 따라 다른 노드를 실행하거나 파라미터를 달리 적용할 수 있다.

**예시 코드**:

```python
import launch
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch.conditions import IfCondition
from launch_ros.actions import Node

def generate_launch_description():
    debug_arg = DeclareLaunchArgument(
        'debug_mode',
        default_value='False',
        description='Enable debug node if True'
    )

    debug_condition = IfCondition(LaunchConfiguration('debug_mode'))
    
    debug_node = Node(
        package='my_debug_package',
        executable='my_debug_node',
        name='my_debug_node',
        output='screen',
        condition=debug_condition
    )

    normal_node = Node(
        package='my_normal_package',
        executable='my_node',
        name='my_node',
        output='screen'
    )

    return launch.LaunchDescription([
        debug_arg,
        debug_node,
        normal_node
    ])
```

위 코드는 Launch 파일 실행 시에 `debug_mode:=True` 인자를 넘길 경우에만 `my_debug_node`가 실행된다. 디버깅을 위한 특정 노드나 상태를 Launch 파일에 조건부로 넣어두면, 상황에 따라 디버그 모드를 켜고 끄며 실험할 수 있다.

#### Launch 시스템 환경 변수, 서브스티튜션(Substitution) 디버깅

Launch 파일에서 환경 변수나 `PathJoinSubstitution`, `EnvironmentVariable`, `Command` 등의 서브스티튜션 기능을 활용하여, 동적으로 경로나 명령을 결정하기도 한다. 이 과정에서 실제로 무엇이 전달되는지 확인하려면, Substitution 결과를 직접 출력하거나, Launch 파일에서 `print()`로 검사해야 한다.

**Substitution 예시**:

```python
from launch.substitutions import PathJoinSubstitution, ThisLaunchFileDir
from launch.actions import LogInfo

def generate_launch_description():
    config_path = PathJoinSubstitution([ThisLaunchFileDir(), 'config', 'test.yaml'])
    
    # Substitution 결과를 확인하기 위해 로그 출력
    debug_log = LogInfo(msg=['Config file path: ', config_path])
    
    return launch.LaunchDescription([
        debug_log,
        # 이후 Node 등의 액션에서 config_path 사용
    ])
```

위처럼 `LogInfo` 액션을 이용하면 Launch 프로세스가 시작될 때, 최종적으로 어떤 문자열이 완성되었는지 알 수 있다. 만약 서브스티튜션이 예상과 다르게 동작한다면, `print()`로도 값을 찍어 보거나, LaunchIntrospector를 활용하여 LaunchDescription 전체 구조를 확인해 볼 수 있다.

#### 복잡한 Launch 구성에서의 디버깅 전략

대규모 ROS2 프로젝트의 Launch 파일은 여러 서브 Launch 파일을 포함하고, 파라미터 파일 역시 여러 층위에서 불러올 수 있다. 이때 디버깅 효율을 높이려면, 전체 Launch 흐름을 논리적으로 분할하고, 단계별로 실제로 동작하는 부분만 테스트하는 전략이 필요하다.

**단일 Launch 파일로 단순화**:여러 서브 Launch 파일이 얽혀 있으면 디버깅이 어렵다. 우선 특정 의심 구간을 단일 Launch 파일로 독립시켜 최소 예제(minimal example)를 만들고, 문제가 재현되는지 확인한다. 최소 예제를 통해 원인을 찾은 다음, 전체 시스템에 다시 통합한다.

**서브 Launch 파일 단계적 실행**: 서브 Launch 파일 각각을 단독으로 `$ ros2 launch` 명령으로 실행해 보며 문제 여부를 점검한다. 예:

```bash
ros2 launch my_package sub_launch_1.py
ros2 launch my_package sub_launch_2.py
```

각각 문제가 없는 것을 확인한 뒤, 최종적으로 상위 Launch 파일에서 이들을 include하는 구조로 합쳐 본다.

**IncludeLaunchDescription 내부 로깅**: Python 기반 Launch 파일로 다른 Python Launch 파일을 include할 때, include되는 서브 Launch 파일 내부에서 `print()`나 `LogInfo()`를 활용해 어떤 액션들이 추가되는지 기록해 두면, 실제로 상위 Launch에서 어떤 노드들이 실행되는지 시각적으로 확인할 수 있다.

#### 멀티 머신 디버깅

ROS2 Humble에서 멀티 머신(네트워크 분산) 환경을 구성할 때는, Launch 파일 하나로 여러 머신에서의 노드 실행을 제어할 수도 있다. 그러나 네트워크, DDS 설정, 파라미터 동기화 등 많은 요소가 얽혀 있으므로 디버깅이 더욱 복잡해진다.

**멀티 머신 환경 변수 점검**: 노드가 서로 다른 머신에서 통신할 때, `$ROS_DOMAIN_ID`, `$ROS_LOCALHOST_ONLY`, DDS 관련 설정 등이 일관성 있게 맞춰져 있는지 확인한다. Launch 파일을 통해 직접 환경 변수를 세팅하기도 하므로, 각 머신별 Launch 파일 혹은 쉘 스크립트에서 설정이 충돌되지 않는지 살펴야 한다.

**네트워크 트래픽 모니터링**: 특정 머신에서 노드가 구동되지 않거나 통신이 되지 않는다면, `tcpdump`나 Wireshark 같은 패킷 분석 도구를 이용해 네트워크 트래픽이 실제로 오가는지 확인한다.

**멀티 머신 Launch 디버그 예시**: 하나의 메인 Launch 파일에서 `ssh`를 통해 원격 머신에서 노드를 실행시키는 방식을 가정해 보자. 이런 경우에는 원격 머신에서 실행되는 노드의 로그가 제대로 전달되는지, SSH 연결이 정상인지 등을 추가로 확인해야 한다.

#### Launch 파일 버전 관리

여러 개발자가 동시에 작업하는 프로젝트에서는, Launch 파일이 자주 변경되므로 문제 발생 시점 추적이 곤란해진다. 이를 방지하려면 Launch 파일을 포함한 모든 설정 파일을 버전 관리 시스템(Git 등)으로 꼼꼼히 관리하고, 커밋 로그에 변경 의도를 남기는 습관이 중요하다.

* **Git blame / Git bisect** 언제부터 Launch 파일이 깨졌는지 모를 때는 `git blame`, `git bisect` 등을 활용해 특정 시점으로 거슬러 올라가 디버깅할 수 있다.
* **작업 브랜치 분리** 디버깅을 위해 Launch 파일을 대폭 수정해야 한다면, 별도 브랜치에서 실험하고, 문제를 해결한 뒤에만 메인 브랜치에 병합하도록 한다.

#### 정적 검사 도구 활용

ROS2 Launch 파일 전용 정적 검사 도구는 많지 않지만, Python 문법에 대해 일반적인 정적 검사(Flake8, PyLint 등)를 적용할 수 있다. 또한 `.xml` 또는 `.yaml` 기반의 Launch 파일은 XML, YAML 전용Lint 도구로 문법 오류를 사전에 찾아낼 수 있다.

**Python 기반 Launch 정적 검사**:

```bash
flake8 my_launch.py
pylint my_launch.py
```

일반 파이썬 코드로서 포맷팅 오류나 명백한 문법 오류 등을 선제적으로 잡아낼 수 있다.

**XML Launch 파일 검사**: XML Lint 도구(예: `xmllint`)를 활용해 형식을 점검할 수 있다.

```bash
xmllint --noout my_launch.xml
```

문법 오류가 있으면 오류 메시지를 출력한다.

**YAML Launch 파일 검사**: YAML Lint 도구(예: `yamllint`)를 사용하여 포맷 오류와 스타일 위반 등을 잡아낼 수 있다.

```bash
yamllint my_launch.yaml
```

#### UI 기반 Launch 디버깅 툴 (Foxglove, rqt 등)

ROS2에는 Foxglove Studio(외부 툴)나 rqt 플러그인을 통해 토픽, 파라미터 등을 시각적으로 모니터링할 수 있다. Launch 파일 디버깅 자체를 위한 전용 UI 툴은 아직 활성화되지 않았으나, 노드 상태나 파라미터 설정 상태를 UI로 빠르게 확인할 수 있다면 문제 원인을 찾는 데 도움이 된다.

**ROS2 노드 상태 시각화**: rqt\_graph를 실행해 노드 간 토픽 연결이 정상인지 확인한다.

```bash
ros2 run rqt_graph rqt_graph
```

Launch로 구동된 여러 노드가 의도한 대로 연결되어 있는지, 혹은 누락된 노드가 있는지 시각적으로 확인할 수 있다.

**파라미터 동적 변경 툴**:rqt를 통해 노드 파라미터를 실시간 변경하고 테스트할 수도 있다.

```bash
ros2 run rqt_reconfigure rqt_reconfigure
```

만약 Launch 파일에서 파라미터가 의도대로 전달되지 않았다면, 여기서 동적으로 값을 설정하여 문제를 좁혀갈 수 있다.

#### Launch 디버깅 절차 예시

다음은 일반적인 Launch 디버깅 흐름을 간단히 다이어그램으로 나타낸 예시이다:

{% @mermaid/diagram content="flowchart TB
A\[Launch 파일 수정/작성] --> B{테스트 실행<br>ros2 launch}
B --> C{오류 메시지 체크<br>혹은 노드 동작 확인}
C -- 문제가 있음 --> D\[Launch 로깅 수준, <br> LaunchIntrospector, <br> pdb 활용]
D --> E{노드 파라미터,<br>서브스티튜션<br>정상 여부 점검}
E -- 해결됨 --> F\[이슈 트래킹에 정리 후<br>버전 관리 반영]
C -- 문제가 없음 --> G\[추가 기능 구현<br>or 다음 단계로 이동]" %}
