Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,22 @@ HTTP.Telemetry.request_stop(200, URI.parse("https://example.com"), 1024, 1500)
HTTP.Telemetry.request_exception(URI.parse("https://example.com"), :timeout, 5000)
```

### HTTP.Logger
The library includes a logger that can be attached to telemetry events to provide detailed logging of HTTP requests. To enable the logger, simply call `HTTP.Logger.attach()` in your application's startup code, for example in `application.ex`:

```elixir
defmodule MyApp.Application do
use Application

def start(_type, _args) do
# Enable HTTP fetch logging
HTTP.Logger.attach()

# ... rest of your application startup
end
end
```

### HTTP.Request
Request configuration struct.

Expand Down
91 changes: 91 additions & 0 deletions lib/http/logger.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
defmodule HTTP.Logger do
@moduledoc """
Attaches to HTTP telemetry events and logs them.
"""
require Logger

@doc """
Attaches the logger to the telemetry events.
"""
def attach do
:telemetry.attach_many(
"http-logger",
[
[:http_fetch, :request, :start],
[:http_fetch, :request, :stop],
[:http_fetch, :request, :exception],
[:http_fetch, :response, :body_read_start],
[:http_fetch, :response, :body_read_stop],
[:http_fetch, :streaming, :start],
[:http_fetch, :streaming, :chunk],
[:http_fetch, :streaming, :stop]
],
&__MODULE__.handle_event/4,
nil
)
end

def handle_event([:http_fetch, :request, :start], _measurements, metadata, _config) do
Logger.info(
"#{metadata.method} #{metadata.url} - Request started",
request_id: metadata.url
)
end

def handle_event([:http_fetch, :request, :stop], measurements, metadata, _config) do
Logger.info(
"#{metadata.status} #{metadata.url} - Request completed in #{measurements.duration}µs",
request_id: metadata.url,
duration: measurements.duration,
status: measurements.status,
response_size: measurements.response_size
)
end

def handle_event([:http_fetch, :request, :exception], measurements, metadata, _config) do
Logger.error(
"Request to #{metadata.url} failed: #{inspect(metadata.error)}",
request_id: metadata.url,
duration: measurements.duration,
error: metadata.error
)
end

def handle_event([:http_fetch, :response, :body_read_start], measurements, _metadata, _config) do
Logger.debug(
"Response body reading started. Content-Length: #{measurements.content_length}",
content_length: measurements.content_length
)
end

def handle_event([:http_fetch, :response, :body_read_stop], measurements, _metadata, _config) do
Logger.debug(
"Response body reading stopped. Bytes read: #{measurements.bytes_read} in #{measurements.duration}µs",
bytes_read: measurements.bytes_read,
duration: measurements.duration
)
end

def handle_event([:http_fetch, :streaming, :start], measurements, _metadata, _config) do
Logger.debug(
"Streaming started. Content-Length: #{measurements.content_length}",
content_length: measurements.content_length
)
end

def handle_event([:http_fetch, :streaming, :chunk], measurements, _metadata, _config) do
Logger.debug(
"Streaming chunk received. Bytes: #{measurements.bytes_received}/#{measurements.total_bytes}",
bytes_received: measurements.bytes_received,
total_bytes: measurements.total_bytes
)
end

def handle_event([:http_fetch, :streaming, :stop], measurements, _metadata, _config) do
Logger.debug(
"Streaming stopped. Total bytes: #{measurements.total_bytes} in #{measurements.duration}µs",
total_bytes: measurements.total_bytes,
duration: measurements.duration
)
end
end
42 changes: 42 additions & 0 deletions test/http/logger_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
defmodule HTTP.LoggerTest do
use ExUnit.Case, async: true

import ExUnit.CaptureLog

alias HTTP.Logger
alias HTTP.Telemetry

setup do
# It's already attached by the application, but we can re-attach for the test
# to be sure. This is safe to do.
Logger.attach()
:ok
end

test "logs request start event" do
log =
capture_log(fn ->
Telemetry.request_start("GET", URI.parse("http://example.com"), %{})
end)

assert log =~ "GET http://example.com - Request started"
end

test "logs request stop event" do
log =
capture_log(fn ->
Telemetry.request_stop(200, URI.parse("http://example.com"), 1024, 123)
end)

assert log =~ "200 http://example.com - Request completed in 123µs"
end

test "logs request exception event" do
log =
capture_log(fn ->
Telemetry.request_exception(URI.parse("http://example.com"), :timeout, 456)
end)

assert log =~ "Request to http://example.com failed: :timeout"
end
end
3 changes: 0 additions & 3 deletions test/http/telemetry_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@ defmodule HTTP.TelemetryTest do
doctest HTTP.Telemetry

setup do
# Capture telemetry events
events = []

# Start telemetry event capture
:telemetry.attach_many(
"test_handler",
Expand Down
Loading