defmodule LogSnag do
@moduledoc """
LogSnag is a simple client for the LogSnag API.
This is an unofficial library, and isn't supported by LogSnag. It will try to
follow the functionality of [the official Node.js
client](https://github.com/LogSnag/logsnag.js) for the most part, with some
slight differences in naming conventions.
Full documentation of the API and fields that are used can be found on [the
official LogSnag docs page](https://docs.logsnag.com/).
"""
alias LogSnag.Error
alias LogSnag.Event
alias LogSnag.Insight
@base_url "https://api.logsnag.com/v1"
@typedoc """
A string containing a single emoji character.
"""
@type emoji :: String.t()
@doc """
Publish an event to LogSnag.
An event can be anything you want to track within your application, service,
or system. It will be published under your project and organized within your
specified channels.
For further documentation see LogSnag's official docs:
https://docs.logsnag.com/endpoints/log
## Examples
iex> publish_event(%{
...> channel: "waitlist",
...> event: "User Joined",
...> description: "Email: john@example.com",
...> icon: "🎉",
...> tags: %{
...> name: "john doe",
...> email: "john@example.com",
...> },
...> notify: true
...> })
{:ok,
%Event{
channel: "waitlist",
event: "User Joined",
description: "Email: john@example.com",
icon: "🎉",
notify: true,
tags: %{
name: "john doe",
email: "john@example.com"
}
}}
"""
@spec publish_event(params) :: {:ok, Event.t()} | {:error, Error.t()}
when params: %{
channel: String.t(),
event: String.t(),
description: String.t() | nil,
icon: emoji | nil,
notify: boolean | nil,
tags: map | nil
}
def publish_event(params) do
project = Application.fetch_env!(:log_snag, :project)
params = Map.put(params, :project, project)
with {:ok, body} <- make_request(:post, "/log", params) do
{:ok, Event.from_json(body)}
end
end
@doc """
Publish an insight to LogSnag.
Insights are real-time widgets that you can add to each of your projects.
They are use-case agnostic and can be used to display any information that
you want in real-time.
For further documentation see LogSnag's official docs:
https://docs.logsnag.com/endpoints/insight
## Examples
iex> publish_insight(%{
...> title: "User Count",
...> value: 100,
...> icon: "👨"
...> })
{:ok,
%Insight{
title: "User Count",
value: 100,
icon: "👨"
}}
"""
@spec publish_insight(params) :: {:ok, Insight.t()} | {:error, Error.t()}
when params: %{
title: String.t(),
value: String.t() | integer,
icon: emoji | nil
}
def publish_insight(params) do
project = Application.fetch_env!(:log_snag, :project)
params = Map.put(params, :project, project)
with {:ok, body} <- make_request(:post, "/insight", params) do
{:ok, Insight.from_json(body)}
end
end
# Private
@spec build_url(String.t()) :: String.t()
defp build_url("/" <> _ = path) do
@base_url <> path
end
@spec make_request(atom, String.t(), map) :: {:ok, map} | {:error, Error.t()}
defp make_request(method, path, params) do
api_key = Application.fetch_env!(:log_snag, :api_key)
headers = [
{"Authorization", "Bearer #{api_key}"},
{"Content-Type", "application/json"},
{"Accept", "application/json"}
]
body = Jason.encode!(params)
url = build_url(path)
request = Finch.build(method, url, headers, body)
result = Finch.request(request, LogSnag.Finch)
with {:ok, response} <- result,
{:ok, body} <- Jason.decode(response.body),
status when status >= 200 and status <= 299 <- response.status do
{:ok, body}
else
{:error, %Mint.TransportError{} = error} ->
{:error, Error.new(type: :system, reason: :network_error, context: error)}
401 ->
{:error, Error.new(type: :system, reason: :invalid_auth_header)}
400 ->
{:ok, response} = result
body = Jason.decode!(response.body)
message = body["validation"]["body"]["message"]
{:error, Error.new(type: :system, reason: :validation_error, context: message)}
_ ->
{:error, Error.new(type: :system, reason: :unknown, context: result)}
end
end
end