Current section
26 Versions
Jump to
Current section
26 Versions
Compare versions
5
files changed
+190
additions
-11
deletions
| @@ -1,3 +1,6 @@ | |
| 1 | + # 0.3.7 |
| 2 | + - feat: add metrics module |
| 3 | + |
| 1 4 | # 0.3.6 |
| 2 5 | - chore: fix child_spec type |
| @@ -1,6 +1,6 @@ | |
| 1 1 | {<<"links">>,[{<<"GitHub">>,<<"https://github.com/mikaak/elixir_cache">>}]}. |
| 2 2 | {<<"name">>,<<"elixir_cache">>}. |
| 3 | - {<<"version">>,<<"0.3.6">>}. |
| 3 | + {<<"version">>,<<"0.3.7">>}. |
| 4 4 | {<<"description">>, |
| 5 5 | <<"Standardized and testable caching across your app. In test caches are isolated.">>}. |
| 6 6 | {<<"elixir">>,<<"~> 1.11">>}. |
| @@ -11,8 +11,9 @@ | |
| 11 11 | <<"lib/cache.ex">>,<<"lib/cache">>,<<"lib/cache/con_cache.ex">>, |
| 12 12 | <<"lib/cache/redis">>,<<"lib/cache/redis/global.ex">>, |
| 13 13 | <<"lib/cache/redis/json.ex">>,<<"lib/cache/redis/hash.ex">>, |
| 14 | - <<"lib/cache/dets.ex">>,<<"lib/cache/sandbox_registry.ex">>, |
| 15 | - <<"lib/cache/ets.ex">>,<<"lib/cache/sandbox.ex">>,<<"lib/cache/redis.ex">>, |
| 14 | + <<"lib/cache/metrics.ex">>,<<"lib/cache/dets.ex">>, |
| 15 | + <<"lib/cache/sandbox_registry.ex">>,<<"lib/cache/ets.ex">>, |
| 16 | + <<"lib/cache/sandbox.ex">>,<<"lib/cache/redis.ex">>, |
| 16 17 | <<"lib/cache/term_encoder.ex">>,<<"lib/cache/agent.ex">>]}. |
| 17 18 | {<<"requirements">>, |
| 18 19 | [[{<<"name">>,<<"error_message">>}, |
| @@ -59,5 +60,10 @@ | |
| 59 60 | {<<"app">>,<<"telemetry_metrics">>}, |
| 60 61 | {<<"optional">>,false}, |
| 61 62 | {<<"requirement">>,<<"~> 1.0">>}, |
| 63 | + {<<"repository">>,<<"hexpm">>}], |
| 64 | + [{<<"name">>,<<"prometheus_telemetry">>}, |
| 65 | + {<<"app">>,<<"prometheus_telemetry">>}, |
| 66 | + {<<"optional">>,true}, |
| 67 | + {<<"requirement">>,<<"~> 0.3">>}, |
| 62 68 | {<<"repository">>,<<"hexpm">>}]]}. |
| 63 69 | {<<"build_tools">>,[<<"mix">>]}. |
| @@ -10,7 +10,9 @@ defmodule Cache do | |
| 10 10 | |
| 11 11 | @callback opts_definition() :: Keyword.t() |
| 12 12 | |
| 13 | - @callback start_link(cache_opts :: Keyword.t()) :: {:ok, pid()} | {:error, {:already_started, pid()} | {:shutdown, term()} | term()} | :ignore |
| 13 | + @callback start_link( |
| 14 | + cache_opts :: Keyword.t() |
| 15 | + ) :: {:ok, pid()} | {:error, {:already_started, pid()} | {:shutdown, term()} | term()} | :ignore |
| 14 16 | |
| 15 17 | @callback put(cache_name :: atom, key :: atom | String.t(), ttl :: pos_integer, value :: any) :: |
| 16 18 | :ok | ErrorMessage.t() |
| @@ -112,22 +114,75 @@ defmodule Cache do | |
| 112 114 | value = Cache.TermEncoder.encode(value, @compression_level) |
| 113 115 | key = maybe_sandbox_key(key) |
| 114 116 | |
| 115 | - @cache_adapter.put(@cache_name, key, ttl, value, adapter_options()) |
| 117 | + :telemetry.span( |
| 118 | + [:elixir_cache, :cache, :put], |
| 119 | + %{cache_name: @cache_name}, |
| 120 | + fn -> |
| 121 | + result = with {:error, error} = e <- @cache_adapter.put(@cache_name, key, ttl, value, adapter_options()) do |
| 122 | + :telemetry.execute([:elixir_cache, :cache, :put, :error], %{count: 1}, %{ |
| 123 | + cache_name: @cache_name, |
| 124 | + error: error |
| 125 | + }) |
| 126 | + |
| 127 | + e |
| 128 | + end |
| 129 | + |
| 130 | + {result, %{cache_name: @cache_name}} |
| 131 | + end |
| 132 | + ) |
| 116 133 | end |
| 117 134 | |
| 118 135 | def get(key) do |
| 119 136 | key = maybe_sandbox_key(key) |
| 120 137 | |
| 121 | - with {:ok, value} when not is_nil(value) <- |
| 122 | - @cache_adapter.get(@cache_name, key, adapter_options()) do |
| 123 | - {:ok, Cache.TermEncoder.decode(value)} |
| 124 | - end |
| 138 | + :telemetry.span( |
| 139 | + [:elixir_cache, :cache, :get], |
| 140 | + %{cache_name: @cache_name}, |
| 141 | + fn -> |
| 142 | + result = |
| 143 | + case @cache_adapter.get(@cache_name, key, adapter_options()) do |
| 144 | + {:ok, nil} = res -> |
| 145 | + :telemetry.execute([:elixir_cache, :cache, :get, :miss], %{count: 1}, %{ |
| 146 | + cache_name: @cache_name |
| 147 | + }) |
| 148 | + |
| 149 | + res |
| 150 | + |
| 151 | + {:ok, value} -> {:ok, Cache.TermEncoder.decode(value)} |
| 152 | + |
| 153 | + {:error, error} = e -> |
| 154 | + :telemetry.execute([:elixir_cache, :cache, :get, :error], %{count: 1}, %{ |
| 155 | + cache_name: @cache_name, |
| 156 | + error: error |
| 157 | + }) |
| 158 | + |
| 159 | + e |
| 160 | + end |
| 161 | + |
| 162 | + {result, %{cache_name: @cache_name}} |
| 163 | + end |
| 164 | + ) |
| 125 165 | end |
| 126 166 | |
| 127 167 | def delete(key) do |
| 128 168 | key = maybe_sandbox_key(key) |
| 129 169 | |
| 130 | - @cache_adapter.delete(@cache_name, key, adapter_options()) |
| 170 | + :telemetry.span( |
| 171 | + [:elixir_cache, :cache, :delete], |
| 172 | + %{cache_name: @cache_name}, |
| 173 | + fn -> |
| 174 | + result = with {:error, error} = e <- @cache_adapter.delete(@cache_name, key, adapter_options()) do |
| 175 | + :telemetry.execute([:elixir_cache, :cache, :delete, :error], %{count: 1}, %{ |
| 176 | + cache_name: @cache_name, |
| 177 | + error: error |
| 178 | + }) |
| 179 | + |
| 180 | + e |
| 181 | + end |
| 182 | + |
| 183 | + {result, %{cache_name: @cache_name}} |
| 184 | + end |
| 185 | + ) |
| 131 186 | end |
| 132 187 | |
| 133 188 | def get_or_create(key, fnc) do |
| @@ -0,0 +1,114 @@ | |
| 1 | + if Application.ensure_loaded(:prometheus_telemetry) === :ok do |
| 2 | + defmodule Cache.Metrics do |
| 3 | + @moduledoc """ |
| 4 | + Add the following metrics for elixir_cache: |
| 5 | + |
| 6 | + Metrics included: |
| 7 | + - `elixir_cache.cache.get.count` |
| 8 | + - `elixir_cache.cache.get.miss.count` |
| 9 | + - `elixir_cache.cache.get.error.count` |
| 10 | + - `elixir_cache.cache.put.count` |
| 11 | + - `elixir_cache.cache.delete.count` |
| 12 | + - `elixir_cache.cache.get.duration.millisecond` |
| 13 | + """ |
| 14 | + |
| 15 | + import Telemetry.Metrics, only: [counter: 2, distribution: 2] |
| 16 | + |
| 17 | + @duration_unit {:native, :millisecond} |
| 18 | + @buckets PrometheusTelemetry.Config.default_millisecond_buckets() |
| 19 | + |
| 20 | + def metrics do |
| 21 | + [ |
| 22 | + counter("elixir_cache.cache.get.count", |
| 23 | + event_name: [:elixir_cache, :cache, :get, :start], |
| 24 | + measurement: :count, |
| 25 | + description: "Total cache calls", |
| 26 | + tags: [:cache_name] |
| 27 | + ), |
| 28 | + |
| 29 | + counter("elixir_cache.cache.get.miss.count", |
| 30 | + event_name: [:elixir_cache, :cache, :get, :miss], |
| 31 | + measurement: :count, |
| 32 | + description: "Cache miss count", |
| 33 | + tags: [:cache_name] |
| 34 | + ), |
| 35 | + |
| 36 | + counter("elixir_cache.cache.get.error.count", |
| 37 | + event_name: [:elixir_cache, :cache, :get, :error], |
| 38 | + measurement: :count, |
| 39 | + description: "Cache error count", |
| 40 | + tags: [:cache_name, :error], |
| 41 | + tag_values: &extract_error_metadata/1 |
| 42 | + ), |
| 43 | + |
| 44 | + counter("elixir_cache.cache.put.count", |
| 45 | + event_name: [:elixir_cache, :cache, :put], |
| 46 | + measurement: :count, |
| 47 | + description: "Cache put count", |
| 48 | + tags: [:cache_name] |
| 49 | + ), |
| 50 | + |
| 51 | + counter("elixir_cache.cache.put.error.count", |
| 52 | + event_name: [:elixir_cache, :cache, :put, :error], |
| 53 | + measurement: :count, |
| 54 | + description: "Cache error count", |
| 55 | + tags: [:cache_name, :error], |
| 56 | + tag_values: &extract_error_metadata/1 |
| 57 | + ), |
| 58 | + |
| 59 | + counter("elixir_cache.cache.delete.count", |
| 60 | + event_name: [:elixir_cache, :cache, :delete], |
| 61 | + measurement: :count, |
| 62 | + description: "Cache delete count", |
| 63 | + tags: [:cache_name] |
| 64 | + ), |
| 65 | + |
| 66 | + counter("elixir_cache.cache.delete.error.count", |
| 67 | + event_name: [:elixir_cache, :cache, :delete, :error], |
| 68 | + measurement: :count, |
| 69 | + description: "Cache error count", |
| 70 | + tags: [:cache_name, :error], |
| 71 | + tag_values: &extract_error_metadata/1 |
| 72 | + ), |
| 73 | + |
| 74 | + distribution("elixir_cache.cache.get.duration.millisecond", |
| 75 | + event_name: [:elixir_cache, :cache, :get, :stop], |
| 76 | + measurement: :duration, |
| 77 | + description: "Time taken for cache get", |
| 78 | + tags: [:cache_name], |
| 79 | + unit: @duration_unit, |
| 80 | + reporter_options: [buckets: @buckets] |
| 81 | + ), |
| 82 | + |
| 83 | + distribution("elixir_cache.cache.put.duration.millisecond", |
| 84 | + event_name: [:elixir_cache, :cache, :put, :stop], |
| 85 | + measurement: :duration, |
| 86 | + description: "Time taken for cache put", |
| 87 | + tags: [:cache_name], |
| 88 | + unit: @duration_unit, |
| 89 | + reporter_options: [buckets: @buckets] |
| 90 | + ), |
| 91 | + |
| 92 | + distribution("elixir_cache.cache.delete.duration.millisecond", |
| 93 | + event_name: [:elixir_cache, :cache, :delete, :stop], |
| 94 | + measurement: :duration, |
| 95 | + description: "Time taken for cache delete", |
| 96 | + tags: [:cache_name], |
| 97 | + unit: @duration_unit, |
| 98 | + reporter_options: [buckets: @buckets] |
| 99 | + ) |
| 100 | + ] |
| 101 | + end |
| 102 | + |
| 103 | + defp extract_error_metadata(metadata) do |
| 104 | + metadata |
| 105 | + |> Map.take([:error, :cache_name]) |
| 106 | + |> Map.update(:error, "unknown", fn |
| 107 | + %ErrorMessage{code: code, message: error} -> "code}: #{error}" |
| 108 | + reason when is_atom(reason) -> to_string(reason) |
| 109 | + reason when is_binary(reason) -> reason |
| 110 | + _ -> "unknown" |
| 111 | + end) |
| 112 | + end |
| 113 | + end |
| 114 | + end |
| @@ -4,7 +4,7 @@ defmodule ElixirCache.MixProject do | |
| 4 4 | def project do |
| 5 5 | [ |
| 6 6 | app: :elixir_cache, |
| 7 | - version: "0.3.6", |
| 7 | + version: "0.3.7", |
| 8 8 | elixir: "~> 1.11", |
| 9 9 | start_permanent: Mix.env() == :prod, |
| 10 10 | description: "Standardized and testable caching across your app. In test caches are isolated.", |
| @@ -49,6 +49,7 @@ defmodule ElixirCache.MixProject do | |
| 49 49 | |
| 50 50 | {:telemetry, "~> 1.1"}, |
| 51 51 | {:telemetry_metrics, "~> 1.0"}, |
| 52 | + {:prometheus_telemetry, "~> 0.3", optional: true}, |
| 52 53 | |
| 53 54 | {:faker, "~> 0.17", only: [:test]}, |
| 54 55 | {:credo, "~> 1.6", only: [:test, :dev], runtime: false}, |