Current section

26 Versions

Jump to

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},