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