Skip to content

Commit 8c8c01c

Browse files
committed
feat: rename Monitoring to Telemetry with enhanced logging and render duration tracking
BREAKING CHANGE: `Phoenix.React.Monitoring` has been renamed to `Phoenix.React.Telemetry` - Rename Phoenix.React.Monitoring module to Phoenix.React.Telemetry for better clarity - Add structured logging with visual indicators (✓/✗) for success/error states - Implement comprehensive render duration tracking across all render methods - Add cache hit/miss telemetry events with automatic tracking in Cache module - Enhance all logging with [Phoenix.React] prefix for better log filtering - Add new telemetry functions: record_cache_hit/2 and record_cache_miss/2 - Improve measure/3 function with better error logging and duration tracking - Update Server module to track and log render duration for all three methods: - render_to_readable_stream - render_to_string - render_to_static_markup - Add comprehensive telemetry documentation to CLAUDE.md with examples - Update all references in Deno runtime and integration tests - Maintain backward compatibility for telemetry event names Log format examples: - Render: "[Phoenix.React] ✓ Rendered 'chart' in 45ms (method: render_to_string, result: ok)" - Runtime: "[Phoenix.React] Runtime Bun started on port 5225" - Cache: "[Phoenix.React] Cache hit for 'my_component' (method: render_to_string)" - Build: "[Phoenix.React] ✓ Build completed for Bun in 1234ms (result: ok)"
1 parent 329b8d8 commit 8c8c01c

File tree

6 files changed

+322
-55
lines changed

6 files changed

+322
-55
lines changed

CLAUDE.md

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ The system consists of three main layers:
2424
- `Phoenix.React.Cache` - ETS-based caching layer with TTL support
2525
- `Phoenix.React.Helper` - Phoenix.Component integration with multiple rendering modes
2626
- `Phoenix.React.Config` - Centralized configuration management
27-
- `Phoenix.React.Monitoring` - Telemetry and performance monitoring
27+
- `Phoenix.React.Telemetry` - Telemetry and performance monitoring with structured logging
2828
- `Phoenix.React.Runtime.FileWatcher` - File watching for development hot reload
2929

3030
### Rendering Flow
@@ -247,6 +247,111 @@ hydrateRoot(container, <MyComponent data={window.data} />);
247247
- Cache can be disabled by setting `cache_ttl: 0`
248248
- ETS-based in-memory caching with automatic garbage collection
249249
- Cache invalidation occurs on component file changes in development
250+
- Cache hits/misses are automatically tracked via telemetry events
251+
252+
## Telemetry and Monitoring
253+
254+
Phoenix.React includes comprehensive telemetry support via `Phoenix.React.Telemetry` for monitoring performance and tracking events.
255+
256+
### Telemetry Events
257+
258+
The following telemetry events are emitted automatically:
259+
260+
**Render Events** - `[:phoenix, :react, :render]`
261+
- Measurements: `%{duration: duration_ms}`
262+
- Metadata: `%{component: component, method: method, result: result, timestamp: timestamp}`
263+
- Logged as: `[Phoenix.React] ✓ Rendered 'chart' in 45ms (method: render_to_string, result: ok)`
264+
265+
**Cache Events**
266+
- `[:phoenix, :react, :cache, :hit]` - Fired on cache hits
267+
- `[:phoenix, :react, :cache, :miss]` - Fired on cache misses
268+
- Metadata: `%{component: component, method: method, timestamp: timestamp}`
269+
270+
**Runtime Events**
271+
- `[:phoenix, :react, :runtime_startup]` - Fired when runtime starts
272+
- `[:phoenix, :react, :runtime_shutdown]` - Fired when runtime stops
273+
- Metadata: `%{runtime: runtime_name, port: port, timestamp: timestamp}`
274+
275+
**Build Events** - `[:phoenix, :react, :build]`
276+
- Measurements: `%{duration: duration_ms}`
277+
- Metadata: `%{runtime: runtime_name, result: result, timestamp: timestamp}`
278+
279+
**File Change Events** - `[:phoenix, :react, :file_change]`
280+
- Metadata: `%{path: path, action: action, timestamp: timestamp}`
281+
282+
### Attaching Telemetry Handlers
283+
284+
Attach handlers in your application's telemetry module:
285+
286+
```elixir
287+
defmodule MyApp.Telemetry do
288+
def attach_handlers do
289+
:telemetry.attach_many(
290+
"phoenix-react-telemetry",
291+
[
292+
[:phoenix, :react, :render],
293+
[:phoenix, :react, :cache, :hit],
294+
[:phoenix, :react, :cache, :miss],
295+
[:phoenix, :react, :runtime_startup],
296+
[:phoenix, :react, :build]
297+
],
298+
&handle_event/4,
299+
%{}
300+
)
301+
end
302+
303+
def handle_event([:phoenix, :react, :render], %{duration: duration}, metadata, _config) do
304+
# Send to your metrics system (Prometheus, StatsD, etc.)
305+
MyMetrics.histogram("phoenix_react.render.duration", duration,
306+
tags: ["component:#{metadata.component}", "method:#{metadata.method}"]
307+
)
308+
end
309+
310+
def handle_event([:phoenix, :react, :cache, :hit], _measurements, metadata, _config) do
311+
MyMetrics.increment("phoenix_react.cache.hits",
312+
tags: ["component:#{metadata.component}"]
313+
)
314+
end
315+
316+
# ... other handlers
317+
end
318+
```
319+
320+
### Structured Logging
321+
322+
All telemetry events are automatically logged with structured formatting:
323+
324+
- **Render Duration**: `[Phoenix.React] ✓ Rendered 'my_component' in 45ms (method: render_to_string, result: ok)`
325+
- **Runtime Startup**: `[Phoenix.React] Runtime Bun started on port 5225`
326+
- **Cache Events**: `[Phoenix.React] Cache hit for 'my_component' (method: render_to_string)`
327+
- **Build Events**: `[Phoenix.React] ✓ Build completed for Bun in 1234ms (result: ok)`
328+
329+
### Health Checks
330+
331+
Use telemetry for runtime health monitoring:
332+
333+
```elixir
334+
case Phoenix.React.Telemetry.health_check("Bun", 5225) do
335+
{:ok, metadata} ->
336+
# Runtime is healthy
337+
Logger.info("Runtime healthy: #{metadata.response_time_ms}ms")
338+
339+
{:error, reason} ->
340+
# Runtime is unhealthy
341+
Logger.error("Runtime unhealthy: #{inspect(reason)}")
342+
end
343+
```
344+
345+
### Custom Measurements
346+
347+
Wrap operations with telemetry measurements:
348+
349+
```elixir
350+
Phoenix.React.Telemetry.measure("custom_operation", [:my_app, :custom], fn ->
351+
# Your operation here
352+
do_expensive_work()
353+
end)
354+
```
250355

251356
## File Structure
252357

lib/phoenix/react/cache.ex

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,15 @@ defmodule Phoenix.React.Cache do
115115
"""
116116
@spec get(String.t(), map(), cache_method()) :: String.t() | nil
117117
def get(component, props, method) do
118-
lookup(component, props, method)
118+
case lookup(component, props, method) do
119+
nil ->
120+
Phoenix.React.Telemetry.record_cache_miss(component, method)
121+
nil
122+
123+
result ->
124+
Phoenix.React.Telemetry.record_cache_hit(component, method)
125+
result
126+
end
119127
end
120128

121129
@doc """

lib/phoenix/react/runtime/deno.ex

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ defmodule Phoenix.React.Runtime.Deno do
113113
"Deno.Server started on port: #{inspect(port)} and OS pid: #{get_port_os_pid(port)}"
114114
)
115115

116-
Phoenix.React.Monitoring.record_runtime_startup("Deno", config()[:port])
116+
Phoenix.React.Telemetry.record_runtime_startup("Deno", config()[:port])
117117

118118
Phoenix.React.Server.set_runtime_process(self())
119119

@@ -265,16 +265,16 @@ defmodule Phoenix.React.Runtime.Deno do
265265
server_port = config()[:port]
266266
timeout = state.render_timeout
267267

268-
Phoenix.React.Monitoring.measure(
268+
Phoenix.React.Telemetry.measure(
269269
"render_#{method}_#{component}",
270270
[:phoenix, :react, :render],
271271
fn ->
272272
result = make_http_request(server_port, Atom.to_string(method), component, props, timeout)
273273

274-
# Record the result for monitoring
274+
# Record the result for telemetry
275275
case result do
276-
{:ok, _} -> Phoenix.React.Monitoring.record_render(component, method, 0, :ok)
277-
{:error, _} -> Phoenix.React.Monitoring.record_render(component, method, 0, :error)
276+
{:ok, _} -> Phoenix.React.Telemetry.record_render(component, method, 0, :ok)
277+
{:error, _} -> Phoenix.React.Telemetry.record_render(component, method, 0, :error)
278278
end
279279

280280
result
@@ -285,7 +285,7 @@ defmodule Phoenix.React.Runtime.Deno do
285285
@impl true
286286
def terminate(reason, state) do
287287
Logger.debug("Deno.Server terminating")
288-
Phoenix.React.Monitoring.record_runtime_shutdown("Deno", reason)
288+
Phoenix.React.Telemetry.record_runtime_shutdown("Deno", reason)
289289
cleanup_runtime_process(state.runtime_port, reason)
290290
end
291291
end

lib/phoenix/react/server.ex

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -164,21 +164,30 @@ defmodule Phoenix.React.Server do
164164
_from,
165165
%{runtime_process: runtime_process} = state
166166
) do
167+
start_time = System.monotonic_time(:millisecond)
168+
167169
reply =
168170
case Cache.get(component, props, :render_to_readable_stream) do
169171
nil ->
170172
render_timeout = config()[:render_timeout]
171173

172-
case GenServer.call(
173-
runtime_process,
174-
{:render_to_readable_stream, component, props},
175-
render_timeout
176-
) do
174+
result =
175+
GenServer.call(
176+
runtime_process,
177+
{:render_to_readable_stream, component, props},
178+
render_timeout
179+
)
180+
181+
duration = System.monotonic_time(:millisecond) - start_time
182+
183+
case result do
177184
{:ok, html} = reply ->
178185
Cache.put(component, props, :render_to_readable_stream, html)
186+
Phoenix.React.Telemetry.record_render(component, :render_to_readable_stream, duration, :ok)
179187
reply
180188

181-
reply ->
189+
{:error, _} = reply ->
190+
Phoenix.React.Telemetry.record_render(component, :render_to_readable_stream, duration, :error)
182191
reply
183192
end
184193

@@ -194,21 +203,30 @@ defmodule Phoenix.React.Server do
194203
_from,
195204
%{runtime_process: runtime_process} = state
196205
) do
206+
start_time = System.monotonic_time(:millisecond)
207+
197208
reply =
198209
case Cache.get(component, props, :render_to_string) do
199210
nil ->
200211
render_timeout = config()[:render_timeout]
201212

202-
case GenServer.call(
203-
runtime_process,
204-
{:render_to_string, component, props},
205-
render_timeout
206-
) do
213+
result =
214+
GenServer.call(
215+
runtime_process,
216+
{:render_to_string, component, props},
217+
render_timeout
218+
)
219+
220+
duration = System.monotonic_time(:millisecond) - start_time
221+
222+
case result do
207223
{:ok, html} = reply ->
208224
Cache.put(component, props, :render_to_string, html)
225+
Phoenix.React.Telemetry.record_render(component, :render_to_string, duration, :ok)
209226
reply
210227

211-
reply ->
228+
{:error, _} = reply ->
229+
Phoenix.React.Telemetry.record_render(component, :render_to_string, duration, :error)
212230
reply
213231
end
214232

@@ -224,21 +242,30 @@ defmodule Phoenix.React.Server do
224242
_from,
225243
%{runtime_process: runtime_process} = state
226244
) do
245+
start_time = System.monotonic_time(:millisecond)
246+
227247
reply =
228248
case Cache.get(component, props, :render_to_static_markup) do
229249
nil ->
230250
render_timeout = config()[:render_timeout]
231251

232-
case GenServer.call(
233-
runtime_process,
234-
{:render_to_static_markup, component, props},
235-
render_timeout
236-
) do
252+
result =
253+
GenServer.call(
254+
runtime_process,
255+
{:render_to_static_markup, component, props},
256+
render_timeout
257+
)
258+
259+
duration = System.monotonic_time(:millisecond) - start_time
260+
261+
case result do
237262
{:ok, html} = reply ->
238263
Cache.put(component, props, :render_to_static_markup, html)
264+
Phoenix.React.Telemetry.record_render(component, :render_to_static_markup, duration, :ok)
239265
reply
240266

241-
reply ->
267+
{:error, _} = reply ->
268+
Phoenix.React.Telemetry.record_render(component, :render_to_static_markup, duration, :error)
242269
reply
243270
end
244271

0 commit comments

Comments
 (0)