TeleDec
Zero-dependency Elixir library for declarative telemetry instrumentation using compile-time decorators.
TeleDec provides a clean, efficient way to add telemetry events to your functions with minimal boilerplate. Simply add @telemetry attributes to your functions, and the library handles all the instrumentation at compile-time with negligible runtime overhead.
Features
- Zero Dependencies - Only requires the standard
:telemetrylibrary - Compile-Time Code Generation - All instrumentation happens at compile-time with no runtime reflection
- Auto-Inferred Event Names - Automatically generate event names from module and function names
- Umbrella Project Support - Per-module app configuration for multi-app monorepos
- Multiple Modes - Choose between full span tracking or fast one-shot events
- Performance Optimized - Minimal overhead (~700ns for span mode, ~300ns for one-shot mode)
- Flexible Configuration - Global, per-module, and per-function configuration options
- Selective Metadata - Capture only the arguments and variables you need
Installation
Add tele_dec to your list of dependencies in mix.exs:
def deps do
[
{:tele_dec, "~> 0.1.0"}
]
endQuick Start
Basic Usage with Auto-Inferred Names
Option 1: Per-module app (recommended for umbrella projects)
defmodule MyApp.UserService do
use TeleDec, app: :my_app # App specified here
@telemetry_service :user_service
@telemetry true
def create_user(attrs) do
# Your implementation
# Emits: [:my_app, :user_service, :create_user, :start/:stop/:exception]
end
@telemetry include: [:user_count]
def list_users() do
users = fetch_users()
user_count = length(users) # This will be included in telemetry metadata
users
end
endOption 2: Global config (simpler for single-app projects)
# config/config.exs
config :tele_dec, app: :my_app
# lib/my_app/user_service.ex
defmodule MyApp.UserService do
use TeleDec # Uses config for app name
@telemetry_service :user_service
@telemetry true
def create_user(attrs) do
# Emits: [:my_app, :user_service, :create_user, :start/:stop/:exception]
end
endExplicit Event Names
defmodule MyApp.Service do
use TeleDec
@telemetry {[:my_app, :service, :process], []}
def process(data) do
# Implementation
# Emits: [:my_app, :service, :process, :start/:stop/:exception]
end
endConfiguration
Global Configuration
Set in config/config.exs:
config :tele_dec,
app: :my_app, # Application name prefix for auto-inferred events
enabled: true # Global enable/disable flag (default: true)Module Configuration
defmodule MyApp.Service do
use TeleDec
# Set service name for auto-inferred event names
@telemetry_service :service_name
# Functions...
endPer-Module App Configuration
For umbrella projects or multi-app monorepos, you can specify the app name per-module, overriding the global config:
# In app1/lib/my_app/user_service.ex
defmodule MyApp.UserService do
use TeleDec, app: :my_app # Explicit app for this module
@telemetry_service :users
@telemetry true
def create_user(attrs) do
# Emits: [:my_app, :users, :create_user, :start/:stop/:exception]
end
end
# In app2/lib/other_app/order_service.ex
defmodule OtherApp.OrderService do
use TeleDec, app: :other_app # Different app!
@telemetry_service :orders
@telemetry true
def process_order(id) do
# Emits: [:other_app, :orders, :process_order, :start/:stop/:exception]
end
endApp Name Priority:
-
Explicit module option (
use TeleDec, app: :my_app) - highest priority -
Global config (
config :tele_dec, app: :my_app) - fallback - No app prefix if neither is set
Function Options
# Auto-infer event name
@telemetry true
# Auto-infer with options
@telemetry include: [:computed_value]
# Explicit event name
@telemetry {[:custom, :event], [include: [:var1]]}
# Available options:
# - mode: :span (default) or :one_shot
# - include: list of variables to include in stop event metadata
# - args: list of specific arguments to capture (default: all)
# - metadata: set to false to skip all metadata capture
# - enabled: set to false to disable at compile-timePerformance Modes
Span Mode (default)
Full telemetry span with start, stop, and exception events:
@telemetry true
def process(data) do
# ~700ns overhead
endOne-Shot Mode
Single event on completion, approximately 2x faster:
@telemetry mode: :one_shot
def fast_operation(x, y) do
# ~300ns overhead
# No start event, no exception tracking
endMetadata-Free Mode
Maximum performance, no metadata capture:
@telemetry metadata: false
def high_frequency_operation() do
# Minimal overhead
endPerformance Characteristics
TeleDec is designed for minimal overhead:
- Baseline function call: ~27ns
- Disabled telemetry (
enabled: false): ~32ns (5ns overhead) - One-shot mode: ~300ns
- Span mode (full instrumentation): ~700ns
All metadata capture happens at compile-time via direct map construction, avoiding runtime reflection and Kernel.binding() calls.
Event Structure
Span Mode Events
event_name ++ [:start]- Emitted when function starts-
Measurements:
%{monotonic_time: integer} - Metadata: Function arguments
-
Measurements:
event_name ++ [:stop]- Emitted when function completes successfully-
Measurements:
%{duration: integer} - Metadata: Arguments + result + included variables
-
Measurements:
event_name ++ [:exception]- Emitted when function raises-
Measurements:
%{duration: integer} - Metadata: Arguments + error info (kind, reason, stacktrace)
-
Measurements:
One-Shot Mode Events
event_name- Single event emitted on completion-
Measurements:
%{duration: integer} - Metadata: Arguments + result + included variables
- No exception tracking
-
Measurements:
Attaching Handlers
Use standard :telemetry functions to attach handlers:
:telemetry.attach_many(
"my-handler",
[
[:my_app, :user_service, :create_user, :start],
[:my_app, :user_service, :create_user, :stop],
[:my_app, :user_service, :create_user, :exception]
],
fn event, measurements, metadata, config ->
# Handle event
end,
%{some: :config}
)Documentation
Full documentation is available on HexDocs.
License
MIT License - see LICENSE for details.
Contributing
Issues and pull requests are welcome on GitHub.