기술 동향

Debugging using Livebook

gozalex 2022. 7. 21. 18:15

Elixir Korea Meetup 에서 Livebook 을 사용한 관련 Topic을 세미나 한다길래 꼭 들으려 했으나

피치 못할 사정으로 노쇼하는 바람에 따로 시간 내어 조사해 봤습니다.

#노쇼노매너 #노쇼 #엘릭서밋업

 

예전에 설치했던 Livebook을 업그레이드 하고, erlang dbg모듈을 사용해 봤습니다.

아래는 테스트 화면입니다.

#훌륭 #OTP #Process #RemoteProcess #Cluster

와.. 이건 뭐 사기캐인가?

 

Debug Using Livebook 

 

Install as an Escript

This is the easiest way to use Livebook locally. 

This assumes you are doing Elixir development locally and are setup for Elixir development.

 

Following the Livebook Escript README section, 

this is how you install Livebook.

$ mix escript.install hex livebook

 

Then locally launching Livebook is:

$ livebook server

 

Upgrading When Using an Escript

When upgrading your Livebook version while using asdf for managing your Elixir and Erlang installs, 

then here's a helpful tip:

  • Remove the existing livebook shim: rm ~/.asdf/shims/livebook
  • Install the new version: mix escript.install hex livebook
  • Re-shim to get the command: asdf reshim elixir
  • Verify you have the upgrade: livebook --version

 

 

참고자료

https://fly.io/docs/getting-started/elixir/

 

The Fly Global Application Platform · Fly Docs

Documentation and guides from the team at Fly.io.

fly.io

https://fly.io/docs/app-guides/elixir-static-cookie/

 

The Fly Global Application Platform · Fly Docs

Documentation and guides from the team at Fly.io.

fly.io

https://fly.io/docs/elixir/advanced-guides/connect-livebook-to-your-app/

 

Connecting Livebook to Your App in Production

Documentation and guides from the team at Fly.io.

fly.io

https://kaiwern.com/posts/2020/11/02/debugging-with-tracing-in-elixir/

 

Debugging With Tracing in Elixir | kw7oe

If you are a video person, and have 24 minutes to spend with, just jump over to this ElixirConf 2020 - Debugging Live Systems on the BEAM talk by Jeffery Utter. This article is a downgraded version of the video 😂. I wrote this before the video is publis

kaiwern.com

https://www.erlang.org/doc/man/dbg.html#tracer-0

 

Erlang -- dbg

When tracing function calls on a group leader process (an IO process), there is risk of causing a deadlock. This will happen if a group leader process generates a trace message and the tracer process, by calling the trace handler function, sends an IO requ

www.erlang.org

 

들어가기 전에...

 iex --sname fit --cookie iQ-VfumnytJJ9omnF8s...oNzwDsRIQlrsTA== -S mix phx.server

위와 같이 Trace 할 프로젝트를 실행합니다.

 

시작:dbg

추적을 시작하기 전에 다음 코드로 dbg 및 tracer 프로세스를 시작해야 합니다.

tracer는 단순히 생각해서 receive block 안에서 log를 출력해주는 일을 하는 간단한 모듈이라고 생각하면 됩니다.

:dbg.start
:dbg.tracer

 

명시적으로 추적하려는 것을 명시할 때까지 아무 작업도 수행하지 않습니다.


추적할 대상 지정

시스템에서 호출된 Enum.map/2 함수를 추적한다고 가정해 보겠습니다. 다음을 실행하여 지정할 수 있습니다.

:dbg.tp(Enum, :map, 2, [])
:dbg.p(:all, :c)

코드의 일부가 Enum.map([1,2,3], & &1 + 1)을 호출하면 위의 :dbg 코드를 실행하는 위치에 따라 쉘에서 다른 출력을 얻게 됩니다.

 

  • Local Shell (응용 프로그램 코드 실행)

Local Shell에서 시스템을 실행하는 셸에 있는 경우 추적한 함수가 호출될 때마다 다음과 같은 내용이 표시됩니다.

