디버깅(Debugging)
디버깅(Debugging)
Elixir에서 코드를 디버깅하는 방법은 여럿이에요. 이번 장에서는 그중 비교적 자주 쓰는 방법 몇 가지를 살펴볼게요.
출처: Debugging
본문
IO.inspect/2
IO.inspect(item, opts \\\\ [])가 디버깅에서 정말 유용한 이유는, 원래 코드의 동작에 영향을 주지 않으면서 전달받은 item 인자를 그대로 반환하기 때문이에요. 예를 하나 볼게요.
(1..10)
|> IO.inspect()
|> Enum.map(fn x -> x * 2 end)
|> IO.inspect()
|> Enum.sum()
|> IO.inspect()
출력:
1..10
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
110
보시다시피 IO.inspect/2는 결과를 바꾸지 않고 코드의 거의 어느 곳에서든 값을 "엿볼" 수 있게 해 줘요. 그래서 위 예시처럼 파이프라인 중간에 끼워 넣기에 아주 좋죠.
IO.inspect/2는 label 옵션으로 출력에 장식도 할 수 있어요. 라벨은 inspect된 item 앞에 출력됩니다.
[1, 2, 3]
|> IO.inspect(label: "before")
|> Enum.map(&(&1 * 2))
|> IO.inspect(label: "after")
|> Enum.sum
출력:
before: [1, 2, 3]
after: [2, 4, 6]
IO.inspect/2를 binding/0과 함께 쓰는 것도 매우 흔해요. binding/0은 모든 변수 이름과 값을 반환하거든요.
def some_function(a, b, c) do
IO.inspect(binding())
...
end
some_function/3을 :foo, "bar", :baz로 호출하면 이렇게 출력돼요.
[a: :foo, b: "bar", c: :baz]
이 함수와 지원되는 모든 옵션에 대해 더 자세히 알고 싶다면 각각 IO.inspect/2와 Inspect.Opts를 참고하세요.
dbg/2
Elixir v1.14에서 dbg/2가 도입됐어요. dbg는 IO.inspect/2와 비슷하지만 디버깅에 특화되어 있어요. 전달된 값을 출력하고 반환한다는 점(IO.inspect/2와 같음)은 동일하지만, 코드와 위치도 함께 출력합니다.
# In my_file.exs
feature = %{name: :dbg, inspiration: "Rust"}
dbg(feature)
dbg(Map.put(feature, :in_version, "1.14.0"))
위 코드는 이렇게 출력해요.
[my_file.exs:2: (file)]
feature #=> %{inspiration: "Rust", name: :dbg}
[my_file.exs:3: (file)]
Map.put(feature, :in_version, "1.14.0") #=> %{in_version: "1.14.0", inspiration: "Rust", name: :dbg}
IO.inspect/2를 이야기할 때 |> 파이프라인의 단계 사이에 놓으면 유용하다고 했었죠. dbg는 더 잘해요. Elixir 코드를 이해하므로 파이프라인의 모든 단계에서 값을 출력해 줍니다.
# In dbg_pipes.exs
__ENV__.file
|> String.split("/", trim: true)
|> List.last()
|> File.exists?()
|> dbg()
이 코드는 이렇게 출력해요.
[dbg_pipes.exs:5: (file)]
__ENV__.file #=> "/home/myuser/dbg_pipes.exs"
|> String.split("/", trim: true) #=> ["home", "myuser", "dbg_pipes.exs"]
|> List.last() #=> "dbg_pipes.exs"
|> File.exists?() #=> true
dbg는 Elixir 구조에 대한 편의를 제공하지만, 디버깅하면서 코드를 실행하고 중단점(breakpoint)을 설정하려면 IEx가 필요해요.
Pry
IEx를 쓸 때 --dbg pry 옵션을 넘기면 dbg 호출이 있는 지점에서 코드 실행을 "멈출" 수 있어요.
$ iex --dbg pry
프로젝트 안에서 디버깅하려면 이렇게 합니다.
$ iex --dbg pry -S mix
이제 dbg를 호출할 때마다 기존 코드를 pry할지 물어봐요. 수락하면 해당 코드의 모든 변수, import, alias에 IEx에서 직접 접근할 수 있어요. 이걸 "prying"이라고 해요. pry 세션이 실행되는 동안에는 continue(또는 c)나 next(또는 n)를 호출할 때까지 코드 실행이 멈춰 있어요. iex -S mix TASK로 프로젝트 맥락에서 iex를 실행할 수 있다는 점도 항상 기억하세요.
중단점(Breakpoints)
dbg 호출은 디버깅하려는 코드를 수정해야 하고 단계별 실행(stepping) 기능도 제한적이에요. 다행히 IEx는 IEx.break!/2 함수를 제공해서, 소스를 수정하지 않고도 어떤 Elixir 코드든 중단점을 설정하고 관리할 수 있어요.
dbg와 비슷하게, 일단 중단점에 도달하면 continue(또는 c)나 next(또는 n)를 호출할 때까지 코드 실행이 멈춰요. 중단점은 기본적으로 줄 단위로 이동할 수 있지만, 컴파일된 모듈에 중단점을 설정하면 alias와 import에는 접근할 수 없어요.
mix test 태스크는 -b/--breakpoints 플래그로 중단점과 직접 통합됩니다. 이 플래그를 쓰면 실행될 모든 테스트의 시작 부분에 중단점이 설정돼요.
실제로 쓸 수 있는 명령 몇 가지를 보여드릴게요.
# Debug all failed tests
$ iex -S mix test --breakpoints --failed
# Debug the test at the given file:line
$ iex -S mix test -b path/to/file:line
Observer
복잡한 시스템을 디버깅할 때는 코드 단위로 뛰어다니는 것만으로는 부족해요. 전체 가상 머신, 프로세스, 애플리케이션을 이해하고 추적(tracing) 메커니즘을 설정하는 게 필요해요. 다행히 Erlang의 :observer로 이걸 할 수 있습니다. 애플리케이션에서:
$ iex
iex> :observer.start()
누락된 의존성
iex -S mix로 프로젝트 안에서 iex를 실행하면 observer가 의존성으로 제공되지 않아요. 그러려면 그 전에 다음 함수들을 호출해야 합니다.
iex> Mix.ensure_application!(:observer)
iex> :observer.start()
위 호출 중 하나라도 실패하면 다음과 같은 경우일 수 있어요. 일부 패키지 매니저는 GUI 지원을 위한 WX 바인딩 없이 최소화된 Erlang을 기본 설치해요. 어떤 패키지 매니저에서는 headless Erlang을 더 완전한 패키지로 교체할 수 있어요(Debian/Ubuntu/Arch에서 erlang vs erlang-nox라는 패키지를 찾아보세요). 다른 매니저에서는 별도의 erlang-wx(또는 비슷한 이름) 패키지를 설치해야 할 수도 있어요.
위 작업을 하면 런타임과 프로젝트를 완전히 이해하고 탐색할 수 있는 여러 창(pane)을 제공하는 또 하나의 그래픽 사용자 인터페이스가 열립니다.
Observer를 실제 프로젝트 맥락에서 살펴보는 건 Mix & OTP 가이드의 Dynamic Supervisor 챕터에서 해요. 이건 Phoenix 프레임워크가 단일 머신에서 2백만 연결을 달성할 때 사용한 디버깅 기법이기도 합니다.
Phoenix 웹 프레임워크를 쓰고 있다면, 생산 노드를 위한 웹 대시보드인 Phoenix LiveDashboard가 딸려 와서 Observer와 비슷한 기능을 제공해요.
마지막으로, IEx에서 runtime_info/0을 직접 호출하면 런타임 정보의 미니 오버뷰를 얻을 수 있다는 점도 기억하세요.
다른 도구와 커뮤니티(Other tools and community)
우리는 Erlang VM이 제공하는 것의 표면만 살짝 만져봤어요. 예를 들어:
- observer 애플리케이션과 함께 Erlang은 크래시 덤프를 보는
:crashdump_viewer도 포함해요. - Linux Trace Toolkit, DTRACE, SystemTap 같은 OS 레벨 트레이서와의 통합.
- Microstate accounting은 짧은 시간 간격 동안 런타임이 여러 저수준 작업에 얼마나 많은 시간을 쓰는지 측정해요.
- Mix는
profile네임스페이스 아래에mix profile.cprof나mix profile.fprof같은 많은 태스크를 제공해요. - 더 고급 사례는 무료 ebook으로 제공되는 훌륭한 Erlang in Anger를 추천해요.
즐거운 디버깅 되세요!