Packages
timber
2.4.3
3.1.2
3.1.1
3.1.0
3.0.0
3.0.0-alpha.3
3.0.0-alpha.2
3.0.0-alpha.1
2.8.4
2.8.3
2.8.2
2.8.1
2.8.0
2.7.0
2.6.1
2.6.0
2.5.6
2.5.5
2.5.4
2.5.3
2.5.2
2.5.1
2.5.0
2.4.5
2.4.4
2.4.3
2.4.2
2.4.1
2.4.0
2.3.4
2.3.3
2.3.1
2.3.0
2.2.1
2.2.0
2.1.8
2.1.7
2.1.6
2.1.5
2.1.4
2.1.3
2.1.2
2.1.1
2.1.0
2.0.2
2.0.1
2.0.0
2.0.0-rc7
2.0.0-rc6
2.0.0-rc5
2.0.0-rc4
2.0.0-rc3
2.0.0-rc2
2.0.0-rc1
1.1.18
1.1.17
1.1.16
1.1.15
1.1.14
1.1.13
1.1.12
1.1.11
1.1.10
1.1.9
1.1.8
1.1.7
1.1.6
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.0.16
1.0.15
1.0.14
1.0.13
1.0.12
1.0.11
1.0.10
1.0.9
1.0.8
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.2.3
0.2.2
0.2.1
0.2.0
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
🌲 Great Elixir Logging Made Easy. Official Timber.io Integration.
Current section
Files
Jump to
Current section
Files
lib/timber/log_entry.ex
defmodule Timber.LogEntry do
@moduledoc """
The LogEntry module formalizes the structure of every log entry.
When a log is produced, it is converted to this intermediary form
by the `Timber.LoggerBackend` module before being passed on to the desired
transport. Each transport implements a `write/2` function as defined
by the `Timber.Transport.write/2` behaviour. Inside of this function,
the transport is responsible for formatting the data contained in a
log entry appropriately.
Each log entry consists of the log message, its level, the timestamp
it was logged at, a context map, and an optional event.
See the main `Timber` module for more information.
"""
alias Timber.Config
alias Timber.Context
alias Timber.Contexts.RuntimeContext
alias Timber.Contexts.SystemContext
alias Timber.CurrentContext
alias Timber.LoggerBackend
alias Timber.Event
alias Timber.Eventable
alias Timber.Utils.JSON
alias Timber.Utils.Module, as: UtilsModule
alias Timber.Utils.Timestamp, as: UtilsTimestamp
alias Timber.Utils.Map, as: UtilsMap
alias Timber.LogfmtEncoder
defstruct [:dt, :level, :message, :meta, :event, :tags, :time_ms, context: %{}]
@type format :: :json | :logfmt
@type t :: %__MODULE__{
dt: IO.chardata,
level: LoggerBackend.level,
message: LoggerBackend.message,
context: Context.t,
event: nil | Event.t,
meta: nil | Map.t,
tags: nil | [String.t],
time_ms: nil | float
}
@schema "https://raw.githubusercontent.com/timberio/log-event-json-schema/v3.0.5/schema.json"
@doc """
Creates a new `LogEntry` struct
The metadata from Logger is given as the final parameter. If the
`:timber_context` key is present in the metadata, it will be used
to fill the context for the log entry. Otherwise, a blank context
will be used.
"""
@spec new(LoggerBackend.timestamp, Logger.level, Logger.message, Keyword.t) :: t
def new(timestamp, level, message, metadata) do
dt_iso8601 =
if Config.use_nanosecond_timestamps? do
DateTime.utc_now()
|> DateTime.to_iso8601()
else
timestamp
|> UtilsTimestamp.format_timestamp()
|> IO.chardata_to_string()
end
context =
metadata
|> CurrentContext.extract_from_metadata()
|> add_runtime_context(metadata)
|> add_system_context()
meta = Keyword.get(metadata, :meta)
{message, event} = extract_message_and_event(metadata, message)
tags = Keyword.get(metadata, :tags)
time_ms = Keyword.get(metadata, :time_ms)
%__MODULE__{
dt: dt_iso8601,
level: level,
message: message,
context: context,
event: event,
meta: meta,
tags: tags,
time_ms: time_ms
}
end
# Add the default Elixir Logger runtime metadata as runtime context.
defp add_runtime_context(context, metadata) do
application = Keyword.get(metadata, :application)
module_name = Keyword.get(metadata, :module)
module_name =
if module_name do
UtilsModule.name(module_name)
else
module_name
end
fun = Keyword.get(metadata, :function)
file = Keyword.get(metadata, :file)
line = Keyword.get(metadata, :line)
runtime_context =
%RuntimeContext{
application: application,
module_name: module_name,
function: fun,
file: file,
line: line
}
Context.add(context, runtime_context)
end
defp add_system_context(context) do
hostname =
case :inet.gethostname() do
{:ok, hostname} -> to_string(hostname)
_else -> nil
end
pid = System.get_pid()
system_context = %SystemContext{hostname: hostname, pid: pid}
Context.add(context, system_context)
end
# Attemps to extract the message and event from the given logger metadata and message.
# We take the message so that we can convert upstream error logger messages into actual
# `ErrorEvent.t` events.
defp extract_message_and_event(metadata, message) do
case Event.extract_from_metadata(metadata) do
nil ->
if Keyword.has_key?(metadata, :error_logger) do
case Timber.Events.ErrorEvent.from_log_message(to_string(message)) do
{:ok, event} ->
message = Timber.Events.ErrorEvent.message(event)
{message, event}
{:error, _reason} ->
{message, nil}
end
else
{message, nil}
end
data ->
event = Eventable.to_event(data)
{message, event}
end
end
def schema, do: @schema
@doc """
Encodes the log event to chardata
## Options
- `:only` - A list of key names. Only the key names passed will be encoded.
"""
@spec to_iodata!(t, format, Keyword.t) :: iodata
def to_iodata!(log_entry, format, options \\ []) do
log_entry
|> to_map!(options)
|> encode_to_iodata!(format)
end
@spec to_map!(t, Keyword.t) :: map()
def to_map!(log_entry, options \\ []) do
map =
log_entry
|> Map.from_struct()
|> Map.update(:event, nil, fn existing_event ->
if existing_event != nil do
Event.to_api_map(existing_event)
else
existing_event
end
end)
only = Keyword.get(options, :only, false)
if only do
Map.take(map, only)
else
map
end
|> Map.put(:"$schema", @schema)
|> UtilsMap.recursively_drop_blanks()
end
@spec encode_to_iodata!(format, map) :: iodata
defp encode_to_iodata!(value, :json) do
JSON.encode_to_iodata!(value)
end
# The logfmt encoding will actually use a pretty-print style
# of encoding rather than converting the data structure directly to
# logfmt
defp encode_to_iodata!(value, :logfmt) do
context =
case Map.get(value, :context) do
nil -> []
val -> [?\n, ?\t, "Context: ", LogfmtEncoder.encode!(val)]
end
event =
case Map.get(value, :event) do
nil -> []
val -> [?\n, ?\t, "Event: ", LogfmtEncoder.encode!(val)]
end
meta =
case Map.get(value, :meta) do
nil -> []
val -> [?\n, ?\t, "Meta: ", LogfmtEncoder.encode!(val)]
end
[context, event, meta]
end
end