Keeping tabs on production

Hubert Łępicki
CODE BEAM LITE AMSTERDAM

28 November 2019

@hubertlepicki

https://slides.com/

hubertlepicki

Today I will focus on

  • Debugging production issues
  • Monitoring
  • Bunch of semi-random tips and tricks
  • From Elixir perspective but heavy on Erlang tools

Most important thing when running a business?

Problem

$ mix test
Randomized with seed 145268
==> myapp
.........................................................
.........................................................
.........................................................
................................                                                                                                                                                           

Finished in 20.6 seconds
30 doctests, 165 tests, 0 failures

Bugs are often difficult to reproduce

What happens on production?

Reconstruct user journey

Ask for more information

  • Who is the issue happening to?
  • What is the URL the issue happens at?
  • Can we have screenshot with the issue visible?

We check the logs

Logger

Logger in Elixir & Phoenix

config :logger,
  level: :info,
  handle_sasl_reports: true,
  backends: [:console]

config :logger, :console,
  format: "$time $metadata[$level] $message\n",
  metadata: [:request_id]

Logger.debug("I am debugging info")
Logger.info("I am info-level message! Probably ok.")
Logger.warn("I am a warning! Beware!")
Logger.error("Oh no, this is not good :(")

Logger backends

def deps do
  [{:logger_papertrail_backend, "~> 1.1"}]
end

config :logger, :logger_papertrail_backend,
  url: "papertrail://logs.papertrailapp.com:<port>/<system_name>",
  level: :info,
  format: "$metadata $message"
  

config :logger,
  backends: [ :console,
    LoggerPapertrailBackend.Logger
  ]

Inspecting the logs

