TelemetricEvents (telemetric_events v0.1.0)

Changes the logging and metric paradigm to event emission rather than the modules taking care of their own logging and metrics. Uses :telemetry to receive the events and route them to different handlers. Combines Logger and Prometheus to process the events.

There is a setup function: setup_handler/1. The argument is a module. See the Prometheus section for more information.

The main function you will be interacting with is emit_event/2. It simply makes use of :telemetry.

Telemetry

:telemetry routes events to their handlers based on the event name. This package uses the pattern app, type, action for the event name. So for example, if you had an app called blog_collector that had a webhook that listened for new blogs being published, normalized the text, and then inserted the blog into a database, you could feasibly have three events from that process (or more or less). That would mean you'd need three event names: [:blog_collector, :blog, :received], [:blog_collector, :blog, :normalized], [:blog_collector, :blog, :inserted].

The configuration for the metrics will also follow this naming pattern:

config :blog_collector, blog: [
  recieved: metric,
  normalized: metric,
  inserted: metric
]

I will come back to further metric setup soon, but first I will finish up :telemetry usage and then touch on Logger usage.

:telemetry will also be given a map alongside the event name. The map must have the values that will be placed in the configured Prometheus labels for that event (more on that later). The map can have any other number of key-value pairs that won't effect the metrics, they will simply be added to the logs. There will also be two forms you can use for emit_event/2: full event name or just the type with with the action as part of the map. emit_event([:blog_collector, :blog, :received], any_map) and emit_event(:blog, %{any_map | action: "received"}) respectively.

Note: If an error is raised by the functions that are given to :telemetry, the handler will detach. I will try to mitigate that as much as possible, but until :telemetry has a way of broadcasting that they've detached the handler, mitigation is all I can do.

Logger

Logging an event just uses the standard Logger package. In the future (as needed) I will support other logging packages, but to be honest I don't know of anyone that uses anything other than Logger. However, if you need Logger to perform differently than the way I configure it in this package and your configuration disrupts the performance of this package, please submit an issue and I will try to accommodate.

I am presupposing an ELK stack implementation of log collection because, to date, that has been the best process for easily used logs. Therefore, logs will be emitted as maps/JSON. Even Logger statements outside of the event will be wrapped in a map through Logger's configuration.

E.g.

{
  "app": "blog_collector",
  "type": "blog",
  "action": "received",
  "payload_size": 239,
  "blog_subdomain": "example-health-tips",
  "message": "Recieved blog post with title: "Fit and Fabulous in 15 days!"",
  "timestamp": "2015-06-24T05:04:13.293Z"
}

Note: Version 0.1.0 won't have the logging implemented. I'm working on an app that will include this package. It's not at a place where it's ready to implement this logging strategy. So to get it up and going, I'm skipping logging for now.

Prometheus

Prometheus is more hands on in it's setup. Take the configuration from above; where it says metric, you can put in a few options. Each option corresponds to a Prometheus metric type. Each has a different tuple for configuration:

  1. {:counter, name :: atom(), labels :: [atoms()], help :: String.t()}
  2. {:gauge, name :: atom(), labels :: [atoms()], help :: String.t()}
  3. {:histogram, name :: atom(), labels :: [atoms()], buckets :: [integer()], help :: String.t()}
  4. {:summary, name :: atom(), labels :: [atoms()], quantiles :: [integer()], help :: String.t()}

Note: Last time I checked the summary metric from :prometheus didn't work properly, but counters, gauges and histograms are available.

Remember that anything you put in the labels for the metric must have values under that key in the map passed to emit_event/2. E.g. if you have :payload_size as a label you must have payload_size: some_int in the map. If you just want logging and not metrics then just don't define any metrics for that event name.

Then you just create a module like so:

defmodule ExampleApp.TelemetricEvents.Prometheus do
  use TelemetricEvents.Prometheus
end

Plug Exporter

Then all you need is a way for Prometheus to reach your server. PlugExporter will let you configure an endpoint that Prometheus will query to collect the metrics.

And that's about it. As long as your metrics and event match up it should just work. If anything is wrong, please write an issue for it. I am planning on actively developing this project.

Link to this section Summary

Functions

This will be the most commonly used function of this package. It simply makes use of :telemetry. :telemetry routes events to their handlers based on the event name. This package uses the pattern app, type, action for the event name.

Call this function in your application.ex file with the module you created using TelemetricEvents.Prometheus.

Link to this section Types

Link to this type

event_name()

Specs

event_name() :: {app :: atom(), type(), action :: atom()} | type()
Link to this type

observation()

Specs

observation() :: %{action: atom()} | %{optional(atom()) => term()}

Specs

type() :: atom()

Link to this section Functions

Link to this function

emit_event(event_name, observation)

Specs

emit_event(event_name(), observation()) :: :ok

This will be the most commonly used function of this package. It simply makes use of :telemetry. :telemetry routes events to their handlers based on the event name. This package uses the pattern app, type, action for the event name.

The event_name can be either the full event name or more likely just the type. If the event_name is just the type then the action must be part of the observation map.

E.g. emit_event([:blog_collector, :blog, :received], any_map) and emit_event(:blog, %{any_map | action: "received"}) respectively.

Link to this function

setup_handler(module)

Specs

setup_handler(module()) :: :ok

Call this function in your application.ex file with the module you created using TelemetricEvents.Prometheus.

This will setup the configured metrics and attach a handler for each event name that will route to the proper observe/2 function.