import datadog_client/metric.{type Metric}
import gleam/http
import gleam/http/request.{type Request}
import gleam/http/response.{type Response}
import gleam/json
import gleam/string
@target(erlang)
import gleam/httpc
@target(erlang)
import gleam/result
@target(javascript)
import gleam/fetch
@target(javascript)
import gleam/javascript/promise.{type Promise}
/// HTTP host for the Datadog API. Default `datadoghq.com` (US1).
/// Use `datadoghq.eu`, `us3.datadoghq.com`, `us5.datadoghq.com`, `ap1.datadoghq.com`, etc.
pub type Client {
Client(api_key: String, site: String)
}
/// Failure modes returned by `send`.
pub type SendError {
/// Transport-level failure (DNS, TCP, TLS, etc.).
HttpError(String)
/// Datadog returned a non-2xx status.
ApiError(status: Int, body: String)
}
// --- Client -----------------------------------------------------------------
/// Build a client for the default site (`datadoghq.com`).
pub fn new(api_key: String) -> Client {
Client(api_key: api_key, site: "datadoghq.com")
}
/// Override the Datadog site (e.g. `"datadoghq.eu"`).
pub fn with_site(client: Client, to site: String) -> Client {
Client(..client, site: site)
}
// --- Request building (transport-agnostic) ----------------------------------
/// Build the HTTPS request body for `/api/v1/series` without sending it.
/// Use this if you want to send via your own HTTP backend.
pub fn to_request(client: Client, metrics: List(Metric)) -> Request(String) {
request.new()
|> request.set_method(http.Post)
|> request.set_scheme(http.Https)
|> request.set_host("api." <> client.site)
|> request.set_path("/api/v1/series")
|> request.set_header("dd-api-key", client.api_key)
|> request.set_header("content-type", "application/json")
|> request.set_body(encode_to_json(metrics))
}
// --- Sending (Erlang via httpc) ---------------------------------------------
@target(erlang)
/// POST one or more metrics to `/api/v1/series`. Erlang target.
pub fn send(
client: Client,
metrics: List(Metric),
) -> Result(Response(String), SendError) {
use resp <- result.try(
httpc.send(to_request(client, metrics))
|> result.map_error(fn(e) { HttpError(string.inspect(e)) }),
)
case resp.status {
s if s >= 200 && s < 300 -> Ok(resp)
s -> Error(ApiError(status: s, body: resp.body))
}
}
@target(erlang)
/// Send a single metric. Convenience over `send`. Erlang target.
pub fn send_one(
client: Client,
m: Metric,
) -> Result(Response(String), SendError) {
send(client, [m])
}
// --- Sending (JS via fetch) -------------------------------------------------
@target(javascript)
/// POST one or more metrics to `/api/v1/series`. JavaScript target.
pub fn send(
client: Client,
metrics: List(Metric),
) -> Promise(Result(Response(String), SendError)) {
to_request(client, metrics)
|> fetch.send
|> promise.try_await(fetch.read_text_body)
|> promise.map(fn(res) {
case res {
Error(e) -> Error(HttpError(string.inspect(e)))
Ok(resp) ->
case resp.status {
s if s >= 200 && s < 300 -> Ok(resp)
s -> Error(ApiError(status: s, body: resp.body))
}
}
})
}
@target(javascript)
/// Send a single metric. Convenience over `send`. JavaScript target.
pub fn send_one(
client: Client,
m: Metric,
) -> Promise(Result(Response(String), SendError)) {
send(client, [m])
}
// --- JSON encoding ----------------------------------------------------------
/// Serialize a list of metrics to the exact JSON body sent to `/api/v1/series`.
/// Exposed for tests and for buffering payloads for later submission.
@internal
pub fn encode_to_json(metrics: List(Metric)) -> String {
json.object([#("series", json.array(metrics, of: metric.to_json))])
|> json.to_string
}