Skip to content

Instantly share code, notes, and snippets.

@stephanos
Last active November 24, 2019 23:19
Show Gist options
  • Save stephanos/08d611fc8886ff92ab1aa07296abb5fc to your computer and use it in GitHub Desktop.
Save stephanos/08d611fc8886ff92ab1aa07296abb5fc to your computer and use it in GitHub Desktop.
using Google Cloud's Stackdriver Logging API on App Engine with Elixir
defmodule GoogleAppEngine do
def google_cloud_project() do
System.get_env("GCLOUD_PROJECT",
System.fetch_env!("GOOGLE_CLOUD_PROJECT"))
end
def google_app_engine_service() do
System.fetch_env!("GAE_SERVICE")
end
def google_app_engine_version() do
System.fetch_env!("GAE_VERSION")
end
end
defmodule GoogleCloudLogger do
use GenServer
alias GoogleAppEngine
alias GoogleApi.Logging.V2.Connection
alias GoogleApi.Logging.V2.Api.Entries
alias GoogleApi.Logging.V2.Model.{LogEntry, MonitoredResource, WriteLogEntriesRequest}
def init(_) do
{:ok, configure([])}
end
def handle_call({:configure, opts}, _config) do
{:ok, :ok, configure(opts)}
end
def handle_event({_level, gl, _event}, config) when node(gl) != node() do
{:ok, config}
end
def handle_event({level, _gl, {Logger, msg, ts, md}}, %{level: min_level} = config) do
if is_nil(min_level) or Logger.compare_levels(level, min_level) != :lt do
case log_event(level, msg, ts, md, config) do
{:ok, _} -> :ok
{:error, reason} -> IO.puts("unable to write to Logging API: #{inspect reason}")
end
end
{:ok, config}
end
defp log_event(level, msg, ts, md, config) do
%{project_id: project_id, module_id: module_id, version_id: version_id} = config
case Goth.Token.for_scope("https://www.googleapis.com/auth/cloud-platform") do
{:ok, token} ->
request = %WriteLogEntriesRequest{
entries: [%LogEntry{
textPayload: IO.chardata_to_string(msg),
severity: to_severity(level),
timestamp: to_iso8601(ts)
}],
labels: to_labels(md),
logName: "projects/#{project_id}/logs/appengine.googleapis.com%2Fstdout",
partialSuccess: true,
resource: %MonitoredResource{
labels: %{
project_id: project_id,
module_id: module_id,
version_id: version_id
},
type: "gae_app"
}
}
Entries.logging_entries_write(Connection.new(token.token), [
body: request
])
error -> error
end
end
defp configure(opts) do
env = Application.get_env(:logger, :google_cloud, [])
opts = Keyword.merge(env, opts)
Application.put_env(:logger, :google_cloud, opts)
%{
level: Keyword.get(opts, :level, :info),
project_id: GoogleAppEngine.google_cloud_project(),
module_id: GoogleAppEngine.google_app_engine_service(),
version_id: GoogleAppEngine.google_app_engine_version()
}
end
defp to_severity(:debug), do: "DEBUG"
defp to_severity(:info), do: "INFO"
defp to_severity(:warn), do: "WARNING"
defp to_severity(:error), do: "ERROR"
defp to_labels(metadata) do
Enum.reduce(metadata, %{}, fn
({key, val}, acc) when is_binary(val) ->
Map.put(acc, key, to_string(val))
({key, val}, acc) ->
case String.Chars.impl_for(val) do
nil -> acc
_ -> Map.put(acc, key, to_string(val))
end
end)
end
defp to_iso8601({{year, month, day}, {hour, minute, second, millisecond}}) do
%DateTime{
year: year, month: month, day: day,
hour: hour, minute: minute, second: second, microsecond: {millisecond * 1000, 3},
zone_abbr: "UTC", utc_offset: 0, std_offset: 0, time_zone: "Etc/UTC" # assuming UTC
}
|> DateTime.to_iso8601()
end
end
defp deps do
[
{:goth, "~> 1.2.0"},
{:google_api_logging, "~> 0.18"},
...
use Mix.Config
config :logger,
backends: [
GoogleCloudLogger
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment