From dafb6ec0eb24f32386422b16a215591c223b7a2f Mon Sep 17 00:00:00 2001 From: Jake Morrison Date: Mon, 6 Aug 2018 17:34:09 -0500 Subject: [PATCH 01/18] Start hackney; add ex_aws so tests will run --- mix.exs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index bc3e785..6d54764 100644 --- a/mix.exs +++ b/mix.exs @@ -17,7 +17,7 @@ defmodule CloudWatch.Mixfile do # # Type "mix help compile.app" for more information def application do - [applications: [:logger]] + [applications: [:logger, :hackney]] end # This makes sure your factory and any other modules in test/support are compiled @@ -35,7 +35,9 @@ defmodule CloudWatch.Mixfile do # # Type "mix help deps" for more examples and options defp deps do - [{:aws, "~> 0.5.0", optional: true}, + [ + {:aws, "~> 0.5.0", optional: true}, + {:ex_aws, "~> 2.0"}, {:httpoison, "~> 0.11.1"}, {:credo, "~> 0.4.13", only: :dev}, {:mock, "~> 0.2.0", only: :test}, From ce6819303c6e74b1d570d76379b5770177202db0 Mon Sep 17 00:00:00 2001 From: Jake Morrison Date: Mon, 6 Aug 2018 17:34:28 -0500 Subject: [PATCH 02/18] Update deps --- mix.lock | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/mix.lock b/mix.lock index d6ce20b..eea41e7 100644 --- a/mix.lock +++ b/mix.lock @@ -1,20 +1,23 @@ -%{"aws": {:hex, :aws, "0.5.0", "e3916abd9d1fb4c182cbeac8a964ddeb32de9b20d441b66fba838ade06a4a278", [:mix], [{:httpoison, "~> 0.11.1", [hex: :httpoison, repo: "hexpm", optional: false]}, {:poison, "~> 3.1", [hex: :poison, repo: "hexpm", optional: false]}, {:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm"}, +%{ + "aws": {:hex, :aws, "0.5.0", "e3916abd9d1fb4c182cbeac8a964ddeb32de9b20d441b66fba838ade06a4a278", [:mix], [{:httpoison, "~> 0.11.1", [hex: :httpoison, repo: "hexpm", optional: false]}, {:poison, "~> 3.1", [hex: :poison, repo: "hexpm", optional: false]}, {:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm"}, "bunt": {:hex, :bunt, "0.1.6", "5d95a6882f73f3b9969fdfd1953798046664e6f77ec4e486e6fafc7caad97c6f", [:mix], [], "hexpm"}, "certifi": {:hex, :certifi, "1.2.1", "c3904f192bd5284e5b13f20db3ceac9626e14eeacfbb492e19583cf0e37b22be", [:rebar3], [], "hexpm"}, "combine": {:hex, :combine, "0.9.6", "8d1034a127d4cbf6924c8a5010d3534d958085575fa4d9b878f200d79ac78335", [:mix], [], "hexpm"}, - "credo": {:hex, :credo, "0.4.13", "0e9d0479c7e0591e36b093bf17fbdde012e2a69b5571e2df8d0b75b4fc8adc74", [:mix], [{:bunt, "~> 0.1.6", [hex: :bunt, repo: "hexpm", optional: false]}], "hexpm"}, + "credo": {:hex, :credo, "0.4.14", "594a965ae2224997fae9e705e881983911a052c701b05bd3bcf89af44b5f6b45", [:mix], [{:bunt, "~> 0.1.6", [hex: :bunt, repo: "hexpm", optional: false]}], "hexpm"}, "earmark": {:hex, :earmark, "1.2.2", "f718159d6b65068e8daeef709ccddae5f7fdc770707d82e7d126f584cd925b74", [:mix], [], "hexpm"}, + "ex_aws": {:hex, :ex_aws, "2.1.0", "b92651527d6c09c479f9013caa9c7331f19cba38a650590d82ebf2c6c16a1d8a", [:mix], [{:configparser_ex, "~> 2.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "1.6.3 or 1.6.5 or 1.7.1 or 1.8.6 or ~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:poison, ">= 1.2.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:xml_builder, "~> 0.1.0", [hex: :xml_builder, repo: "hexpm", optional: true]}], "hexpm"}, "ex_doc": {:hex, :ex_doc, "0.16.2", "3b3e210ebcd85a7c76b4e73f85c5640c011d2a0b2f06dcdf5acdb2ae904e5084", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"}, "gettext": {:hex, :gettext, "0.13.1", "5e0daf4e7636d771c4c71ad5f3f53ba09a9ae5c250e1ab9c42ba9edccc476263", [:mix], [], "hexpm"}, "hackney": {:hex, :hackney, "1.8.6", "21a725db3569b3fb11a6af17d5c5f654052ce9624219f1317e8639183de4a423", [:rebar3], [{:certifi, "1.2.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.0.2", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, "httpoison": {:hex, :httpoison, "0.11.2", "9e59f17a473ef6948f63c51db07320477bad8ba88cf1df60a3eee01150306665", [:mix], [{:hackney, "~> 1.8.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, "idna": {:hex, :idna, "5.0.2", "ac203208ada855d95dc591a764b6e87259cb0e2a364218f215ad662daa8cd6b4", [:rebar3], [{:unicode_util_compat, "0.2.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, - "meck": {:hex, :meck, "0.8.4", "59ca1cd971372aa223138efcf9b29475bde299e1953046a0c727184790ab1520", [:make, :rebar], [], "hexpm"}, + "meck": {:hex, :meck, "0.8.11", "2c39e15ec87d847da6cf69b4a1c4af3fd850ae2a272e719e0e8751a7fe54771f", [:rebar3], [], "hexpm"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"}, - "mock": {:hex, :mock, "0.2.0", "5991877be6bb514b647dbd6f4869bc12bd7f2829df16e86c98d6108f966d34d7", [:mix], [{:meck, "~> 0.8.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"}, + "mock": {:hex, :mock, "0.2.1", "bfdba786903e77f9c18772dee472d020ceb8ef000783e737725a4c8f54ad28ec", [:mix], [{:meck, "~> 0.8.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"}, "timex": {:hex, :timex, "3.1.21", "7d1ec0f73c4668bea71f38af6357d75992f356a7e032c69620c5e87ca4b95c66", [:mix], [{:combine, "~> 0.7", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"}, "tzdata": {:hex, :tzdata, "0.5.12", "1c17b68692c6ba5b6ab15db3d64cc8baa0f182043d5ae9d4b6d35d70af76f67b", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, - "unicode_util_compat": {:hex, :unicode_util_compat, "0.2.0", "dbbccf6781821b1c0701845eaf966c9b6d83d7c3bfc65ca2b78b88b8678bfa35", [:rebar3], [], "hexpm"}} + "unicode_util_compat": {:hex, :unicode_util_compat, "0.2.0", "dbbccf6781821b1c0701845eaf966c9b6d83d7c3bfc65ca2b78b88b8678bfa35", [:rebar3], [], "hexpm"}, +} From 18915faa1b77546f4482121c293520f88a6d6ed9 Mon Sep 17 00:00:00 2001 From: Jake Morrison Date: Mon, 6 Aug 2018 17:34:57 -0500 Subject: [PATCH 03/18] Add access_key_id in config for tests --- test/cloud_watch_test.exs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/cloud_watch_test.exs b/test/cloud_watch_test.exs index 17ad69a..fcaafa9 100644 --- a/test/cloud_watch_test.exs +++ b/test/cloud_watch_test.exs @@ -12,7 +12,10 @@ defmodule CloudWatchTest do setup_all do {:ok, _} = Cycler.start_link Logger.add_backend(@backend) - :ok = Logger.configure_backend(@backend, [format: "$message", level: :info, log_group_name: "testLogGroup", log_stream_name: "testLogStream", max_buffer_size: 39]) + config = [format: "$message", level: :info, log_group_name: "testLogGroup", + log_stream_name: "testLogStream", max_buffer_size: 39, + access_key_id: "fake"] + :ok = Logger.configure_backend(@backend, config) end setup do From f725b2348995dde3936ee1679a088d416328593a Mon Sep 17 00:00:00 2001 From: Jake Morrison Date: Mon, 6 Aug 2018 17:35:59 -0500 Subject: [PATCH 04/18] Read AWS config from instance metadata --- README.md | 22 +++++-- lib/cloud_watch.ex | 154 ++++++++++++++++++++++++++++++++++++++------- 2 files changed, 147 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 04a2627..bbc2f4a 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Add the backend to `config.exs`: utc_log: true ``` -Configure the following example to suit your needs: +Following is a full example with the default values: ```elixir config :logger, CloudWatch, @@ -46,10 +46,22 @@ Configure the following example to suit your needs: max_timeout: 60_000 ``` -The `endpoint` may be omitted from the configuration and will default to -`amazonaws.com`. The `max_buffer_size` controls when `cloud_watch` will flush -the buffer in bytes. You may specify anything up to a maximum of 1,048,576 -bytes. If omitted, it will default to 10,485 bytes. +CloudWatch flushes the buffer when it has collected `max_buffer_size` bytes of +messages or `max_timeout` milliseconds have elapsed. `max_buffer_size` can be +anything up to a maximum of 1,048,576 bytes. If omitted, it will default to +10,485 bytes. + +CloudWatch supports getting AWS credentials and other defaults from +[EC2 instance metadata](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html) + +In that case, a minimal configuration is: + + ```elixir + config :logger, CloudWatch, + log_group_name: "api" + ``` + +`log_stream_name` defaults to the instance id. ## Alternative AWS client library: ExAws diff --git a/lib/cloud_watch.ex b/lib/cloud_watch.ex index 8c012b9..a0bab38 100644 --- a/lib/cloud_watch.ex +++ b/lib/cloud_watch.ex @@ -9,8 +9,20 @@ defmodule CloudWatch do alias CloudWatch.InputLogEvent alias CloudWatch.AwsProxy - def init(_) do - state = configure(Application.get_env(:logger, CloudWatch, [])) + def init(_args) do + state = configure([]) + + # If AWS keys are not defined statically, get them from the instance metadata. + # This may fail while the instance is starting up, so retry quickly. + # Otherwise refresh every 10 minutes, as the keys expire periodically. + unless state.access_key_id do + if state.client do + Process.send_after(self(), :refresh_creds, 300_000) + else + Process.send_after(self(), :refresh_creds, 200) + end + end + Process.send_after(self(), :flush, state.max_timeout) {:ok, state} end @@ -39,7 +51,7 @@ defmodule CloudWatch do end def handle_event(:flush, state) do - {:ok, Map.merge(state, %{buffer: [], buffer_size: 0})} + {:ok, %{state | buffer: [], buffer_size: 0}} end def handle_info(:flush, state) do @@ -48,6 +60,16 @@ defmodule CloudWatch do {:ok, flushed_state} end + def handle_info(:refresh_creds, state) do + state = configure_aws(state) + if state.client do + Process.send_after(self(), :refresh_creds, 300_000) + else + Process.send_after(self(), :refresh_creds, 200) + end + {:ok, state} + end + def handle_info(_msg, state) do {:ok, state} end @@ -60,34 +82,82 @@ defmodule CloudWatch do :ok end + @spec configure(Keyword.t) :: Map.t defp configure(opts) do opts = Keyword.merge(Application.get_env(:logger, CloudWatch, []), opts) - format = Logger.Formatter.compile(Keyword.get(opts, :format, @default_format)) - level = Keyword.get(opts, :level, @default_level) - log_group_name = Keyword.get(opts, :log_group_name) - log_stream_name = Keyword.get(opts, :log_stream_name) - max_buffer_size = Keyword.get(opts, :max_buffer_size, @default_max_buffer_size) - max_timeout = Keyword.get(opts, :max_timeout, @default_max_timeout) - - # AWS configuration, only if needed by the AWS library - region = Keyword.get(opts, :region) - access_key_id = Keyword.get(opts, :access_key_id) - endpoint = Keyword.get(opts, :endpoint, @default_endpoint) - secret_access_key = Keyword.get(opts, :secret_access_key) - client = AwsProxy.client(access_key_id, secret_access_key, region, endpoint) - %{buffer: [], buffer_size: 0, client: client, format: format, level: level, log_group_name: log_group_name, - log_stream_name: log_stream_name, max_buffer_size: max_buffer_size, max_timeout: max_timeout, - sequence_token: nil, flushed_at: nil} - end - - defp flush(_state, _opts \\ [force: false]) + + state = %{ + access_key_id: opts[:access_key_id], + secret_access_key: opts[:secret_access_key], + region: opts[:region] || metadata_region(), + endpoint: opts[:endpoint] || metadata_endpoint(), + client: nil, + buffer: [], buffer_size: 0, + level: opts[:level] || @default_level, + format: Logger.Formatter.compile(opts[:format] || @default_format), + log_group_name: opts[:log_group_name], + log_stream_name: opts[:log_stream_name], + max_buffer_size: opts[:max_buffer_size] || @default_max_buffer_size, + max_timeout: opts[:max_timeout] || @default_max_timeout, + sequence_token: nil, flushed_at: nil + } + + if state.access_key_id do + %{state | client: AwsProxy.client(state.access_key_id, state.secret_access_key, state.region, state.endpoint)} + else + configure_aws(state) + end + end + + defp configure_aws(state) do + case System.get_env("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI") do + nil -> + # https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html + case get_metadata("http://169.254.169.254/latest/meta-data/iam/security-credentials/") do + {:ok, ""} -> + state + {:ok, role} -> + {:ok, json} = get_metadata("http://169.254.169.254/latest/meta-data/iam/security-credentials/" <> role) + creds = Poison.Parser.parse!(json) + access_key_id = Map.get(creds, "AccessKeyId") + secret_access_key = Map.get(creds, "SecretAccessKey") + region = state.region || metadata_region() + endpoint = state.endpoint || metadata_endpoint() || @default_endpoint + client = AwsProxy.client(access_key_id, secret_access_key, region, endpoint) + + log_stream_name = state.log_stream_name || metadata_instance_id() + + %{state | client: client, log_stream_name: log_stream_name} + _ -> + state + end + uri -> + # https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html + case get_metadata("http://169.254.170.2" <> uri) do + {:ok, json} -> + creds = Poison.Parser.parse!(json) + access_key_id = Map.get(creds, "AccessKeyId") + secret_access_key = Map.get(creds, "SecretAccessKey") + region = state.region + endpoint = state.endpoint || @default_endpoint + client = AwsProxy.client(access_key_id, secret_access_key, region, endpoint) + %{state | client: client} + _ -> + state + end + end + end + + defp flush(state, opts \\ [force: false]) defp flush(%{buffer: buffer, buffer_size: buffer_size, max_buffer_size: max_buffer_size} = state, [force: false]) when buffer_size < max_buffer_size and length(buffer) < 10_000 do {:ok, state} end - - defp flush(%{buffer: []} = state, _opts), do: {:ok, state} + + defp flush(%{buffer: []} = state, _opts), do: {:ok, state} + + defp flush(%{client: nil} = state, _opts), do: {:ok, state} defp flush(state, opts) do case AwsProxy.put_log_events(state.client, %{logEvents: Enum.sort_by(state.buffer, &(&1.timestamp)), @@ -116,4 +186,40 @@ defmodule CloudWatch do |> flush(opts) end end + + defp get_metadata(url) do + case HTTPoison.get(url) do + {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> + {:ok, body} + _ -> + nil + end + end + + defp get_metadata!(url) do + case HTTPoison.get(url) do + {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> + body + _ -> + nil + end + end + + defp metadata_endpoint do + get_metadata!("http://169.254.169.254/latest/meta-data/services/domain") + end + + defp metadata_instance_id do + get_metadata!("http://169.254.169.254/latest/meta-data/instance-id") + end + + defp metadata_region do + case HTTPoison.get("http://169.254.169.254/latest/meta-data/placement/availability-zone") do + {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> + String.slice(body, Range.new(0, -2)) + _ -> + nil + end + end + end From 5ee4565e9c0d4365ddd9ab433f6ea36f2dcc3f09 Mon Sep 17 00:00:00 2001 From: Jake Morrison Date: Mon, 6 Aug 2018 18:50:41 -0500 Subject: [PATCH 05/18] Add explicit dep on poison --- mix.exs | 1 + 1 file changed, 1 insertion(+) diff --git a/mix.exs b/mix.exs index 6d54764..6f35668 100644 --- a/mix.exs +++ b/mix.exs @@ -39,6 +39,7 @@ defmodule CloudWatch.Mixfile do {:aws, "~> 0.5.0", optional: true}, {:ex_aws, "~> 2.0"}, {:httpoison, "~> 0.11.1"}, + {:poison, "~> 4.0"}, {:credo, "~> 0.4.13", only: :dev}, {:mock, "~> 0.2.0", only: :test}, {:ex_doc, ">= 0.0.0", only: :dev}] From 6c619bd87b96a7fc33c118d1e5b3bf004d1cedd2 Mon Sep 17 00:00:00 2001 From: Jake Morrison Date: Mon, 6 Aug 2018 18:51:37 -0500 Subject: [PATCH 06/18] Use 3.0 version of poison --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 6f35668..921a28b 100644 --- a/mix.exs +++ b/mix.exs @@ -39,7 +39,7 @@ defmodule CloudWatch.Mixfile do {:aws, "~> 0.5.0", optional: true}, {:ex_aws, "~> 2.0"}, {:httpoison, "~> 0.11.1"}, - {:poison, "~> 4.0"}, + {:poison, "~> 3.0"}, {:credo, "~> 0.4.13", only: :dev}, {:mock, "~> 0.2.0", only: :test}, {:ex_doc, ">= 0.0.0", only: :dev}] From dacabb8d4fb5d814e43073e2ca48bcde7172fb34 Mon Sep 17 00:00:00 2001 From: Jake Morrison Date: Tue, 7 Aug 2018 07:23:57 -0500 Subject: [PATCH 07/18] Use hackney directly to avoid dependency on httpoison --- lib/cloud_watch.ex | 35 ++++++++++++++++++++++++++++------- mix.exs | 1 - 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/lib/cloud_watch.ex b/lib/cloud_watch.ex index a0bab38..ca2bc79 100644 --- a/lib/cloud_watch.ex +++ b/lib/cloud_watch.ex @@ -188,21 +188,34 @@ defmodule CloudWatch do end defp get_metadata(url) do - case HTTPoison.get(url) do - {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> - {:ok, body} + case :hackney.request(:get, url, [], "", []) do + {:ok, 200, _resp_headers, client_ref} -> + :hackney.body(client_ref) _ -> nil end + # case HTTPoison.get(url) do + # {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> + # {:ok, body} + # _ -> + # nil + # end end defp get_metadata!(url) do - case HTTPoison.get(url) do - {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> + case :hackney.request(:get, url, [], "", []) do + {:ok, 200, _resp_headers, client_ref} -> + {:ok, body} = :hackney.body(client_ref) body _ -> nil end + # case HTTPoison.get(url) do + # {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> + # body + # _ -> + # nil + # end end defp metadata_endpoint do @@ -214,12 +227,20 @@ defmodule CloudWatch do end defp metadata_region do - case HTTPoison.get("http://169.254.169.254/latest/meta-data/placement/availability-zone") do - {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> + url = "http://169.254.169.254/latest/meta-data/placement/availability-zone" + case :hackney.request(:get, url, [], "", []) do + {:ok, 200, _resp_headers, client_ref} -> + {:ok, body} = :hackney.body(client_ref) String.slice(body, Range.new(0, -2)) _ -> nil end + # case HTTPoison.get("http://169.254.169.254/latest/meta-data/placement/availability-zone") do + # {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> + # String.slice(body, Range.new(0, -2)) + # _ -> + # nil + # end end end diff --git a/mix.exs b/mix.exs index 921a28b..bf2cb08 100644 --- a/mix.exs +++ b/mix.exs @@ -38,7 +38,6 @@ defmodule CloudWatch.Mixfile do [ {:aws, "~> 0.5.0", optional: true}, {:ex_aws, "~> 2.0"}, - {:httpoison, "~> 0.11.1"}, {:poison, "~> 3.0"}, {:credo, "~> 0.4.13", only: :dev}, {:mock, "~> 0.2.0", only: :test}, From 353f96b9fc838032d669217d178ebda4f9fe3e8d Mon Sep 17 00:00:00 2001 From: Jake Morrison Date: Tue, 7 Aug 2018 07:38:58 -0500 Subject: [PATCH 08/18] Add explicit hackney dep --- mix.exs | 1 + 1 file changed, 1 insertion(+) diff --git a/mix.exs b/mix.exs index bf2cb08..0f89724 100644 --- a/mix.exs +++ b/mix.exs @@ -39,6 +39,7 @@ defmodule CloudWatch.Mixfile do {:aws, "~> 0.5.0", optional: true}, {:ex_aws, "~> 2.0"}, {:poison, "~> 3.0"}, + {:hackney, "~> 1.0"}, {:credo, "~> 0.4.13", only: :dev}, {:mock, "~> 0.2.0", only: :test}, {:ex_doc, ">= 0.0.0", only: :dev}] From 84aed09f91b505e73ba45d4f630c0d1dfc10449f Mon Sep 17 00:00:00 2001 From: Jake Morrison Date: Tue, 7 Aug 2018 07:50:44 -0500 Subject: [PATCH 09/18] Use top level API for Poison --- lib/cloud_watch.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cloud_watch.ex b/lib/cloud_watch.ex index ca2bc79..dfc1fbf 100644 --- a/lib/cloud_watch.ex +++ b/lib/cloud_watch.ex @@ -118,7 +118,7 @@ defmodule CloudWatch do state {:ok, role} -> {:ok, json} = get_metadata("http://169.254.169.254/latest/meta-data/iam/security-credentials/" <> role) - creds = Poison.Parser.parse!(json) + {:ok, creds} = Poison.decode(json) access_key_id = Map.get(creds, "AccessKeyId") secret_access_key = Map.get(creds, "SecretAccessKey") region = state.region || metadata_region() @@ -135,7 +135,7 @@ defmodule CloudWatch do # https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html case get_metadata("http://169.254.170.2" <> uri) do {:ok, json} -> - creds = Poison.Parser.parse!(json) + {:ok, creds} = Poison.decode(json) access_key_id = Map.get(creds, "AccessKeyId") secret_access_key = Map.get(creds, "SecretAccessKey") region = state.region From 41e65bd51871b30d4e76d445ae908e111d24d5c7 Mon Sep 17 00:00:00 2001 From: Jake Morrison Date: Tue, 7 Aug 2018 08:34:22 -0500 Subject: [PATCH 10/18] Remove aws deps --- mix.exs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index 0f89724..3c4e54f 100644 --- a/mix.exs +++ b/mix.exs @@ -36,8 +36,8 @@ defmodule CloudWatch.Mixfile do # Type "mix help deps" for more examples and options defp deps do [ - {:aws, "~> 0.5.0", optional: true}, - {:ex_aws, "~> 2.0"}, + # {:aws, "~> 0.5.0", optional: true}, + # {:ex_aws, "~> 2.0"}, {:poison, "~> 3.0"}, {:hackney, "~> 1.0"}, {:credo, "~> 0.4.13", only: :dev}, From db5218aa344ee748aa15cb373144ca68d2d2ed3f Mon Sep 17 00:00:00 2001 From: Jake Morrison Date: Tue, 7 Aug 2018 22:36:28 +0000 Subject: [PATCH 11/18] Add deps directly --- mix.exs | 8 ++++---- mix.lock | 11 ++++++----- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/mix.exs b/mix.exs index 3c4e54f..10e79f4 100644 --- a/mix.exs +++ b/mix.exs @@ -17,7 +17,7 @@ defmodule CloudWatch.Mixfile do # # Type "mix help compile.app" for more information def application do - [applications: [:logger, :hackney]] + [extra_applications: [:logger]] end # This makes sure your factory and any other modules in test/support are compiled @@ -37,9 +37,9 @@ defmodule CloudWatch.Mixfile do defp deps do [ # {:aws, "~> 0.5.0", optional: true}, - # {:ex_aws, "~> 2.0"}, - {:poison, "~> 3.0"}, - {:hackney, "~> 1.0"}, + {:httpoison, "~> 0.11"}, + {:poison, "~> 3.1"}, + {:hackney, "~> 1.8"}, {:credo, "~> 0.4.13", only: :dev}, {:mock, "~> 0.2.0", only: :test}, {:ex_doc, ">= 0.0.0", only: :dev}] diff --git a/mix.lock b/mix.lock index eea41e7..d48b6dd 100644 --- a/mix.lock +++ b/mix.lock @@ -1,23 +1,24 @@ %{ "aws": {:hex, :aws, "0.5.0", "e3916abd9d1fb4c182cbeac8a964ddeb32de9b20d441b66fba838ade06a4a278", [:mix], [{:httpoison, "~> 0.11.1", [hex: :httpoison, repo: "hexpm", optional: false]}, {:poison, "~> 3.1", [hex: :poison, repo: "hexpm", optional: false]}, {:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm"}, "bunt": {:hex, :bunt, "0.1.6", "5d95a6882f73f3b9969fdfd1953798046664e6f77ec4e486e6fafc7caad97c6f", [:mix], [], "hexpm"}, - "certifi": {:hex, :certifi, "1.2.1", "c3904f192bd5284e5b13f20db3ceac9626e14eeacfbb492e19583cf0e37b22be", [:rebar3], [], "hexpm"}, + "certifi": {:hex, :certifi, "2.3.1", "d0f424232390bf47d82da8478022301c561cf6445b5b5fb6a84d49a9e76d2639", [:rebar3], [{:parse_trans, "3.2.0", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, "combine": {:hex, :combine, "0.9.6", "8d1034a127d4cbf6924c8a5010d3534d958085575fa4d9b878f200d79ac78335", [:mix], [], "hexpm"}, "credo": {:hex, :credo, "0.4.14", "594a965ae2224997fae9e705e881983911a052c701b05bd3bcf89af44b5f6b45", [:mix], [{:bunt, "~> 0.1.6", [hex: :bunt, repo: "hexpm", optional: false]}], "hexpm"}, "earmark": {:hex, :earmark, "1.2.2", "f718159d6b65068e8daeef709ccddae5f7fdc770707d82e7d126f584cd925b74", [:mix], [], "hexpm"}, "ex_aws": {:hex, :ex_aws, "2.1.0", "b92651527d6c09c479f9013caa9c7331f19cba38a650590d82ebf2c6c16a1d8a", [:mix], [{:configparser_ex, "~> 2.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "1.6.3 or 1.6.5 or 1.7.1 or 1.8.6 or ~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:poison, ">= 1.2.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:xml_builder, "~> 0.1.0", [hex: :xml_builder, repo: "hexpm", optional: true]}], "hexpm"}, "ex_doc": {:hex, :ex_doc, "0.16.2", "3b3e210ebcd85a7c76b4e73f85c5640c011d2a0b2f06dcdf5acdb2ae904e5084", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"}, "gettext": {:hex, :gettext, "0.13.1", "5e0daf4e7636d771c4c71ad5f3f53ba09a9ae5c250e1ab9c42ba9edccc476263", [:mix], [], "hexpm"}, - "hackney": {:hex, :hackney, "1.8.6", "21a725db3569b3fb11a6af17d5c5f654052ce9624219f1317e8639183de4a423", [:rebar3], [{:certifi, "1.2.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.0.2", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, - "httpoison": {:hex, :httpoison, "0.11.2", "9e59f17a473ef6948f63c51db07320477bad8ba88cf1df60a3eee01150306665", [:mix], [{:hackney, "~> 1.8.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, - "idna": {:hex, :idna, "5.0.2", "ac203208ada855d95dc591a764b6e87259cb0e2a364218f215ad662daa8cd6b4", [:rebar3], [{:unicode_util_compat, "0.2.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, + "hackney": {:hex, :hackney, "1.13.0", "24edc8cd2b28e1c652593833862435c80661834f6c9344e84b6a2255e7aeef03", [:rebar3], [{:certifi, "2.3.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.2", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, + "httpoison": {:hex, :httpoison, "0.13.0", "bfaf44d9f133a6599886720f3937a7699466d23bb0cd7a88b6ba011f53c6f562", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, + "idna": {:hex, :idna, "5.1.2", "e21cb58a09f0228a9e0b95eaa1217f1bcfc31a1aaa6e1fdf2f53a33f7dbd9494", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, "meck": {:hex, :meck, "0.8.11", "2c39e15ec87d847da6cf69b4a1c4af3fd850ae2a272e719e0e8751a7fe54771f", [:rebar3], [], "hexpm"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"}, "mock": {:hex, :mock, "0.2.1", "bfdba786903e77f9c18772dee472d020ceb8ef000783e737725a4c8f54ad28ec", [:mix], [{:meck, "~> 0.8.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"}, + "parse_trans": {:hex, :parse_trans, "3.2.0", "2adfa4daf80c14dc36f522cf190eb5c4ee3e28008fc6394397c16f62a26258c2", [:rebar3], [], "hexpm"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"}, "timex": {:hex, :timex, "3.1.21", "7d1ec0f73c4668bea71f38af6357d75992f356a7e032c69620c5e87ca4b95c66", [:mix], [{:combine, "~> 0.7", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"}, "tzdata": {:hex, :tzdata, "0.5.12", "1c17b68692c6ba5b6ab15db3d64cc8baa0f182043d5ae9d4b6d35d70af76f67b", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, - "unicode_util_compat": {:hex, :unicode_util_compat, "0.2.0", "dbbccf6781821b1c0701845eaf966c9b6d83d7c3bfc65ca2b78b88b8678bfa35", [:rebar3], [], "hexpm"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm"}, } From 54f494c32bd67ba81a7eaeed76b0141cadd13320 Mon Sep 17 00:00:00 2001 From: Jake Morrison Date: Tue, 7 Aug 2018 22:39:14 +0000 Subject: [PATCH 12/18] Debugging --- lib/cloud_watch.ex | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/lib/cloud_watch.ex b/lib/cloud_watch.ex index dfc1fbf..24ec454 100644 --- a/lib/cloud_watch.ex +++ b/lib/cloud_watch.ex @@ -9,7 +9,10 @@ defmodule CloudWatch do alias CloudWatch.InputLogEvent alias CloudWatch.AwsProxy - def init(_args) do + @spec init(module()) :: {:ok, term()} | {:error, term()} + def init(__MODULE__) do + {:ok, _} = Application.ensure_all_started(:hackney) + state = configure([]) # If AWS keys are not defined statically, get them from the instance metadata. @@ -28,6 +31,7 @@ defmodule CloudWatch do end def handle_call({:configure, opts}, _) do + Application.put_env(:logger, __MODULE__, opts) {:ok, :ok, configure(opts)} end @@ -83,14 +87,15 @@ defmodule CloudWatch do end @spec configure(Keyword.t) :: Map.t - defp configure(opts) do - opts = Keyword.merge(Application.get_env(:logger, CloudWatch, []), opts) + def configure(opts) do + env = Application.get_env(:logger, __MODULE__, []) + opts = Keyword.merge(env, opts) state = %{ access_key_id: opts[:access_key_id], secret_access_key: opts[:secret_access_key], - region: opts[:region] || metadata_region(), - endpoint: opts[:endpoint] || metadata_endpoint(), + region: opts[:region], + endpoint: opts[:endpoint], client: nil, buffer: [], buffer_size: 0, level: opts[:level] || @default_level, @@ -109,7 +114,7 @@ defmodule CloudWatch do end end - defp configure_aws(state) do + def configure_aws(state) do case System.get_env("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI") do nil -> # https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html @@ -187,7 +192,7 @@ defmodule CloudWatch do end end - defp get_metadata(url) do + def get_metadata(url) do case :hackney.request(:get, url, [], "", []) do {:ok, 200, _resp_headers, client_ref} -> :hackney.body(client_ref) @@ -202,7 +207,7 @@ defmodule CloudWatch do # end end - defp get_metadata!(url) do + def get_metadata!(url) do case :hackney.request(:get, url, [], "", []) do {:ok, 200, _resp_headers, client_ref} -> {:ok, body} = :hackney.body(client_ref) @@ -218,15 +223,15 @@ defmodule CloudWatch do # end end - defp metadata_endpoint do + def metadata_endpoint do get_metadata!("http://169.254.169.254/latest/meta-data/services/domain") end - defp metadata_instance_id do + def metadata_instance_id do get_metadata!("http://169.254.169.254/latest/meta-data/instance-id") end - defp metadata_region do + def metadata_region do url = "http://169.254.169.254/latest/meta-data/placement/availability-zone" case :hackney.request(:get, url, [], "", []) do {:ok, 200, _resp_headers, client_ref} -> @@ -235,7 +240,7 @@ defmodule CloudWatch do _ -> nil end - # case HTTPoison.get("http://169.254.169.254/latest/meta-data/placement/availability-zone") do + # case HTTPoison.get(url) do # {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> # String.slice(body, Range.new(0, -2)) # _ -> From 49e8f64e6676e112a12803c0ddd62d9be58423f2 Mon Sep 17 00:00:00 2001 From: Jake Morrison Date: Wed, 8 Aug 2018 14:18:29 +0000 Subject: [PATCH 13/18] Update location of aws-elixir --- lib/cloud_watch/aws_proxy.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cloud_watch/aws_proxy.ex b/lib/cloud_watch/aws_proxy.ex index a80482f..12b72a6 100644 --- a/lib/cloud_watch/aws_proxy.ex +++ b/lib/cloud_watch/aws_proxy.ex @@ -8,7 +8,7 @@ defmodule CloudWatch.AwsProxy do cond do Code.ensure_loaded?(AWS) -> # AWS CloudWatch Logs implemented using aws-elixir - # See https://github.com/jkakar/aws-elixir + # https://github.com/aws-beam/aws-elixir # # AWS credentials are configured in CloudWatch def client(access_key_id, secret_access_key, region, endpoint) do From 341dee00c3a4c56fb7a2fb51dc73baaf7444b0fa Mon Sep 17 00:00:00 2001 From: Jake Morrison Date: Wed, 8 Aug 2018 14:18:41 +0000 Subject: [PATCH 14/18] Formatting --- lib/cloud_watch/input_log_event.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/cloud_watch/input_log_event.ex b/lib/cloud_watch/input_log_event.ex index 877db69..71bc79c 100644 --- a/lib/cloud_watch/input_log_event.ex +++ b/lib/cloud_watch/input_log_event.ex @@ -10,6 +10,7 @@ defmodule CloudWatch.InputLogEvent do |> Kernel.-(@epoch) |> Kernel.*(1000) |> Kernel.+(milliseconds) + %{message: message, timestamp: timestamp} |> Poison.Encoder.encode(options) |> IO.chardata_to_string From b5cfa4de7c1a197452febfefd4e542000a028c5f Mon Sep 17 00:00:00 2001 From: Jake Morrison Date: Wed, 8 Aug 2018 14:22:33 +0000 Subject: [PATCH 15/18] Save config to env; crash if group/stream create fails; reduce piping --- lib/cloud_watch.ex | 57 +++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/lib/cloud_watch.ex b/lib/cloud_watch.ex index 24ec454..b56f633 100644 --- a/lib/cloud_watch.ex +++ b/lib/cloud_watch.ex @@ -13,7 +13,8 @@ defmodule CloudWatch do def init(__MODULE__) do {:ok, _} = Application.ensure_all_started(:hackney) - state = configure([]) + env = Application.get_env(:logger, __MODULE__, []) + state = configure(env) # If AWS keys are not defined statically, get them from the instance metadata. # This may fail while the instance is starting up, so retry quickly. @@ -44,10 +45,13 @@ defmodule CloudWatch do :lt -> {:ok, state} _ -> %{buffer: buffer, buffer_size: buffer_size} = state + message = state.format |> Logger.Formatter.format(level, msg, ts, md) |> IO.chardata_to_string + buffer = List.insert_at(buffer, -1, %InputLogEvent{message: message, timestamp: ts}) + state |> Map.merge(%{buffer: buffer, buffer_size: buffer_size + byte_size(message) + 26}) |> flush() @@ -88,9 +92,6 @@ defmodule CloudWatch do @spec configure(Keyword.t) :: Map.t def configure(opts) do - env = Application.get_env(:logger, __MODULE__, []) - opts = Keyword.merge(env, opts) - state = %{ access_key_id: opts[:access_key_id], secret_access_key: opts[:secret_access_key], @@ -165,30 +166,30 @@ defmodule CloudWatch do defp flush(%{client: nil} = state, _opts), do: {:ok, state} defp flush(state, opts) do - case AwsProxy.put_log_events(state.client, %{logEvents: Enum.sort_by(state.buffer, &(&1.timestamp)), - logGroupName: state.log_group_name, logStreamName: state.log_stream_name, sequenceToken: state.sequence_token}) do - {:ok, %{"nextSequenceToken" => next_sequence_token}, _} -> - {:ok, Map.merge(state, %{buffer: [], buffer_size: 0, sequence_token: next_sequence_token})} - {:error, {"DataAlreadyAcceptedException", "The given batch of log events has already been accepted. The next batch can be sent with sequenceToken: " <> next_sequence_token}} -> - state - |> Map.put(:sequence_token, next_sequence_token) - |> flush(opts) - {:error, {"InvalidSequenceTokenException", "The given sequenceToken is invalid. The next expected sequenceToken is: " <> next_sequence_token}} -> - state - |> Map.put(:sequence_token, next_sequence_token) - |> flush(opts) - {:error, {"ResourceNotFoundException", "The specified log group does not exist."}} -> - AwsProxy.create_log_group(state.client, %{logGroupName: state.log_group_name}) - AwsProxy.create_log_stream(state.client, %{logGroupName: state.log_group_name, - logStreamName: state.log_stream_name}) - flush(state, opts) - {:error, {"ResourceNotFoundException", "The specified log stream does not exist."}} -> - AwsProxy.create_log_stream(state.client, %{logGroupName: state.log_group_name, - logStreamName: state.log_stream_name}) - flush(state, opts) - {:error, %HTTPoison.Error{id: nil, reason: reason}} when reason in [:closed, :connect_timeout, :timeout] -> - state - |> flush(opts) + events = %{logEvents: Enum.sort_by(state.buffer, &(&1.timestamp)), + logGroupName: state.log_group_name, logStreamName: state.log_stream_name, + sequenceToken: state.sequence_token} + + case AwsProxy.put_log_events(state.client, events) do + {:ok, %{"nextSequenceToken" => next_sequence_token}, _} -> + {:ok, %{state | buffer: [], buffer_size: 0, sequence_token: next_sequence_token}} + {:error, {"DataAlreadyAcceptedException", + "The given batch of log events has already been accepted. The next batch can be sent with sequenceToken: " <> next_sequence_token}} -> + flush(%{state | sequence_token: next_sequence_token}, opts) + {:error, {"InvalidSequenceTokenException", + "The given sequenceToken is invalid. The next expected sequenceToken is: " <> next_sequence_token}} -> + flush(%{state | sequence_token: next_sequence_token}, opts) + {:error, {"ResourceNotFoundException", "The specified log group does not exist."}} -> + {:ok, _, _} = AwsProxy.create_log_group(state.client, %{logGroupName: state.log_group_name}) + {:ok, _, _} = AwsProxy.create_log_stream(state.client, %{logGroupName: state.log_group_name, + logStreamName: state.log_stream_name}) + flush(state, opts) + {:error, {"ResourceNotFoundException", "The specified log stream does not exist."}} -> + {:ok, _, _} = AwsProxy.create_log_stream(state.client, %{logGroupName: state.log_group_name, + logStreamName: state.log_stream_name}) + flush(state, opts) + {:error, %HTTPoison.Error{id: nil, reason: reason}} when reason in [:closed, :connect_timeout, :timeout] -> + flush(state, opts) end end From 7a777d846760bbb3c8689b2cb88fb640818effdd Mon Sep 17 00:00:00 2001 From: Jake Morrison Date: Wed, 8 Aug 2018 15:57:38 +0000 Subject: [PATCH 16/18] Cleanup and commenting --- lib/cloud_watch.ex | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/lib/cloud_watch.ex b/lib/cloud_watch.ex index b56f633..d3a650d 100644 --- a/lib/cloud_watch.ex +++ b/lib/cloud_watch.ex @@ -42,7 +42,8 @@ defmodule CloudWatch do def handle_event({level, _gl, {Logger, msg, ts, md}}, state) do case Logger.compare_levels(level, state.level) do - :lt -> {:ok, state} + :lt -> + {:ok, state} _ -> %{buffer: buffer, buffer_size: buffer_size} = state @@ -52,9 +53,7 @@ defmodule CloudWatch do buffer = List.insert_at(buffer, -1, %InputLogEvent{message: message, timestamp: ts}) - state - |> Map.merge(%{buffer: buffer, buffer_size: buffer_size + byte_size(message) + 26}) - |> flush() + flush(%{state | buffer: buffer, buffer_size: buffer_size + byte_size(message) + 26}) end end @@ -109,6 +108,7 @@ defmodule CloudWatch do } if state.access_key_id do + # Static AWS config %{state | client: AwsProxy.client(state.access_key_id, state.secret_access_key, state.region, state.endpoint)} else configure_aws(state) @@ -139,6 +139,7 @@ defmodule CloudWatch do end uri -> # https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html + # This is untested case get_metadata("http://169.254.170.2" <> uri) do {:ok, json} -> {:ok, creds} = Poison.decode(json) @@ -163,6 +164,7 @@ defmodule CloudWatch do defp flush(%{buffer: []} = state, _opts), do: {:ok, state} + # Client not configured yet defp flush(%{client: nil} = state, _opts), do: {:ok, state} defp flush(state, opts) do @@ -200,12 +202,6 @@ defmodule CloudWatch do _ -> nil end - # case HTTPoison.get(url) do - # {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> - # {:ok, body} - # _ -> - # nil - # end end def get_metadata!(url) do @@ -216,12 +212,6 @@ defmodule CloudWatch do _ -> nil end - # case HTTPoison.get(url) do - # {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> - # body - # _ -> - # nil - # end end def metadata_endpoint do @@ -241,12 +231,6 @@ defmodule CloudWatch do _ -> nil end - # case HTTPoison.get(url) do - # {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> - # String.slice(body, Range.new(0, -2)) - # _ -> - # nil - # end end end From 2709093ad5f7093ad0b67af427af0687cf86b41f Mon Sep 17 00:00:00 2001 From: Jake Morrison Date: Wed, 8 Aug 2018 18:51:24 +0000 Subject: [PATCH 17/18] Update deps --- mix.exs | 2 +- mix.lock | 29 +++++++++++++++-------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/mix.exs b/mix.exs index 10e79f4..1cb37e1 100644 --- a/mix.exs +++ b/mix.exs @@ -36,7 +36,7 @@ defmodule CloudWatch.Mixfile do # Type "mix help deps" for more examples and options defp deps do [ - # {:aws, "~> 0.5.0", optional: true}, + {:aws, "~> 0.5.0", optional: true}, {:httpoison, "~> 0.11"}, {:poison, "~> 3.1"}, {:hackney, "~> 1.8"}, diff --git a/mix.lock b/mix.lock index d48b6dd..7bbeb31 100644 --- a/mix.lock +++ b/mix.lock @@ -1,24 +1,25 @@ %{ "aws": {:hex, :aws, "0.5.0", "e3916abd9d1fb4c182cbeac8a964ddeb32de9b20d441b66fba838ade06a4a278", [:mix], [{:httpoison, "~> 0.11.1", [hex: :httpoison, repo: "hexpm", optional: false]}, {:poison, "~> 3.1", [hex: :poison, repo: "hexpm", optional: false]}, {:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm"}, "bunt": {:hex, :bunt, "0.1.6", "5d95a6882f73f3b9969fdfd1953798046664e6f77ec4e486e6fafc7caad97c6f", [:mix], [], "hexpm"}, - "certifi": {:hex, :certifi, "2.3.1", "d0f424232390bf47d82da8478022301c561cf6445b5b5fb6a84d49a9e76d2639", [:rebar3], [{:parse_trans, "3.2.0", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, - "combine": {:hex, :combine, "0.9.6", "8d1034a127d4cbf6924c8a5010d3534d958085575fa4d9b878f200d79ac78335", [:mix], [], "hexpm"}, + "certifi": {:hex, :certifi, "1.2.1", "c3904f192bd5284e5b13f20db3ceac9626e14eeacfbb492e19583cf0e37b22be", [:rebar3], [], "hexpm"}, + "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm"}, "credo": {:hex, :credo, "0.4.14", "594a965ae2224997fae9e705e881983911a052c701b05bd3bcf89af44b5f6b45", [:mix], [{:bunt, "~> 0.1.6", [hex: :bunt, repo: "hexpm", optional: false]}], "hexpm"}, - "earmark": {:hex, :earmark, "1.2.2", "f718159d6b65068e8daeef709ccddae5f7fdc770707d82e7d126f584cd925b74", [:mix], [], "hexpm"}, - "ex_aws": {:hex, :ex_aws, "2.1.0", "b92651527d6c09c479f9013caa9c7331f19cba38a650590d82ebf2c6c16a1d8a", [:mix], [{:configparser_ex, "~> 2.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "1.6.3 or 1.6.5 or 1.7.1 or 1.8.6 or ~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:poison, ">= 1.2.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:xml_builder, "~> 0.1.0", [hex: :xml_builder, repo: "hexpm", optional: true]}], "hexpm"}, - "ex_doc": {:hex, :ex_doc, "0.16.2", "3b3e210ebcd85a7c76b4e73f85c5640c011d2a0b2f06dcdf5acdb2ae904e5084", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"}, - "gettext": {:hex, :gettext, "0.13.1", "5e0daf4e7636d771c4c71ad5f3f53ba09a9ae5c250e1ab9c42ba9edccc476263", [:mix], [], "hexpm"}, - "hackney": {:hex, :hackney, "1.13.0", "24edc8cd2b28e1c652593833862435c80661834f6c9344e84b6a2255e7aeef03", [:rebar3], [{:certifi, "2.3.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.2", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, - "httpoison": {:hex, :httpoison, "0.13.0", "bfaf44d9f133a6599886720f3937a7699466d23bb0cd7a88b6ba011f53c6f562", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, - "idna": {:hex, :idna, "5.1.2", "e21cb58a09f0228a9e0b95eaa1217f1bcfc31a1aaa6e1fdf2f53a33f7dbd9494", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, - "meck": {:hex, :meck, "0.8.11", "2c39e15ec87d847da6cf69b4a1c4af3fd850ae2a272e719e0e8751a7fe54771f", [:rebar3], [], "hexpm"}, + "earmark": {:hex, :earmark, "1.2.5", "4d21980d5d2862a2e13ec3c49ad9ad783ffc7ca5769cf6ff891a4553fbaae761", [:mix], [], "hexpm"}, + "ex_doc": {:hex, :ex_doc, "0.19.1", "519bb9c19526ca51d326c060cb1778d4a9056b190086a8c6c115828eaccea6cf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.7", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, + "gettext": {:hex, :gettext, "0.15.0", "40a2b8ce33a80ced7727e36768499fc9286881c43ebafccae6bab731e2b2b8ce", [:mix], [], "hexpm"}, + "hackney": {:hex, :hackney, "1.8.6", "21a725db3569b3fb11a6af17d5c5f654052ce9624219f1317e8639183de4a423", [:rebar3], [{:certifi, "1.2.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.0.2", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, + "httpoison": {:hex, :httpoison, "0.11.2", "9e59f17a473ef6948f63c51db07320477bad8ba88cf1df60a3eee01150306665", [:mix], [{:hackney, "~> 1.8.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, + "idna": {:hex, :idna, "5.0.2", "ac203208ada855d95dc591a764b6e87259cb0e2a364218f215ad662daa8cd6b4", [:rebar3], [{:unicode_util_compat, "0.2.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, + "makeup": {:hex, :makeup, "0.5.1", "966c5c2296da272d42f1de178c1d135e432662eca795d6dc12e5e8787514edf7", [:mix], [{:nimble_parsec, "~> 0.2.2", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.8.0", "1204a2f5b4f181775a0e456154830524cf2207cf4f9112215c05e0b76e4eca8b", [:mix], [{:makeup, "~> 0.5.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 0.2.2", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, + "meck": {:hex, :meck, "0.8.12", "1f7b1a9f5d12c511848fec26bbefd09a21e1432eadb8982d9a8aceb9891a3cf2", [:rebar3], [], "hexpm"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"}, "mock": {:hex, :mock, "0.2.1", "bfdba786903e77f9c18772dee472d020ceb8ef000783e737725a4c8f54ad28ec", [:mix], [{:meck, "~> 0.8.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"}, - "parse_trans": {:hex, :parse_trans, "3.2.0", "2adfa4daf80c14dc36f522cf190eb5c4ee3e28008fc6394397c16f62a26258c2", [:rebar3], [], "hexpm"}, + "nimble_parsec": {:hex, :nimble_parsec, "0.2.2", "d526b23bdceb04c7ad15b33c57c4526bf5f50aaa70c7c141b4b4624555c68259", [:mix], [], "hexpm"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"}, - "timex": {:hex, :timex, "3.1.21", "7d1ec0f73c4668bea71f38af6357d75992f356a7e032c69620c5e87ca4b95c66", [:mix], [{:combine, "~> 0.7", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"}, - "tzdata": {:hex, :tzdata, "0.5.12", "1c17b68692c6ba5b6ab15db3d64cc8baa0f182043d5ae9d4b6d35d70af76f67b", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, - "unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm"}, + "timex": {:hex, :timex, "3.3.0", "e0695aa0ddb37d460d93a2db34d332c2c95a40c27edf22fbfea22eb8910a9c8d", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"}, + "tzdata": {:hex, :tzdata, "0.5.17", "50793e3d85af49736701da1a040c415c97dc1caf6464112fd9bd18f425d3053b", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.2.0", "dbbccf6781821b1c0701845eaf966c9b6d83d7c3bfc65ca2b78b88b8678bfa35", [:rebar3], [], "hexpm"}, } From c0147eac0ef1bdf5433cddd95c4b98dc98dbd09f Mon Sep 17 00:00:00 2001 From: Jake Morrison Date: Wed, 8 Aug 2018 19:17:06 +0000 Subject: [PATCH 18/18] Doc --- lib/cloud_watch.ex | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/cloud_watch.ex b/lib/cloud_watch.ex index d3a650d..9e8192b 100644 --- a/lib/cloud_watch.ex +++ b/lib/cloud_watch.ex @@ -11,6 +11,8 @@ defmodule CloudWatch do @spec init(module()) :: {:ok, term()} | {:error, term()} def init(__MODULE__) do + # Because this is a plugin to Logger, we can't rely on application + # dependencies to be started before this is called {:ok, _} = Application.ensure_all_started(:hackney) env = Application.get_env(:logger, __MODULE__, [])