🌲 Timber - Log Better. Solve Problems Faster.

ISC LicenseHex.pmDocumentationBuild Status

Overview

Timber is a logging platform with one major difference: Instead of parsing, which relies on unreadable, unpredictable, hard to use text logs, Timber integrates directly with your application, producing rich structured events containing metadata and context you couldn't capture otherwise. It fundamentally changes the way you use your logs.

  1. Easy setup - mix timber.install
  2. Seamlessly integrates with popular libraries and frameworks
  3. Modern fast console, designed specifically for your application

Installation

  1. Add timber as a dependency in mix.exs:

    # Mix.exs
    def application do
    [applications: [:timber]]
    end
    def deps do
    [{:timber, "~> 2.1"}]
    end
  2. In your shell, run mix deps.get.

  3. In your shell, run mix timber.install.

Usage

Basic text logging

No special API, Timber works directly with Logger:

Logger.info("My log message")
# => My log message @metadata {"level": "info", "context": {...}}

Structured logging (metadata)

Simply use Elixir's native Logger metadata:

Logger.info("Payment rejected", meta: %{customer_id: "abcd1234", amount: 100, currency: "USD"})
# => My log message @metadata {"level": "info", "meta": {"customer_id": "abcd1234", "amount": 100}}

Custom events

Events are just defined structures with a namespace. They are more formal and avoid type collisions. Custom events, specifically, allow you to extend beyond events already defined in the Timber.Events namespace.

event_data = %{customer_id: "xiaus1934", amount: 1900, currency: "USD"}
Logger.info("Payment rejected", event: %{payment_rejected: event_data})
# => Payment rejected @metadata {"level": "warn", "event": {"payment_rejected": {"customer_id": "xiaus1934", "amount": 100, "reason": "Card expired"}}, "context": {...}}

Custom contexts

Context is additional data shared across log lines. Think of it like log join data. It's stored in the local process dictionary and is incldued in every log written within that process. Custom contexts allow you to extend beyond contexts already defined in the Timber.Contexts namespace.

Timber.add_context(build: %{version: "1.0.0"})
Logger.info("My log message")
# => My log message @metadata {"level": "info", "context": {"build": {"version": "1.0.0"}}}

Metrics

Logging metrics is accomplished by logging custom events. Please see our metrics docs page for a more detailed explanation with examples.


Adding metadata to your errors

By default, Timber will capture and structure all of your errors and exceptions, there is nothing additional you need to do. You'll get the exception message, name, and backtrace. But, in many cases you need additional context and data. Timber supports additional fields in your exceptions, simply add fields as you would any other struct:

defmodule StripeCommunicationError do
defexception [:message, :customer_id, :card_token, :stripe_response]
end
raise(
StripeCommunicationError,
message: "Bad response #{response} from Stripe!",
customer_id: "xiaus1934",
card_token: "mwe42f64",
stripe_response: response_body
)

Searching, graphing, alerting, etc

Checkout the official Timber console docs. It walks you through everything from our search syntax to alerting and graphin.


Integrations

Phoenix

The Phoenix integration structures your existing Phoenix logs into controller_call and template_render events.

Pro-tip: this integration captures the parameters sent to your controller, making it easy to debug issues by understanding exactly which data was sent to your controller.

Installation

To install this integration, please run the mix timber.install command as noted in the installation section.

For manual installation, please see the Timber.Integrations.PhoenixInstrumenter docs.


Ecto

The Ecto integration structures your existing Ecto logs into structured sql_query events.

Pro-tip: this integration captures SQL query times, making it easy to visualize SQL query performance and find slow queries.

Installation

To install this integration, please run the mix timber.install command as noted in the installation section.

For manual installation, please see the Timber.Integrations.EctoLogger docs.


Plug

The Plug integration structures your existing Plug logs into http_request and http_response events.

Pro-tip: this integration captures HTTP response codes and times, making it easy to visualize the performance of your application.

Installation

To install this integration, please run the mix timber.install command as noted in the installation section.

For manual installation, please see the Timber.Integrations.EventPlug, Timber.Integrations.HTTPContextPlug, and Timber.Integrations.SessionContextPlug docs. We highly recommend using the installer!


ExAws

The ExAws integration logs and structures outgoing AWS HTTP communication via the http_request and http_response events. This gives you complete insight into how your application is communicating with AWS services, including timings, errors, etc.

By default this will only log change requests (POST, PUT, DELETE, PATCH). This reduces noise while still logging requests that are meaningful. Please see the Timber.Integrations.ExAwsHTTPClient docs docs for more configuration options.

Installation

config :ex_aws,
http_client: Timber.Integrations.ExAwsHTTPClient

For more details, please see the Timber.Integrations.ExAwsHTTPClient docs.


Jibber-Jabber

Which log events does Timber structure for me?

Out of the box you get everything in the Timber.Events namespace.

We also add context to every log, everything in the Timber.Contexts namespace. Context is structured data representing the current environment when the log line was written. It is included in every log line. Think of it like join data for your logs.


What about my current log statements?

They'll continue to work as expected. Timber adheres strictly to the default Logger interface and will never deviate in any way.

In fact, traditional log statements for non-meaningful events, debug statements, etc, are encouraged. In cases where the data is meaningful, consider logging a custom event.

When to use metadata or events?

At it's basic level, both metadata and events serve the same purpose: they add structured data to your logs. And anyone that's implemented structured logging know's this can quickly get out of hand. This is why we created events. Here's how we recommend using them:

  1. Use events when the log cleanly maps to an event that you'd like to alert on, graph, or use in a meaningful way. Typically something that is core to your business or application.
  2. Use metadata for debugging purposes; when you simply want additional insight without polluting the message.

Example 1: Logging that a payment was rejected

This is clearly an event that is meaningful to your business. You'll probably want to alert and graph this data. So let's log it as an official event:

event_data = %{customer_id: "xiaus1934", amount: 1900, currency: "USD"}
Logger.info("Payment rejected", event: %{payment_rejected: event_data})

Example 2: Logging that an email was changed

This is definitely log worthy, but not something that is core to your business or application. Instead of an event, use metadata:

Logger.info("Email successfully changed", meta: %{old_email: old_email, new_email: new_email})

The Timber Console

Timber Console

Your Moment of Zen