# OpenTelemetry.Honeycomb
[![Build status badge](https://github.com/garthk/opentelemetry_honeycomb/workflows/Elixir%20CI/badge.svg)](https://github.com/garthk/opentelemetry_honeycomb/actions)
[![Hex version badge](https://img.shields.io/hexpm/v/opentelemetry_honeycomb.svg)](https://hex.pm/packages/opentelemetry_honeycomb)
<!-- MDOC !-->
`opentelemetry_honeycomb` provides an in-process [OpenTelemetry] exporter for [Honeycomb].
[OpenTelemetry]: https://opentelemetry.io
[Honeycomb]: https://www.honeycomb.io
## Installation
Add `opentelemetry`, `opentelemetry_api`, and `opentelemetry_honeycomb` to your `deps` in
`mix.exs`:
```elixir
{:opentelemetry, "~> 0.5.0"},
{:opentelemetry_api, "~> 0.5.0"},
{:opentelemetry_honeycomb, "~> 0.5.0-rc.1"},
```
If you're using the default back ends, you'll also need `hackney` and `poison`:
```elixir
{:hackney, ">= 1.11.0"},
{:poison, ">= 1.5.0"},
```
## Configuration
<!-- CDOC !-->
A compact `config/config.exs` for `opentelemetry_honeycomb` is:
```elixir
use Config
# You can also supply opentelemetry resources using environment variables, eg.:
# OTEL_RESOURCE_ATTRIBUTES=service.name=name,service.namespace=namespace
config :opentelemetry, :resource,
service: [
name: "service-name",
namespace: "service-namespace"
]
config :opentelemetry,
processors: [
otel_batch_processor: %{
exporter:
{OpenTelemetry.Honeycomb.Exporter, write_key: System.get_env("HONEYCOMB_WRITEKEY")}
}
]
```
`processors` specifies `otel_batch_processor`, which specifies `exporter`, a 2-tuple of the
exporter's module name and options to be supplied to its `init/1`. Our exporter takes a list of
`t:OpenTelemetry.Honeycomb.Config.config_opt/0` as its options.
<!-- CDOC !-->
## Attribute Handling
<!-- ADOC !-->
OpenTelemetry supports a flat map of attribute keys to string, number, and boolean values (see
`t.OpenTelemetry.attribute_value/0`). The API does not _enforce_ this, implicitly supporting other
attribute value types _eg._ maps until export time.
Honeycomb expects a flat JSON-serialisable object, but can be configured to flatten maps and
stringify arrays at import time.
The data models being quite similar, we:
* Pass string, number, and boolean values through unmodified
* Flatten map values as described below
* Convert most other values to strings using `inspect/1` with a short `limit`
* Trim string values longer than [49127 bytes]
[49127 bytes]: https://docs.honeycomb.io/authentication-and-security/secure-tenancy/#product-limitations-when-using-secure-tenancy
<!-- TRIMDOC !-->
When trimming strings, we replace the last 3-7 characters of the trimmed string or so with an
ellipsis (`"..."`) of equal length. We choose the length of the ellipsis to avoid ending the
trimmed string with a high-bit character, _eg._ splitting a UTF-8 code point.
<!-- TRIMDOC !-->
We drop:
* Entire attribute lists that don't start as a list or map
* Entire list members that don't resemble key/value pairs
When flattening maps, we use periods (`.`) to delimit keys, for example this input:
```elixir
%{
http: %{
host: "localhost",
method: "POST",
path: "/api"
}
}
```
... to this output:
```elixir
%{
"http.host" => "localhost",
"http.method" => "POST",
"http.path" => "/api",
}
```
<!-- ADOC !-->
<!-- MDOC !-->
## Verification
First, we need to check `:opentelemetry` and `:opentelemetry_api`. Fire up `iex -S mix` and paste
in the following code to install the `:otel_exporter_stdout` exporter:
```elixir
:otel_batch_processor.set_exporter(:otel_exporter_stdout, [])
```
After a delay, you should see:
```plain
*SPANS FOR DEBUG*
*SPANS FOR DEBUG*
*SPANS FOR DEBUG*
```
Now, paste in some trace-sending code:
```elixir
require OpenTelemetry.Tracer
OpenTelemetry.Tracer.with_span "example" do
IO.inspect(OpenTelemetry.Tracer.current_span_ctx())
OpenTelemetry.Tracer.set_attribute(:a, 1)
OpenTelemetry.Tracer.set_attributes(b: 2, c: 3)
OpenTelemetry.Tracer.set_attributes(d: %{e: "f"})
end
```
You should get output resembling:
```erlang
{span,142297071326187490948809128380223875835,10977461588491893807,undefined,
undefined,<<"example">>,'INTERNAL',-576460738804789000,
-576460738804601000,
[{a,1},{b,2},{c,3},{d,#{e => <<"f">>}}],
[],[],undefined,1,false,undefined}
```
Next, we need to check `OpenTelemetry.Honeycomb.Exporter`. Paste in:
```elixir
# hard way
:otel_batch_processor.set_exporter(OpenTelemetry.Honeycomb.Exporter,
http_module: OpenTelemetry.Honeycomb.Http.ConsoleBackend,
write_key: "HONEYCOMB_WRITEKEY"
)
# easy way
OpenTelemetry.Honeycomb.Http.ConsoleBackend.activate()
```
Paste in the trace-sending code again to see what the OpenTelemetry Honeycomb Exporter would have
sent to Honeycomb:
```plain
POST /1/batch/opentelemetry HTTP/1.1
Host: api.honeycomb.io
Content-Type: application/json
User-Agent: opentelemetry_honeycomb/0.3.0-rc.0
X-Honeycomb-Team: HONEYCOMB_WRITEKEY
[
{
"time": "2020-04-24T06:12:16.698425Z",
"samplerate": 1,
"data": {
"trace.trace_id": "6c14288156831d40602dc1f5a61489c0",
"trace.span_id": "377fce3346b92811",
"trace.parent_id": null,
"service.namespace": "service-namespace",
"service.name": "service-name",
"name": "example",
"duration_ms": 6.06005859375,
"d.e": "f",
"c": 3,
"b": 2,
"a": 1
}
}
]
```
Restore your configured exporter by pasting:
```elixir
OpenTelemetry.Honeycomb.Http.ConsoleBackend.deactivate()
```
## Development
Dependency management:
* `mix deps.get` to get your dependencies
* `mix deps.compile` to compile them
* `mix licenses` to check their license declarations, recursively
Finding problems:
* `mix compile` to compile your code
* `mix credo` to suggest more idiomatic style for it
* `mix dialyzer` to find problems static typing might spot... *slowly*
* `mix test` to run unit tests
* `mix test.watch` to run the tests again whenever you change something
* `mix coveralls` to check test coverage
Documentation:
* `mix docs` to generate documentation for this project
* `mix help` to find out what else you can do with `mix`
## Changes since Opencensus.Honeycomb
* Removed decorator: use resources or extra processors.