07:00:51.577 request_id=Fds-K5OR1daI51IAAAIx [info] Sent 200 in 1ms                                                                                                            
07:00:51.969 request_id=Fds-K6r-mtJMK_gAAAJB [info] GET /page/                                                                                                                 
07:00:51.970 request_id=Fds-K6r-mtJMK_gAAAJB [info] Sent 200 in 1ms                                                                                                            
07:00:52.324 request_id=Fds-K8AszvRYWgIAAAJR [info] GET /page/                                                                                                                 
07:00:52.350 [error] GenServer Myapp.CurrentUsageCalculator terminating
** (MatchError) no match of right hand side value: {:error, :limits_exceeded}
    (myapp) lib/myapp/current_usage_calculator.ex:26: Myapp.CurrentUsageCalculator.handle_cast/2
    (stdlib) gen_server.erl:637: :gen_server.try_dispatch/4
    (stdlib) gen_server.erl:711: :gen_server.handle_msg/6
    (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
Last message: {:"$gen_cast", :increment}
State: %{usage: 10}
07:00:52.353 request_id=Fds-K8AszvRYWgIAAAJR [info] Converted exit {{{:badmatch, {:error, :limits_exceeded}}, [{Myapp.CurrentUsageCalculator, :handle_cast, 2, [file: 'lib/mya$
p/current_usage_calculator.ex', line: 26]}, {:gen_server, :try_dispatch, 4, [file: 'gen_server.erl', line: 637]}, {:gen_server, :handle_msg, 6, [file: 'gen_server.erl', line: 
711]}, {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 249]}]}, {GenServer, :call, [Myapp.CurrentUsageCalculator, :get_usage, 5000]}} to 500 response
07:00:52.360 [error] #PID<0.565.0> running MyappWeb.Endpoint (connection #PID<0.454.0>, stream id 50) terminated
Server: localhost:4000 (http)
Request: GET /page/
** (exit) exited in: GenServer.call(Myapp.CurrentUsageCalculator, :get_usage, 5000)
    ** (EXIT) an exception was raised:
        ** (MatchError) no match of right hand side value: {:error, :limits_exceeded}
            (myapp) lib/myapp/current_usage_calculator.ex:26: Myapp.CurrentUsageCalculator.handle_cast/2
            (stdlib) gen_server.erl:637: :gen_server.try_dispatch/4
            (stdlib) gen_server.erl:711: :gen_server.handle_msg/6
            (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
07:00:52.636 request_id=Fds-K9K-ZBZTynwAAAJh [info] GET /page/
07:00:52.636 request_id=Fds-K9K-ZBZTynwAAAJh [info] Converted exit {:noproc, {GenServer, :call, [Myapp.CurrentUsageCalculator, :get_usage, 5000]}} to 500 response
07:00:52.636 [error] #PID<0.567.0> running MyappWeb.Endpoint (connection #PID<0.454.0>, stream id 51) terminated
Server: localhost:4000 (http)
Request: GET /page/
** (exit) exited in: GenServer.call(Myapp.CurrentUsageCalculator, :get_usage, 5000)
    ** (EXIT) no process: the process is not alive or there's no process currently associated with the given name, possibly because its application isn't started
07:00:53.075 request_id=Fds-K-zvfZsVjAEAAAJx [info] GET /page/

Logs = messages + metadata

Use metadata

request_id=Fds-K6r-mtJMK_gAAAJB [info] GET /page/     
request_id=Fds-K5OR1daI51IAAAIx [info] Sent 200 in 1ms  
request_id=Fds-K6r-mtJMK_gAAAJB [info] Sent 200 in 1ms                                                                                                            
request_id=Fds-K8AszvRYWgIAAAJR [info] GET /page/                                                                                                                 
[error] GenServer Myapp.CurrentUsageCalculator terminating
** (MatchError) no match of right hand side value: {:error, :limits_exceeded}
    (myapp) lib/myapp/current_usage_calculator.ex:26: Myapp.CurrentUsageCalculator.handle_cast/2
    (stdlib) gen_server.erl:637: :gen_server.try_dispatch/4
    (stdlib) gen_server.erl:711: :gen_server.handle_msg/6
    (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
Last message: {:"$gen_cast", :increment}
State: %{usage: 10}
request_id=Fds-K8AszvRYWgIAAAJR [info] Converted exit {{{:badmatch, {:error, :limits_exceeded}}, [{Myapp.CurrentUsageCalculator, :handle_cast, 2, [file: 'lib/mya$
p/current_usage_calculator.ex', line: 26]}, {:gen_server, :try_dispatch, 4, [file: 'gen_server.erl', line: 637]}, {:gen_server, :handle_msg, 6, [file: 'gen_server.erl', line: 
711]}, {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 249]}]}, {GenServer, :call, [Myapp.CurrentUsageCalculator, :get_usage, 5000]}} to 500 response
07:00:52.360 [error] #PID<0.565.0> running MyappWeb.Endpoint (connection #PID<0.454.0>, stream id 50) terminated
Server: localhost:4000 (http)
Request: GET /page/
** (exit) exited in: GenServer.call(Myapp.CurrentUsageCalculator, :get_usage, 5000)
    ** (EXIT) an exception was raised:
        ** (MatchError) no match of right hand side value: {:error, :limits_exceeded}
            (myapp) lib/myapp/current_usage_calculator.ex:26: Myapp.CurrentUsageCalculator.handle_cast/2
            (stdlib) gen_server.erl:637: :gen_server.try_dispatch/4
            (stdlib) gen_server.erl:711: :gen_server.handle_msg/6
            (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3

Plug.RequestId

...

defp set_request_id({conn, request_id}, header) do
    Logger.metadata(request_id: request_id)
    Conn.put_resp_header(conn, header, request_id)
end

...


config :logger, :console,
  format: "$time $metadata[$level] $message\n",
  metadata: [:request_id],
  level: :info
     

Use metadata in own code

...

Logger.metadata(user_id: user_id)

...


config :logger, :console,
  format: "$time $metadata[$level] $message\n",
  metadata: [:request_id, user_id],
  level: :info
     
request_id=JB user_id=1 [info] GET /page/     
request_id=Ix user_id=2 [info] Sent 200 in 1ms  
request_id=JB user_id=1 [info] Sent 200 in 1ms                                                                                                            
request_id=JR user_id=1 [info] GET /page/                                                                                                                 
[error] GenServer Myapp.CurrentUsageCalculator terminating
** (MatchError) no match of right hand side value: {:error, :limits_exceeded}
    (myapp) lib/myapp/current_usage_calculator.ex:26: Myapp.CurrentUsageCalculator.handle_cast/2
    (stdlib) gen_server.erl:637: :gen_server.try_dispatch/4
    (stdlib) gen_server.erl:711: :gen_server.handle_msg/6
    (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
Last message: {:"$gen_cast", :increment}
State: %{usage: 10}
request_id=JR user_id=1 [info] Converted exit {{{:badmatch, {:error, :limits_exceeded}}, [{Myapp.CurrentUsageCalculator, :handle_cast, 2, [file: 'lib/mya$
p/current_usage_calculator.ex', line: 26]}, {:gen_server, :try_dispatch, 4, [file: 'gen_server.erl', line: 637]}, {:gen_server, :handle_msg, 6, [file: 'gen_server.erl', line: 
711]}, {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 249]}]}, {GenServer, :call, [Myapp.CurrentUsageCalculator, :get_usage, 5000]}} to 500 response
07:00:52.360 [error] #PID<0.565.0> running MyappWeb.Endpoint (connection #PID<0.454.0>, stream id 50) terminated
Server: localhost:4000 (http)
Request: GET /page/
** (exit) exited in: GenServer.call(Myapp.CurrentUsageCalculator, :get_usage, 5000)
    ** (EXIT) an exception was raised:
        ** (MatchError) no match of right hand side value: {:error, :limits_exceeded}
            (myapp) lib/myapp/current_usage_calculator.ex:26: Myapp.CurrentUsageCalculator.handle_cast/2
            (stdlib) gen_server.erl:637: :gen_server.try_dispatch/4
            (stdlib) gen_server.erl:711: :gen_server.handle_msg/6
            (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3

I know Clojure

Important thing to know about Logger

  • async/sync modes
  • switches to sync when buffer is full
  • use own log statements but *don't abuse it*
  • use own metadata but *don't abuse it*
  • can track metrics (but you probably want to use :telemetry instead)
  • abusing logger will result in performance punishment to your production environment!

Future of Logger

  • rely on OTP 21+ :logger
  • backward-compatible with current Logger (for now)
  • allow structured logging
  • allow sharing metadata with Erlang logs
  • new log levels (:emergency, :alert, :critical, :warning, :notice)
  • handlers written in Erlang (or Elixir!)
  • built-in logging to files (with file rotation!)
  • smooth integration with Lager (erlang-lager/lager)

Inspecting logs is just first step

Poking around production

Remote console

./bin/myapp remote_console

(or)

gigalixir ps:remote_console --app_name=myapp

iex>

Debugging OTP behaviours

:sys module - :sys.log/2

iex> pid = Process.whereis(CurrentUsageCalculator)       
#PID<0.435.0>

iex> :sys.log(pid, {true, 100})
:ok

iex> CurrentUsageCalculator.increment()           
:ok

iex> CurrentUsageCalculator.increment()
:ok

iex> CurrentUsageCalculator.get_usage()
2

:sys module - :sys.log/2

iex> :sys.log pid, :get
{:ok,
 [
   {:in, {:"$gen_cast", :increment}},
   {:noreply, %{usage: 1}},
   {:in, {:"$gen_cast", :increment}},
   {:noreply, %{usage: 2}},
   {:in,
    {:"$gen_call", {#PID<0.436.0>, #Reference<0.4113248558.1777598466.76538>},
     :get_usage}},
   {:out, 2, {#PID<0.436.0>, #Reference<0.4113248558.1777598466.76538>},
    %{usage: 2}} 
 ]}
 
 

:sys module - :sys.log/2

iex> :sys.log pid, :print
*DBG* 'CurrentUsageCalculator' got cast increment
*DBG* 'CurrentUsageCalculator' new state #{usage => 1}
*DBG* 'CurrentUsageCalculator' got cast increment
*DBG* 'CurrentUsageCalculator' new state #{usage => 2}
*DBG* 'CurrentUsageCalculator' got call get_usage from 
<0.436.0>
*DBG* 'CurrentUsageCalculator' sent 2 to <0.436.0>, new
state #{usage =>
                                                                                 2}
:ok
 

:sys module - :sys.trace/2

iex> :sys.trace pid, true
:ok
iex> CurrentUsageCalculator.get_usage()
*DBG* 'CurrentUsageCalculator' got call get_usage from
<0.436.0>
*DBG* 'CurrentUsageCalculator' sent 2 to <0.436.0>, new
state #{usage => 2}
2
iex> CurrentUsageCalculator.increment()
*DBG* 'CurrentUsageCalculator' got cast increment
:ok
*DBG* 'CurrentUsageCalculator' new state #{usage => 3}

:sys module - inspecting state

iex> :sys.get_state(pid)
%{usage: 6}

iex> :sys.replace_state(pid, fn(_) -> %{usage: 0} end)
%{usage: 0}

iex> :sys.get_state(pid)                              
%{usage: 0}

:sys module - turn off

iex> :sys.no_debug(pid)
:ok

:erlang.trace/3 #yolo

iex(27)> :erlang.trace pid, true, [:receive]
1
iex(28)> Myapp.CurrentUsageCalculator.increment()         
:ok
iex(29)> flush
{:trace, #PID<0.435.0>, :receive, {:"$gen_cast", :increment}}
:ok

:erlang.trace/3 #yolo

iex(27)> :erlang.trace :all, true, [:all]
1
iex(28)> Myapp.CurrentUsageCalculator.increment()         
:ok
iex(29)> flush
{:trace_ts, #PID<0.65.0>, :send,
 {#PID<0.65.0>,
  {:put_chars_sync, :unicode,
   "\e[39m{\e[0m\e[33m\e[36m:trace_ts\e[0m\e[33m\e[39m,\e[0m\e[33m #PID<0.65.0>\e[39m,\e[0m\e[33m \e[36m:send\e[0m\e[33m\e[39m,\e[0m\e[33m\n \e[39m{\e[0m\e[33m#PID<0.65.0>\e[39m,\e[0m\e[33m \e[39m{\e[0m\e[33m\e[36m:requests\e[0m\e[33m\e[39m,\e[0m\e[33m \e[39m[\e[0m\e[33m\e[39m{\e[0m\e[33m\e[36m:put_chars\e[0m\e[33m\e[39m,\e[0m\e[33m \e[36m:unicode\e[0m\e[33m\e[39m,\e[0m\e[33m 'iex(32)> '\e[39m}\e[0m\e[33m\e[39m]\e[0m\e[33m\e[39m}\e[0m\e[33m\e[39m}\e[0m\e[33m\e[39m,\e[0m\e[33m\n #PID<0.63.0>\e[39m,\e[0m\e[33m 2\e[39m,\e[0m\e[33m \e[39m{\e[0m\e[33m1574\e[39m,\e[0m\e[33m 928951\e[39m,\e[0m\e[33m 843593\e[39m}\e[0m\e[33m\e[39m}\e[0m\e[33m\n",
   {{#PID<0.436.0>, #Reference<0.4113248558.1777598466.80190>}, :ok}}},
 #PID<0.63.0>, 2, {1574, 928954, 598373}}
{:trace_ts, #PID<0.65.0>, :out, {:group, :server_loop, 3}, 2,
 {1574, 928954, 598374}}
{:trace_ts, #PID<0.63.0>, :in, {:user_drv, :server_loop, 6}, 2,
 {1574, 928954, 598377}}
{:trace_ts, #PID<0.63.0>, :receive,
 {#PID<0.65.0>,
  {:put_chars_sync, :unicode,
   "\e[39m{\e[0m\e[33m\e[36m:trace_ts\e[0m\e[33m\e[39m,\e[0m\e[33m #PID<0.65.0>\e[39m,\e[0m\e[33m \e[36m:send\e[0m\e[33m\e[39m,\e[0m\e[33m\n \e[39m{\e[0m\e[33m#PID<0.65.0>\e[39m,\e[0m\e[33m \e[39m{\e[0m\e[33m\e[36m:requests\e[0m\e[33m\e[39m,\e[0m\e[33m \e[39m[\e[0m\e[33m\e[39m{\e[0m\e[33m\e[36m:put_chars\e[0m\e[33m\e[39m,\e[0m\e[33m \e[36m:unicode\e[0m\e[33m\e[39m,\e[0m\e[33m 'iex(32)> '\e[39m}\e[0m\e[33m\e[39m]\e[0m\e[33m\e[39m}\e[0m\e[33m\e[39m}\e[0m\e[33m\e[39m,\e[0m\e[33m\n #PID<0.63.0>\e[39m,\e[0m\e[33m 2\e[39m,\e[0m\e[33m \e[39m{\e[0m\e[33m1574\e[39m,\e[0m\e[33m 928951\e[39m,\e[0m\e[33m 843593\e[39m}\e[0m\e[33m\e[39m}\e[0m\e[33m\n",
   {{#PID<0.436.0>, #Reference<0.4113248558.1777598466.80190>}, :ok}}}, 2,
 {1574, 928954, 598378}}
{:trace_ts, #PID<0.63.0>, :out, {:user_drv, :server_loop, 6}

:erlang.trace/3 #yolo

{:trace_ts, #PID<0.65.0>, :send,
 {#PID<0.65.0>,
  {:put_chars_sync, :unicode,
   "\e[39m{\e[0m\e[33m\e[36m:trace_ts\e[0m\e[33m\e[39m,\e[0m\e[33m #PID<0.65.0>\e[39m,\e[0m\e[33m \e[36m:send\e[0m\e[33m\e[39m,\e[0m\e[33m\n \e[39m{\e[0m\e[33m#PID<0.65.0>\e[39m,\e[0m\e[33m \e[39m{\e[0m\e[33m\e[36m:requests\e[0m\e[33m\e[39m,\e[0m\e[33m \e[39m[\e[0m\e[33m\e[39m{\e[0m\e[33m\e[36m:put_chars\e[0m\e[33m\e[39m,\e[0m\e[33m \e[36m:unicode\e[0m\e[33m\e[39m,\e[0m\e[33m 'iex(32)> '\e[39m}\e[0m\e[33m\e[39m]\e[0m\e[33m\e[39m}\e[0m\e[33m\e[39m}\e[0m\e[33m\e[39m,\e[0m\e[33m\n #PID<0.63.0>\e[39m,\e[0m\e[33m 2\e[39m,\e[0m\e[33m \e[39m{\e[0m\e[33m1574\e[39m,\e[0m\e[33m 928951\e[39m,\e[0m\e[33m 843593\e[39m}\e[0m\e[33m\e[39m}\e[0m\e[33m\n",
   {{#PID<0.436.0>, #Reference<0.4113248558.1777598466.80190>}, :ok}}},
 #PID<0.63.0>, 2, {1574, 928954, 598373}}
{:trace_ts, #PID<0.65.0>, :out, {:group, :server_loop, 3}, 2,
 {1574, 928954, 598374}}
{:trace_ts, #PID<0.63.0>, :in, {:user_drv, :server_loop, 6}, 2,
 {1574, 928954, 598377}}
{:trace_ts, #PID<0.63.0>, :receive,
 {#PID<0.65.0>,
  {:put_chars_sync, :unicode,
   "\e[39m{\e[0m\e[33m\e[36m:trace_ts\e[0m\e[33m\e[39m,\e[0m\e[33m #PID<0.65.0>\e[39m,\e[0m\e[33m \e[36m:send\e[0m\e[33m\e[39m,\e[0m\e[33m\n \e[39m{\e[0m\e[33m#PID<0.65.0>\e[39m,\e[0m\e[33m \e[39m{\e[0m\e[33m\e[36m:requests\e[0m\e[33m\e[39m,\e[0m\e[33m \e[39m[\e[0m\e[33m\e[39m{\e[0m\e[33m\e[36m:put_chars\e[0m\e[33m\e[39m,\e[0m\e[33m \e[36m:unicode\e[0m\e[33m\e[39m,\e[0m\e[33m 'iex(32)> '\e[39m}\e[0m\e[33m\e[39m]\e[0m\e[33m\e[39m}\e[0m\e[33m\e[39m}\e[0m\e[33m\e[39m,\e[0m\e[33m\n #PID<0.63.0>\e[39m,\e[0m\e[33m 2\e[39m,\e[0m\e[33m \e[39m{\e[0m\e[33m1574\e[39m,\e[0m\e[33m 928951\e[39m,\e[0m\e[33m 843593\e[39m}\e[0m\e[33m\e[39m}\e[0m\e[33m\n",
   {{#PID<0.436.0>, #Reference<0.4113248558.1777598466.80190>}, :ok}}}, 2,
 {1574, 928954, 598378}}
{:trace_ts, #PID<0.63.0>, :out, {:user_drv, :server_loop, 6}

:erlang.trace/3 #yolo

{:trace_ts, #PID<0.65.0>, :send,
 {#PID<0.65.0>,
  {:put_chars_sync, :unicode,
   "\e[39m{\e[0m\e[33m\e[36m:trace_ts\e[0m\e[33m\e[39m,\e[0m\e[33m #PID<0.65.0>\e[39m,\e[0m\e[33m \e[36m:send\e[0m\e[33m\e[39m,\e[0m\e[33m\n \e[39m{\e[0m\e[33m#PID<0.65.0>\e[39m,\e[0m\e[33m \e[39m{\e[0m\e[33m\e[36m:requests\e[0m\e[33m\e[39m,\e[0m\e[33m \e[39m[\e[0m\e[33m\e[39m{\e[0m\e[33m\e[36m:put_chars\e[0m\e[33m\e[39m,\e[0m\e[33m \e[36m:unicode\e[0m\e[33m\e[39m,\e[0m\e[33m 'iex(32)> '\e[39m}\e[0m\e[33m\e[39m]\e[0m\e[33m\e[39m}\e[0m\e[33m\e[39m}\e[0m\e[33m\e[39m,\e[0m\e[33m\n #PID<0.63.0>\e[39m,\e[0m\e[33m 2\e[39m,\e[0m\e[33m \e[39m{\e[0m\e[33m1574\e[39m,\e[0m\e[33m 928951\e[39m,\e[0m\e[33m 843593\e[39m}\e[0m\e[33m\e[39m}\e[0m\e[33m\n",
   {{#PID<0.436.0>, #Reference<0.4113248558.1777598466.80190>}, :ok}}},
 #PID<0.63.0>, 2, {1574, 928954, 598373}}
{:trace_ts, #PID<0.65.0>, :out, {:group, :server_loop, 3}, 2,
 {1574, 928954, 598374}}
{:trace_ts, #PID<0.63.0>, :in, {:user_drv, :server_loop, 6}, 2,
 {1574, 928954, 598377}}
{:trace_ts, #PID<0.63.0>, :receive,
 {#PID<0.65.0>,
  {:put_chars_sync, :unicode,
   "\e[39m{\e[0m\e[33m\e[36m:trace_ts\e[0m\e[33m\e[39m,\e[0m\e[33m #PID<0.65.0>\e[39m,\e[0m\e[33m \e[36m:send\e[0m\e[33m\e[39m,\e[0m\e[33m\n \e[39m{\e[0m\e[33m#PID<0.65.0>\e[39m,\e[0m\e[33m \e[39m{\e[0m\e[33m\e[36m:requests\e[0m\e[33m\e[39m,\e[0m\e[33m \e[39m[\e[0m\e[33m\e[39m{\e[0m\e[33m\e[36m:put_chars\e[0m\e[33m\e[39m,\e[0m\e[33m \e[36m:unicode\e[0m\e[33m\e[39m,\e[0m\e[33m 'iex(32)> '\e[39m}\e[0m\e[33m\e[39m]\e[0m\e[33m\e[39m}\e[0m\e[33m\e[39m}\e[0m\e[33m\e[39m,\e[0m\e[33m\n #PID<0.63.0>\e[39m,\e[0m\e[33m 2\e[39m,\e[0m\e[33m \e[39m{\e[0m\e[33m1574\e[39m,\e[0m\e[33m 928951\e[39m,\e[0m\e[33m 843593\e[39m}\e[0m\e[33m\e[39m}\e[0m\e[33m\n",
   {{#PID<0.436.0>, #Reference<0.4113248558.1777598466.80190>}, :ok}}}, 2,
 {1574, 928954, 598378}}
{:trace_ts, #PID<0.63.0>, :out, {:user_drv, :server_loop, 6}

:erlang.trace/3 #yolo

09:22:41.730 [info] SIGTERM received - shutting down

[1]    31899 killed

:recon_trace

iex> :recon_trace.calls({String, :split, 2}, 1)
1
iex> for i <- 1..100 do String.split("asdfasdf", "a") end


9:30:38.888919 <0.630.0> 
'Elixir.String':split(<<"asdfasdf">>, <<"a">>)

Recon tracer rate limit tripped.

:recon_trace

  • safer on production
  • useful during development
  • add "recon" to your deps today!
  • check out :recon and other :recon_ modules too!

Monitoring production

Exception tracking

Two ways to do it

  • try ... catch
  • Logger way
  • Combination of the two

appsignal-elixir

Catching exceptions: Plug

use Appsignal.Plug

...

conn = Plug.Conn.put_private(conn, 
                             :appsignal_transaction,
                              transaction)

try do
  super(conn, opts)
catch
  kind, reason -> Appsignal.Plug.handle_error(
                  conn, kind, reason,
                  System.stacktrace())
else
  conn -> Appsignal.Plug.finish_with_conn(
                           transaction,
                           conn)
end

More on our blog:

Catching exceptions: logs

:logger.add_handler(
  :appsignal,
  Appsignal.LoggerHandler, %{}
 )

While we're at it...

:telemetry.attach(
  "appsignal-ecto",
  [:my_app, :repo, :query],
  &Appsignal.Ecto.handle_event/4,
  nil
)

:telemetry

:telemetry - dynamic dispatch library

:telemetry.execute(
  [:web, :request, :done],
  %{latency: latency},
  %{request_path: path, status_code: status}
)

:telemetry - dynamic dispatch library

defmodule LogResponseHandler do
  require Logger

  def handle_event([:web, :request, :done], 
                   measurements, metadata,
                   _config) do
    Logger.info(
      "[#{metadata.request_path}] 
        #{metadata.status_code}
        sent in #{measurements.latency}")
  end
end

:telemetry - dynamic dispatch library

:ok = :telemetry.attach(
  # unique handler id
  "log-response-handler",
  [:web, :request, :done],
  &LogResponseHandler.handle_event/4,
  nil
)

:telemetry notes

  • tiny library
  • attach happens at runtime
  • ideal to be used by library authors
  • is becoming de-facto instrumentation standard in Elixir (and Erlang?) ecosystem
  • telemetry_metrics, telemetry_poller

Distributed tracing

OpenTelemetry

OpenCensus + OpenTracing

OpenTelemetry

  • Defines a standard for distributed tracing
  • Implementation independent
  • Multiple back-ends (Jager, DataDog etc.)
  • traces - outer containers for actions
  • spans - inner containers for actions
  • automatic trace_id propagation
  • spandex

Thank you!

Keeping tabs on production

By Hubert Łępicki

Keeping tabs on production

  • 1,466