(<0.106.0>) call 'Elixir.Enum':map([1,2,3],#Fun<erl_eval.44.97283095>)

 

  • Remote Shell (여기서 셸 프로세스가 애플리케이션을 실행하는 프로세스에 연결되었다고 가정)

Remote Shell 에서는 Tracer가 Remote Shell 대신 라이브 프로세스에서 출력을 기록하기 때문에 출력이 표시되지 않습니다. 추적을 보려면 로그 파일(예: erlang.log.1)을 살펴봐야 합니다.

Remote Shell에서 출력하려면 추적 프로그램 프로세스를 다르게 시작해야 합니다.

:dbg.tracer(:process, {fn msg, n -> IO.inspect(msg); n+1 end, 0})


:dbg.tracer는 추적된 각 이벤트를 처리하는 방법을 사용자 정의할 수 있는 두 번째 인수를 허용합니다. 

여기서 우리는 대신 Local Shell에 출력을 기록하도록 Tracer에게 지시합니다.


추적 중지

추적을 중지하려면 다음과 같이 간단합니다.

:dbg.stop_clear

 

필요한 정보를 얻은 후 추적을 중지하는 것이 매우 중요합니다.

 

그 이유는 다음과 같습니다.

  • 추적은 시스템에 대한 추가 오버헤드입니다.
  • IO/로그에 기록합니다. 따라서 디스크 공간을 차지합니다.

이것은 특히 High Frequency 또는 High Volume 시스템을 추적할 때 중요합니다. 

따라서 실제 프로덕션 시스템에서 추적 프로그램을 시작할 때 다음을 사용하는 것이 실제로 더 좋습니다.

:dbg.tracer(:process, {fn _, 5 -> :dbg.stop_clear()
                        msg, n -> IO.inspect(msg); n+1 end, 0})


첫 번째 함수 절은 추적자에게 5개의 이벤트가 발생한 후 중지하도록 지시합니다. 

두 번째 절은 추적자에게 수신 이벤트를 처리하는 방법을 알려줍니다. 이 경우에는 인쇄만 하고 카운터를 증가시킵니다.

 

All Together

:dbg.start
:dbg.tracer(:process, {fn _, 5 -> :dbg.stop_clear()
                        msg, n -> IO.inspect(msg); n+1 end, 0})
:dbg.tpl(Enum, :map, [])
:dbg.p(:all, :c)

# With trace
Enum.map([1,2,3,4], & &1 + 1)
Enum.map([1,2,3,4], & &1 + 1)
Enum.map([1,2,3,4], & &1 + 1)
Enum.map([1,2,3,4], & &1 + 1)
Enum.map([1,2,3,4], & &1 + 1)

# No more trace
Enum.map([1,2,3,4], & &1 + 1)


때로는 함수에 전달되는 인수보다 더 많은 것을 알고 싶을 수도 있고, 

Return Value 또는 함수가 실행될 때의 타임스탬프를 알고 싶을 수도 있습니다.
위에서 사용한 일부 함수에 더 많은 인수를 제공하여 이를 달성할 수 있습니다.

Return Value 를 Trace하고 싶다면 다음과 같이 :dbg.tpl에 더 많은 옵션을 지정할 수 있습니다.

:dbg.start
:dbg.tracer

:dbg.tpl(Enum, :map, 2, [{:_, [], [{:return_trace}]}])

:dbg.p(:all, :c)

Enum.map([1,2,3], & &1 + 1)
# (<0.106.0>) call 'Elixir.Enum':map([1,2,3],#Fun<erl_eval.44.97283095>)
# (<0.106.0>) returned from 'Elixir.Enum':map/2 -> [2,3,4]
# [2,3,4]

:dbg.stop

* 추가 옵션 MatchSpec 은 Document 에서 참고하세요

함수 호출의 타임스탬프를 포함하려면 :dbg.p를 호출할 때 :timestamp를 포함할 수 있습니다.

:dbg.start
:dbg.tracer
:dbg.tpl(Enum, :map, 2, [{:_, [], [{:return_trace}]}])

:dbg.p(:all, [:c, :timestamp])

Enum.map([1,2,3,4], & &1 + 1)
# [2, 3, 4, 5]
# (<0.105.0>) call 'Elixir.Enum':map([1,2,3,4],#Fun<erl_eval.44.40011524>) (Timestamp: {1624, 687361, 187278})
# (<0.105.0>) returned from 'Elixir.Enum':map/2 -> [2,3,4,5] (Timestamp: {1624, 687361, 187303})
# ^ The timestmap is in Erlang Timestamp tuple format.

:dbg.stop

 

보다 구체적인 함수 호출 추적


특정 인수(예: 사용자 ID 또는 특정 범주)로 호출된 함수만 추적할 수도 있습니다. 

:dbg.tpl에 대한 MatchSpec을 수정하여 이를 수행할 수 있습니다.

:dbg.tpl(Enum, :map, 2, [{[[1, 2, 3], :_], [], [{:return_trace}]}])


MatchSpec의 첫 번째 튜플에 있는 첫 번째 인수는 일치시키려는 함수 매개변수입니다. 

여기에서 Enum.map/2를 다음과 일치시키려고 합니다.

  • 첫 번째 매개변수 일치 [1,2,3]
  • 두 번째 매개변수 일치 :_는 무엇이든 됩니다.

:return_trace를 둘러싸는 {}에 유의하세요. Tracer가 Return Value 도 추적할 수 있도록 하는 것이 중요합니다.

:dbg.start
:dbg.tracer

:dbg.tpl(Enum, :map, 2, [{[[1, 2, 3], :_], [], [{:return_trace}]}])

:dbg.p(:all, :c)

Enum.map([1,2,3], & &1 + 1)
# (<0.106.0>) call 'Elixir.Enum':map([1,2,3],#Fun<erl_eval.44.97283095>)
# (<0.106.0>) returned from 'Elixir.Enum':map/2 -> [2,3,4]
# [2, 3, 4]

Enum.map([1,2], & &1 + 1)
# Nothing is logged
# [2, 3]

 

복잡한 시나리오에서는 매치 스펙을 작성하기 어려울 수 있습니다. 

운 좋게도 :dbg.fun2ms를 사용하여 함수를 일치 사양으로 변환할 수 있습니다.

:dbg.fun2ms(fn [[1,2,3], _] -> :return_trace end)
# [{[1, 2, 3], [], [:return_trace]}]


그러나 MatchSpec이 완전히 정확하지는 않습니다. 

Return Value 추적이 올바르게 작동하려면 {}로 래핑해야 합니다. 그래서 이것은 작동하지 않을 것입니다:

:dbg.start
:dbg.tracer

match_spec = :dbg.fun2ms(fn [[1,2,3], _] -> :return_trace end)
:dbg.tpl(Enum, :map, match_spec)
:dbg.p(:all, :c)

# With trace of function call but no return value
Enum.map([1,2,3], & &1 + 1)
# (<0.105.0>) call 'Elixir.Enum':map([1,2,3],#Fun<erl_eval.44.40011524>)
# [2, 3, 4]

:dbg.stop


따라서 일치 사양을 약간 변형해야 합니다. 방법은 다음과 같습니다.

:dbg.start
:dbg.tracer

match_spec = :dbg.fun2ms(fn [[1,2,3], _] -> :return_trace end)
match_spec = Enum.map(match_spec, fn {args, guards, [:return_trace]} ->
  {args, guards, [{:return_trace}]}
end)

:dbg.tpl(Enum, :map, match_spec)
:dbg.p(:all, :c)

# With trace of function call and return value.
Enum.map([1,2,3], & &1 + 1)
# (<0.105.0>) call 'Elixir.Enum':map([1,2,3],#Fun<erl_eval.44.40011524>)
# (<0.105.0>) returned from 'Elixir.Enum':map/2 -> [2,3,4]
# [2, 3, 4]

 

Summary

:dbg는 Low Level의 디버깅 툴 입니다. 더 간단한 인터페이스를 선호한다면 recon에서 recon_trace를 사용하는 것을 고려하십시오.
recon_trace에 비해 :dbg의 이점 중 하나는 내장되어 있다는 것입니다. 코드베이스에 추가 종속성을 추가할 필요가 없습니다. 그러나 특히 라이브 프로덕션 시스템에서 이 작업을 많이 수행하는 경우에는 recon을 종속 항목으로 추가하고 대신 recon_trace를 사용하는 것이 좋습니다.

Recon은 단순한 trace보다 훨씬 더 많은 도구를 제공합니다. 또한 시스템을 안전하게 진단할 수 있습니다. 이와 같은 주제에 관심이 있다면 Recon의 저자가 recon 등으로 BEAM 응용 프로그램을 진단하는 것에 대해 쓴 Erlang in Anger의 무료 사본을 얻을 수도 있습니다.

정찰과 함께 추적을 사용하는 방법에 대해 자세히 알아보려면 여기에서도 읽을 수 있습니다.

 

 

본 내용은 Livebook 내용에 Kai 블로그를 번역하여 추가하였습니다.

원문은 https://kaiwern.com/posts/2020/11/02/debugging-with-tracing-in-elixir/ 를 참고하세요