Packages
mob
0.5.18
0.7.20
0.7.19
0.7.18
0.7.17
0.7.16
0.7.15
0.7.14
0.7.13
0.7.12
0.7.11
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.26
0.6.25
0.6.24
0.6.23
0.6.22
0.6.21
0.6.20
0.6.19
0.6.18
0.6.17
0.6.16
0.6.15
0.6.14
0.6.13
0.6.12
0.6.11
0.6.10
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.2
0.6.1
0.6.0
0.5.18
0.5.17
0.5.16
0.5.15
0.5.14
0.5.11
0.5.10
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.0
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.0
0.1.0
BEAM-on-device mobile framework for Elixir
Current section
Files
Jump to
Current section
Files
lib/mob/native_logger.ex
defmodule Mob.NativeLogger do
@moduledoc """
OTP logger handler that routes Elixir Logger output to the platform's native
system log.
- **Android** — `mob_nif:log/2` → `android.util.Log` → `adb logcat`
- **iOS** — `mob_nif:log/2` → `NSLog` → unified system log (Xcode console,
`xcrun simctl spawn booted log stream`)
Each message appears with the correct priority level (D/I/W/E) under the
tag `Elixir`. OTP supervision reports, GenServer crashes, and all
`Logger.info/warn/error` calls are captured from the first BEAM instruction,
including boot-time failures that happen before Erlang distribution comes up.
## Usage
Call `install/0` once after `application:start(logger)` in your BEAM entry
point (e.g. `mob_demo.erl`):
'Elixir.Mob.NativeLogger':install()
On the host Mix environment (`:host` platform) the call is a no-op, so the
same boot file works unchanged during `mix test` and local development.
## Relationship to Erlang distribution
This handler and dist-based log forwarding are complementary. Native logging
is always-on and survives connection drops; dist forwarding surfaces logs in
the mob_dev dashboard. Both can be active simultaneously — OTP supports
multiple logger handlers.
"""
@handler_id :mob_native_logger
@doc """
Installs the native system log handler.
Checks the platform via the NIF; if `:host`, returns `:ok` without adding
any handler. Safe to call multiple times.
Options:
- `:nif` — NIF module to use (default `:mob_nif`; override in tests)
"""
@spec install(keyword()) :: :ok
def install(opts \\ []) do
nif = Keyword.get(opts, :nif, :mob_nif)
if nif.platform() in [:android, :ios] do
case :logger.add_handler(@handler_id, __MODULE__, %{nif: nif}) do
:ok -> :ok
{:error, {:already_exist, @handler_id}} -> :ok
end
else
:ok
end
end
# ── OTP logger handler callback ───────────────────────────────────────────
@doc false
@spec log(map(), map()) :: :ok
def log(%{level: level, msg: msg, meta: meta}, %{nif: nif}) do
text = format_msg(msg, meta)
nif.log(level_to_nif(level), text)
end
# ── Helpers (public for testing) ──────────────────────────────────────────
@doc false
@spec format_msg(term(), map()) :: String.t()
def format_msg({:string, msg}, _meta), do: IO.iodata_to_binary(msg)
def format_msg({:report, report}, _meta), do: inspect(report)
def format_msg({:format, fmt, args}, _meta) do
:io_lib.format(fmt, args) |> IO.iodata_to_binary()
end
def format_msg(msg, _meta), do: inspect(msg)
@doc false
@spec level_to_nif(:logger.level()) :: :debug | :info | :warning | :error
def level_to_nif(:debug), do: :debug
def level_to_nif(:info), do: :info
def level_to_nif(:notice), do: :info
def level_to_nif(:warning), do: :warning
def level_to_nif(:error), do: :error
def level_to_nif(:critical), do: :error
def level_to_nif(:alert), do: :error
def level_to_nif(:emergency), do: :error
def level_to_nif(_), do: :info
end