LogDot SDK for Elixir
Cloud logging and metrics made simple
Website • Documentation • Quick Start • API Reference
Features
- Separate Clients — Independent Logger and Metrics GenServers for maximum flexibility
- Context-Aware Logging — Create loggers with persistent context that automatically flows through your application
- OTP Compliant — Built as proper OTP application with supervision tree
- Entity-Based Metrics — Create/find entities, then bind to them for organized metric collection
- Batch Operations — Efficiently send multiple logs or metrics in a single request
- Automatic Retry — Exponential backoff retry with configurable attempts
Installation
Add logdot to your dependencies in mix.exs:
def deps do
[
{:logdot, "~> 1.0"}
]
endConfiguration
# config/config.exs
config :logdot,
api_key: "ilog_live_YOUR_API_KEY",
hostname: "my-app",
timeout: 5000,
retry_attempts: 3,
debug: falseQuick Start
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# LOGGING
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
LogDot.info("Application started", %{version: "1.0.0"})
LogDot.error("Something went wrong", %{error_code: 500})
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# METRICS
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Create or find an entity first
{:ok, entity} = LogDot.Metrics.get_or_create_entity(
"my-service",
"My production service",
%{environment: "production"}
)
# Bind to the entity for sending metrics
:ok = LogDot.Metrics.for_entity(entity.id)
# Now send metrics
LogDot.metric("response_time", 123.45, "ms", %{endpoint: "/api/users"})Logging
Log Levels
LogDot.debug("Debug message")
LogDot.info("Info message")
LogDot.warn("Warning message")
LogDot.error("Error message")Structured Tags
LogDot.info("User logged in", %{
user_id: 12345,
ip_address: "192.168.1.1",
session_id: "abc123"
})Context-Aware Logging
Create loggers with persistent context that automatically flows through your application:
# Create a context for a specific request
ctx = LogDot.with_context(%{request_id: "abc-123", user_id: 456})
# All logs include request_id and user_id automatically
LogDot.info(ctx, "Processing request", %{})
LogDot.debug(ctx, "Fetching user data", %{})
# Chain contexts — they merge together
detailed_ctx = LogDot.with_context(ctx, %{operation: "checkout"})
# This log has request_id, user_id, AND operation
LogDot.info(detailed_ctx, "Starting checkout process", %{})Batch Logging
Send multiple logs in a single HTTP request:
LogDot.begin_batch()
LogDot.info("Step 1 complete")
LogDot.info("Step 2 complete")
LogDot.info("Step 3 complete")
LogDot.send_batch() # Single HTTP request
LogDot.end_batch()Metrics
Entity Management
# Create a new entity
{:ok, entity} = LogDot.Metrics.create_entity(
"my-service",
"Production API server",
%{environment: "production", region: "us-east-1"}
)
# Find existing entity
{:ok, existing} = LogDot.Metrics.get_entity_by_name("my-service")
# Get or create (recommended)
{:ok, entity} = LogDot.Metrics.get_or_create_entity(
"my-service",
"Created if not exists"
)Sending Metrics
:ok = LogDot.Metrics.for_entity(entity.id)
# Single metric
LogDot.metric("cpu_usage", 45.2, "percent")
LogDot.metric("response_time", 123.45, "ms", %{
endpoint: "/api/users",
method: "GET"
})Batch Metrics
# Same metric, multiple values
LogDot.begin_metric_batch("temperature", "celsius")
LogDot.add_metric(23.5, %{sensor: "room1"})
LogDot.add_metric(24.1, %{sensor: "room2"})
LogDot.add_metric(23.8, %{sensor: "room3"})
LogDot.send_metric_batch()
LogDot.end_metric_batch()
# Multiple different metrics
LogDot.begin_multi_batch()
LogDot.add_multi_metric("cpu", 45.5, "percent")
LogDot.add_multi_metric("memory", 8192, "MB")
LogDot.add_multi_metric("disk", 75.0, "percent")
LogDot.send_metric_batch()
LogDot.end_metric_batch()Auto-Instrumentation (Phoenix)
Automatically capture Phoenix HTTP requests, errors, and Ecto database queries using
the built-in :telemetry system. No additional dependencies required.
Setup
In your application.ex:
def start(_type, _args) do
# Initialize metrics entity (required for duration metrics)
LogDot.init_metrics()
LogDot.Phoenix.attach(
ecto_prefixes: [[:my_app, :repo]]
)
children = [
# ... your supervision tree
]
Supervisor.start_link(children, strategy: :one_for_one)
endWhat Gets Captured
- Phoenix requests — Every endpoint request with method, path, status, and duration
- Phoenix errors — Errors with kind, reason, and stacktrace
- Ecto queries — Database queries with source table, query time, and queue time
- Metrics — Request and query duration (requires
LogDot.init_metrics()to be called first, which automatically creates/resolves the entity using the configured:entity_name)
Options
| Option | Type | Default | Description |
|---|---|---|---|
:endpoint | boolean | true | Log Phoenix HTTP requests |
:errors | boolean | true | Log Phoenix errors |
:ecto | boolean | true | Log Ecto database queries |
:metrics | boolean | true | Send duration metrics |
:ignore_paths | list | [] |
Paths to skip (e.g. ["/health"]) |
:ecto_prefixes | list | [] | Ecto telemetry prefixes for your repos |
:capture_logging | boolean | false |
Forward Elixir Logger calls to LogDot |
Detaching
LogDot.Phoenix.detach()Log Capture
Automatically forward Elixir Logger calls to LogDot. The original Logger behavior is preserved — messages still appear in your console and file backends as usual.
This works in any Elixir/OTP application (Plug, Bandit, standalone GenServers, Mix tasks, etc.), not just Phoenix. If you use LogDot.Phoenix.attach(capture_logging: true), the handler is installed automatically — don't also call LogDot.capture_logging() or it will return {:error, ...} since the handler is already registered.
Standalone Usage
# Start capturing
LogDot.capture_logging()
# All Logger calls are now sent to LogDot
require Logger
Logger.info("This goes to LogDot") # severity: info
Logger.error("Something failed") # severity: error
Logger.warning("Watch out") # severity: warn
Logger.debug("Debug details") # severity: debug
# Stop capturing
LogDot.stop_capture_logging()With Phoenix
When using Phoenix auto-instrumentation, pass capture_logging: true:
# application.ex
def start(_type, _args) do
LogDot.Phoenix.attach(
capture_logging: true,
ecto_prefixes: [[:my_app, :repo]]
)
children = [...]
Supervisor.start_link(children, strategy: :one_for_one)
endHow It Works
LogDot.LoggerBackend is an Erlang :logger handler:
-
Installed via
:logger.add_handler/3(OTP 21+ logging system) -
Receives all log events from the BEAM —
Logger.info/1,Logger.error/1,IO.puts/1(when:consolebackend is not the only handler), etc. -
Formats the message (supports
:string,:report, and:io_lib.formatmessage formats) - Maps the Erlang/Elixir log level to a LogDot severity
-
Sends via
LogDot.log/3(the existing GenServer-backed API) -
A process dictionary guard (
Process.get(:logdot_sending)) prevents infinite loops — when LogDot's own HTTP client triggers Logger events during delivery, those events are silently skipped - Messages longer than 16KB are truncated
-
All errors are rescued — a crashed
:telemetryhandler gets permanently detached, but a crashed:loggerhandler can also cause issues, so defensive coding is essential
Level Mapping
| Erlang/Elixir Level | LogDot Severity |
|---|---|
:emergency | error |
:alert | error |
:critical | error |
:error | error |
:warning | warn |
:warn | warn |
:notice | info |
:info | info |
:debug | debug |
Tags
All captured Logger logs include { source: "elixir_logger" } in their tags, so you can filter them from manually sent logs in the LogDot dashboard.
Manual Handler Management
For advanced usage, you can manage the handler directly:
# Add handler manually
:logger.add_handler(:logdot_backend, LogDot.LoggerBackend, %{})
# Remove handler manually
:logger.remove_handler(:logdot_backend)API Reference
LogDot (Main Module)
| Function | Description |
|---|---|
with_context(context) | Create a context struct |
with_context(ctx, context) | Chain contexts together |
get_context(ctx) | Get context map |
debug/info/warn/error(message, tags \\ %{}) | Send log at level |
debug/info/warn/error(ctx, message, tags) | Send log with context |
begin_batch() | Start batch mode |
send_batch() | Send queued logs |
end_batch() | End batch mode |
clear_batch() | Clear queue without sending |
capture_logging() | Start forwarding Logger calls to LogDot |
stop_capture_logging() | Stop forwarding Logger calls |
LogDot.Metrics
| Function | Description |
|---|---|
create_entity(name, description, metadata) | Create new entity |
get_entity_by_name(name) | Find entity by name |
get_or_create_entity(name, description, metadata) | Get or create entity |
for_entity(entity_id) | Bind to entity for metrics |
Requirements
- Elixir 1.14+
- OTP 25+
- Req HTTP client
Examples
Create a .env file in the repo root with your API key:
LOGDOT_API_KEY=ilog_live_YOUR_API_KEYCore SDK test app
Tests logging, metrics, context, and batch operations:
cd elixir
mix run --no-start examples/test_app.exsHooks test app (Phoenix + Logger Backend)
Tests Phoenix telemetry handlers and logger backend capture:
cd elixir
mix run --no-start examples/test_hooks.exsLicense
MIT License — see LICENSE for details.
logdot.io • Built with care for developers