diff --git a/collector/body.go b/collector/body.go
index 5c41230..af946ba 100644
--- a/collector/body.go
+++ b/collector/body.go
@@ -34,6 +34,20 @@ func NewBody(rc io.ReadCloser, limit int) *Body {
reader: rc,
buffer: NewLimitedBuffer(limit),
}
+ if rc == nil {
+ b.isFullyCaptured = true
+ }
+ return b
+}
+
+// NewBodyFromBytes creates a Body pre-populated with the given content, for
+// callers that already hold the complete payload.
+func NewBodyFromBytes(content []byte, limit int) *Body {
+ b := &Body{
+ buffer: NewLimitedBuffer(limit),
+ }
+ b.buffer.Write(content)
+ b.isFullyCaptured = !b.buffer.IsTruncated()
return b
}
diff --git a/collector/body_test.go b/collector/body_test.go
index 5dbb5cd..492b113 100644
--- a/collector/body_test.go
+++ b/collector/body_test.go
@@ -44,6 +44,35 @@ func TestBody_PartialRead(t *testing.T) {
assert.True(t, body.IsFullyCaptured())
}
+func TestNewBodyFromBytes(t *testing.T) {
+ content := []byte("This is test data for a synthetic body")
+
+ body := collector.NewBodyFromBytes(content, 100)
+
+ assert.Equal(t, content, body.Bytes())
+ assert.Equal(t, string(content), body.String())
+ assert.Equal(t, uint64(len(content)), body.Size())
+ assert.False(t, body.IsTruncated())
+ assert.True(t, body.IsFullyCaptured())
+}
+
+func TestNewBodyFromBytes_Truncated(t *testing.T) {
+ content := []byte("This is test data that exceeds the configured limit")
+
+ body := collector.NewBodyFromBytes(content, 10)
+
+ assert.Equal(t, content[:10], body.Bytes())
+ assert.Equal(t, uint64(10), body.Size())
+ assert.True(t, body.IsTruncated())
+ assert.False(t, body.IsFullyCaptured())
+}
+
+func TestNewBody_NilReaderIsFullyCaptured(t *testing.T) {
+ body := collector.NewBody(nil, 100)
+
+ assert.True(t, body.IsFullyCaptured())
+}
+
// Fix for TestBody_ReadAfterClose
func TestBody_ReadAfterClose(t *testing.T) {
// Create test data
diff --git a/collector/http_client.go b/collector/http_client.go
index 1306670..51a5db2 100644
--- a/collector/http_client.go
+++ b/collector/http_client.go
@@ -5,6 +5,8 @@ import (
"net/http"
"strconv"
"time"
+
+ "github.com/gofrs/uuid"
)
// HTTPClientOptions configures the HTTP client collector
@@ -82,11 +84,49 @@ func (c *HTTPClientCollector) Subscribe(ctx context.Context) <-chan HTTPClientRe
return c.notifier.Subscribe(ctx)
}
-// Add adds an HTTP request to the collector and notifies subscribers
+// Add notifies subscribers of an HTTP request. It does not apply transformers
+// or involve the event aggregator. For a synthetic request not performed through
+// Transport, use Collect instead.
func (c *HTTPClientCollector) Add(req HTTPClientRequest) {
c.notifier.Notify(req)
}
+// Collect records an HTTP client request that was not performed through the
+// transport returned by Transport, e.g. a response served from a local cache.
+//
+// In contrast to Add, it honours capture sessions and groups the request under
+// the current event group taken from ctx.
+func (c *HTTPClientCollector) Collect(ctx context.Context, req HTTPClientRequest) {
+ if c.eventAggregator != nil && !c.eventAggregator.ShouldCapture(ctx) {
+ return
+ }
+
+ if req.ID == uuid.Nil {
+ req.ID = generateID()
+ }
+ if req.RequestTime.IsZero() {
+ req.RequestTime = time.Now()
+ }
+ if req.ResponseTime.IsZero() {
+ req.ResponseTime = req.RequestTime
+ }
+
+ for _, transformer := range c.options.Transformers {
+ req = transformer(req)
+ }
+
+ c.notifier.Notify(req)
+ if c.eventAggregator != nil {
+ c.eventAggregator.CollectEvent(ctx, req)
+ }
+}
+
+// MaxBodySize returns the configured maximum body size for this collector,
+// for callers building a *Body via NewBodyFromBytes for use with Collect.
+func (c *HTTPClientCollector) MaxBodySize() int {
+ return c.options.MaxBodySize
+}
+
// Close releases resources used by the collector
func (c *HTTPClientCollector) Close() {
c.notifier.Close()
diff --git a/collector/http_client_test.go b/collector/http_client_test.go
index 8564aeb..10bd7ec 100644
--- a/collector/http_client_test.go
+++ b/collector/http_client_test.go
@@ -1,13 +1,16 @@
package collector_test
import (
+ "context"
"io"
"net/http"
"net/http/httptest"
"strconv"
"strings"
"testing"
+ "time"
+ "github.com/gofrs/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -70,6 +73,243 @@ func TestHTTPClientCollector_UnreadResponseBody(t *testing.T) {
assert.True(t, req.ResponseBody.IsFullyCaptured())
}
+func TestHTTPClientCollector_Collect_NoCapture_NoEventNoNotify(t *testing.T) {
+ aggregator := collector.NewEventAggregator()
+ defer aggregator.Close()
+
+ options := collector.DefaultHTTPClientOptions()
+ options.EventAggregator = aggregator
+ httpCollector := collector.NewHTTPClientCollectorWithOptions(options)
+
+ collect := Collect(t, httpCollector.Subscribe)
+
+ // No storage registered, so ShouldCapture is false for any context
+ httpCollector.Collect(context.Background(), collector.HTTPClientRequest{
+ Method: http.MethodGet,
+ URL: "https://example.com",
+ })
+
+ // Give the notifier a chance to deliver, if it were going to
+ time.Sleep(20 * time.Millisecond)
+
+ assert.Empty(t, collect.Stop())
+}
+
+func TestHTTPClientCollector_Collect_DispatchesToStorage(t *testing.T) {
+ aggregator := collector.NewEventAggregator()
+ defer aggregator.Close()
+
+ sessionID := uuid.Must(uuid.NewV4())
+ storage := collector.NewCaptureStorage(sessionID, 100, collector.CaptureModeGlobal)
+ aggregator.RegisterStorage(storage)
+
+ options := collector.DefaultHTTPClientOptions()
+ options.EventAggregator = aggregator
+ httpCollector := collector.NewHTTPClientCollectorWithOptions(options)
+
+ httpCollector.Collect(context.Background(), collector.HTTPClientRequest{
+ Method: http.MethodGet,
+ URL: "https://example.com",
+ })
+
+ events := storage.GetEvents(10)
+ require.Len(t, events, 1)
+
+ req, ok := events[0].Data.(collector.HTTPClientRequest)
+ require.True(t, ok)
+ assert.Equal(t, "https://example.com", req.URL)
+}
+
+func TestHTTPClientCollector_Collect_GroupsUnderParentEvent(t *testing.T) {
+ aggregator := collector.NewEventAggregator()
+ defer aggregator.Close()
+
+ sessionID := uuid.Must(uuid.NewV4())
+ storage := collector.NewCaptureStorage(sessionID, 100, collector.CaptureModeGlobal)
+ aggregator.RegisterStorage(storage)
+
+ options := collector.DefaultHTTPClientOptions()
+ options.EventAggregator = aggregator
+ httpCollector := collector.NewHTTPClientCollectorWithOptions(options)
+
+ parentCtx := aggregator.StartEvent(context.Background())
+
+ httpCollector.Collect(parentCtx, collector.HTTPClientRequest{
+ Method: http.MethodGet,
+ URL: "https://example.com/child",
+ })
+
+ aggregator.EndEvent(parentCtx, "parent event")
+
+ events := storage.GetEvents(10)
+ require.Len(t, events, 1, "the child request must not be dispatched as a top-level event")
+
+ parent := events[0]
+ require.Len(t, parent.Children, 1)
+
+ req, ok := parent.Children[0].Data.(collector.HTTPClientRequest)
+ require.True(t, ok)
+ assert.Equal(t, "https://example.com/child", req.URL)
+}
+
+func TestHTTPClientCollector_Collect_NotifiesSubscribers(t *testing.T) {
+ aggregator := collector.NewEventAggregator()
+ defer aggregator.Close()
+
+ sessionID := uuid.Must(uuid.NewV4())
+ storage := collector.NewCaptureStorage(sessionID, 100, collector.CaptureModeGlobal)
+ aggregator.RegisterStorage(storage)
+
+ options := collector.DefaultHTTPClientOptions()
+ options.EventAggregator = aggregator
+ httpCollector := collector.NewHTTPClientCollectorWithOptions(options)
+
+ collect := Collect(t, httpCollector.Subscribe)
+
+ httpCollector.Collect(context.Background(), collector.HTTPClientRequest{
+ Method: http.MethodGet,
+ URL: "https://example.com",
+ })
+
+ received := collect.Wait(1)
+ require.Len(t, received, 1)
+ assert.Equal(t, "https://example.com", received[0].URL)
+}
+
+func TestHTTPClientCollector_Collect_AppliesTransformers(t *testing.T) {
+ aggregator := collector.NewEventAggregator()
+ defer aggregator.Close()
+
+ sessionID := uuid.Must(uuid.NewV4())
+ storage := collector.NewCaptureStorage(sessionID, 100, collector.CaptureModeGlobal)
+ aggregator.RegisterStorage(storage)
+
+ options := collector.DefaultHTTPClientOptions()
+ options.EventAggregator = aggregator
+ options.Transformers = []collector.HTTPClientRequestTransformer{
+ func(req collector.HTTPClientRequest) collector.HTTPClientRequest {
+ if req.Tags == nil {
+ req.Tags = map[string]string{}
+ }
+ req.Tags["transformed"] = "true"
+ return req
+ },
+ }
+ httpCollector := collector.NewHTTPClientCollectorWithOptions(options)
+
+ httpCollector.Collect(context.Background(), collector.HTTPClientRequest{
+ Method: http.MethodGet,
+ URL: "https://example.com",
+ })
+
+ events := storage.GetEvents(10)
+ require.Len(t, events, 1)
+
+ req, ok := events[0].Data.(collector.HTTPClientRequest)
+ require.True(t, ok)
+ assert.Equal(t, "true", req.Tags["transformed"])
+}
+
+func TestHTTPClientCollector_Collect_FillsZeroValues(t *testing.T) {
+ aggregator := collector.NewEventAggregator()
+ defer aggregator.Close()
+
+ sessionID := uuid.Must(uuid.NewV4())
+ storage := collector.NewCaptureStorage(sessionID, 100, collector.CaptureModeGlobal)
+ aggregator.RegisterStorage(storage)
+
+ options := collector.DefaultHTTPClientOptions()
+ options.EventAggregator = aggregator
+ httpCollector := collector.NewHTTPClientCollectorWithOptions(options)
+
+ before := time.Now()
+
+ httpCollector.Collect(context.Background(), collector.HTTPClientRequest{
+ Method: http.MethodGet,
+ URL: "https://example.com",
+ })
+
+ events := storage.GetEvents(10)
+ require.Len(t, events, 1)
+
+ req, ok := events[0].Data.(collector.HTTPClientRequest)
+ require.True(t, ok)
+
+ assert.NotEqual(t, uuid.Nil, req.ID)
+ assert.False(t, req.RequestTime.Before(before))
+ assert.Equal(t, req.RequestTime, req.ResponseTime)
+}
+
+func TestHTTPClientCollector_Collect_PreservesNonZeroValues(t *testing.T) {
+ aggregator := collector.NewEventAggregator()
+ defer aggregator.Close()
+
+ sessionID := uuid.Must(uuid.NewV4())
+ storage := collector.NewCaptureStorage(sessionID, 100, collector.CaptureModeGlobal)
+ aggregator.RegisterStorage(storage)
+
+ options := collector.DefaultHTTPClientOptions()
+ options.EventAggregator = aggregator
+ httpCollector := collector.NewHTTPClientCollectorWithOptions(options)
+
+ id := uuid.Must(uuid.NewV7())
+ requestTime := time.Now().Add(-time.Minute)
+ responseTime := time.Now().Add(-30 * time.Second)
+
+ httpCollector.Collect(context.Background(), collector.HTTPClientRequest{
+ ID: id,
+ Method: http.MethodGet,
+ URL: "https://example.com",
+ RequestTime: requestTime,
+ ResponseTime: responseTime,
+ })
+
+ events := storage.GetEvents(10)
+ require.Len(t, events, 1)
+
+ req, ok := events[0].Data.(collector.HTTPClientRequest)
+ require.True(t, ok)
+
+ assert.Equal(t, id, req.ID)
+ assert.True(t, requestTime.Equal(req.RequestTime))
+ assert.True(t, responseTime.Equal(req.ResponseTime))
+}
+
+func TestHTTPClientCollector_Add_NotifiesButNoEvent(t *testing.T) {
+ aggregator := collector.NewEventAggregator()
+ defer aggregator.Close()
+
+ sessionID := uuid.Must(uuid.NewV4())
+ storage := collector.NewCaptureStorage(sessionID, 100, collector.CaptureModeGlobal)
+ aggregator.RegisterStorage(storage)
+
+ options := collector.DefaultHTTPClientOptions()
+ options.EventAggregator = aggregator
+ httpCollector := collector.NewHTTPClientCollectorWithOptions(options)
+
+ collect := Collect(t, httpCollector.Subscribe)
+
+ httpCollector.Add(collector.HTTPClientRequest{
+ Method: http.MethodGet,
+ URL: "https://example.com",
+ })
+
+ received := collect.Wait(1)
+ require.Len(t, received, 1)
+ assert.Equal(t, "https://example.com", received[0].URL)
+
+ // Add never reaches the event aggregator
+ assert.Empty(t, storage.GetEvents(10))
+}
+
+func TestHTTPClientCollector_MaxBodySize(t *testing.T) {
+ options := collector.DefaultHTTPClientOptions()
+ options.MaxBodySize = 4096
+ httpCollector := collector.NewHTTPClientCollectorWithOptions(options)
+
+ assert.Equal(t, 4096, httpCollector.MaxBodySize())
+}
+
// BodyReadTracker tracks if a response body was read
type BodyReadTracker struct {
data string
diff --git a/dashboard/handler_download_test.go b/dashboard/handler_download_test.go
new file mode 100644
index 0000000..dd6c83f
--- /dev/null
+++ b/dashboard/handler_download_test.go
@@ -0,0 +1,109 @@
+package dashboard
+
+import (
+ "context"
+ "io"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/gofrs/uuid"
+
+ "github.com/networkteam/devlog/collector"
+)
+
+func startGlobalCapture(t *testing.T, handler *Handler, sessionID uuid.UUID) {
+ t.Helper()
+
+ req := httptest.NewRequest(http.MethodPost, "/s/"+sessionID.String()+"/capture/start?mode=global", nil)
+ rec := httptest.NewRecorder()
+ handler.ServeHTTP(rec, req)
+
+ if rec.Code != http.StatusOK {
+ t.Fatalf("capture/start: unexpected status %d: %s", rec.Code, rec.Body.String())
+ }
+}
+
+// TestHandler_DownloadBody_SyntheticBody verifies that the download endpoints
+// work for HTTPClientRequest bodies built via collector.NewBodyFromBytes, not
+// just bodies captured lazily from a live http.Response - both request and
+// response body downloads resolve the body from the stored event by ID, so
+// they should be agnostic to how the *Body was constructed.
+func TestHandler_DownloadBody_SyntheticBody(t *testing.T) {
+ aggregator := collector.NewEventAggregator()
+ defer aggregator.Close()
+
+ httpClientCollector := collector.NewHTTPClientCollectorWithOptions(collector.HTTPClientOptions{
+ EventAggregator: aggregator,
+ })
+ defer httpClientCollector.Close()
+
+ handler := NewHandler(aggregator)
+ defer handler.Close()
+
+ sessionID := uuid.Must(uuid.NewV4())
+ startGlobalCapture(t, handler, sessionID)
+
+ requestBody := []byte(`{"request":"payload"}`)
+ responseBody := []byte(`{"response":"payload"}`)
+
+ httpClientCollector.Collect(context.Background(), collector.HTTPClientRequest{
+ Method: http.MethodPost,
+ URL: "https://example.com/v2/items",
+ StatusCode: http.StatusOK,
+ RequestBody: collector.NewBodyFromBytes(requestBody, collector.DefaultMaxBodySize),
+ RequestHeaders: http.Header{"Content-Type": {"application/json"}},
+ ResponseBody: collector.NewBodyFromBytes(responseBody, collector.DefaultMaxBodySize),
+ ResponseHeaders: http.Header{"Content-Type": {"application/json"}},
+ })
+
+ storage := handler.sessions.Get(sessionID)
+ if storage == nil {
+ t.Fatal("expected storage to exist after capture start")
+ }
+ events := storage.GetEvents(10)
+ if len(events) != 1 {
+ t.Fatalf("expected 1 stored event, got %d", len(events))
+ }
+ eventID := events[0].ID
+
+ t.Run("request body", func(t *testing.T) {
+ req := httptest.NewRequest(http.MethodGet, "/s/"+sessionID.String()+"/download/request-body/"+eventID.String(), nil)
+ rec := httptest.NewRecorder()
+ handler.ServeHTTP(rec, req)
+
+ if rec.Code != http.StatusOK {
+ t.Fatalf("unexpected status %d: %s", rec.Code, rec.Body.String())
+ }
+ got, err := io.ReadAll(rec.Body)
+ if err != nil {
+ t.Fatalf("read body: %v", err)
+ }
+ if string(got) != string(requestBody) {
+ t.Errorf("expected body %q, got %q", requestBody, got)
+ }
+ if ct := rec.Header().Get("Content-Type"); ct != "application/json" {
+ t.Errorf("expected Content-Type application/json, got %q", ct)
+ }
+ })
+
+ t.Run("response body", func(t *testing.T) {
+ req := httptest.NewRequest(http.MethodGet, "/s/"+sessionID.String()+"/download/response-body/"+eventID.String(), nil)
+ rec := httptest.NewRecorder()
+ handler.ServeHTTP(rec, req)
+
+ if rec.Code != http.StatusOK {
+ t.Fatalf("unexpected status %d: %s", rec.Code, rec.Body.String())
+ }
+ got, err := io.ReadAll(rec.Body)
+ if err != nil {
+ t.Fatalf("read body: %v", err)
+ }
+ if string(got) != string(responseBody) {
+ t.Errorf("expected body %q, got %q", responseBody, got)
+ }
+ if ct := rec.Header().Get("Content-Type"); ct != "application/json" {
+ t.Errorf("expected Content-Type application/json, got %q", ct)
+ }
+ })
+}
diff --git a/dashboard/views/event-details.templ b/dashboard/views/event-details.templ
index d839c5e..02086c6 100644
--- a/dashboard/views/event-details.templ
+++ b/dashboard/views/event-details.templ
@@ -124,6 +124,9 @@ templ HTTPRequestDetails(event *collector.Event, request collector.HTTPClientReq
{ formatTime(request.RequestTime) }
+ if len(request.Tags) > 0 {
+ @tagList(request.Tags)
+ }
diff --git a/dashboard/views/event-details_templ.go b/dashboard/views/event-details_templ.go
index b0569ba..9ab6432 100644
--- a/dashboard/views/event-details_templ.go
+++ b/dashboard/views/event-details_templ.go
@@ -406,61 +406,71 @@ func HTTPRequestDetails(event *collector.Event, request collector.HTTPClientRequ
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "
URL
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if len(request.Tags) > 0 {
+ templ_7745c5c3_Err = tagList(request.Tags).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "URL
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var19 string
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(request.URL)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 133, Col: 29}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 136, Col: 29}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "
Request
Headers
| Name | Value |
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "Request
Headers
| Name | Value |
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for key, values := range request.RequestHeaders {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "| ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, " |
| ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var20 string
templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(key)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 155, Col: 77}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 158, Col: 77}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, " | ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, " | ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var21 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(strings.Join(values, ", "))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 156, Col: 100}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 159, Col: 100}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, " |
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if request.RequestBody != nil && request.RequestBody.Size() > 0 {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -477,63 +487,63 @@ func HTTPRequestDetails(event *collector.Event, request collector.HTTPClientRequ
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "Response
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "Response
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if len(request.ResponseHeaders) > 0 {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "
Headers
| Name | Value |
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "Headers
| Name | Value |
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for key, values := range request.ResponseHeaders {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "| ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, " |
| ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var23 string
templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(key)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 202, Col: 81}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 205, Col: 81}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, " | ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, " | ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var24 string
templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(strings.Join(values, ", "))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 203, Col: 104}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 206, Col: 104}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, " |
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if request.ResponseBody != nil && request.ResponseBody.Size() > 0 {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -550,12 +560,12 @@ func HTTPRequestDetails(event *collector.Event, request collector.HTTPClientRequ
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -586,7 +596,7 @@ func HTTPServerRequestDetails(event *collector.Event, request collector.HTTPServ
}
ctx = templ.ClearChildren(ctx)
duration := request.Duration()
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -597,7 +607,7 @@ func HTTPServerRequestDetails(event *collector.Event, request collector.HTTPServ
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var29 string
templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(request.Method)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 244, Col: 36}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 247, Col: 36}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var30 string
templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(request.Path)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 246, Col: 73}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 249, Col: 73}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "
Status: ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, " Status: ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -645,7 +655,7 @@ func HTTPServerRequestDetails(event *collector.Event, request collector.HTTPServ
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var33 string
templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(request.StatusCode))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 254, Col: 62}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 257, Col: 62}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, " ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var34 string
templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(formatDuration(duration))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 260, Col: 52}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 263, Col: 52}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, " ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var35 string
templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(formatTime(request.RequestTime))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 267, Col: 59}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 270, Col: 59}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "
From: ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, " From: ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var36 string
templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(request.RemoteAddr)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 271, Col: 52}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 274, Col: 52}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, " URL
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "
URL
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var37 string
templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(request.URL)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 280, Col: 29}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 283, Col: 29}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "
Request
Headers
| Name | Value |
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "Request
Headers
| Name | Value |
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for key, values := range request.RequestHeaders {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "| ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, " |
| ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var38 string
templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(key)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 302, Col: 77}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 305, Col: 77}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, " | ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, " | ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var39 string
templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.JoinStringErrs(strings.Join(values, ", "))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 303, Col: 100}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 306, Col: 100}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, " |
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if request.RequestBody != nil && request.RequestBody.Size() > 0 {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -781,63 +791,63 @@ func HTTPServerRequestDetails(event *collector.Event, request collector.HTTPServ
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "
Response
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "Response
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if len(request.ResponseHeaders) > 0 {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "
Headers
| Name | Value |
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "Headers
| Name | Value |
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for key, values := range request.ResponseHeaders {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "| ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, " |
| ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var41 string
templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.JoinStringErrs(key)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 349, Col: 81}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 352, Col: 81}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var41))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, " | ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, " | ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var42 string
templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.JoinStringErrs(strings.Join(values, ", "))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 350, Col: 104}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 353, Col: 104}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var42))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, " |
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if request.ResponseBody != nil && request.ResponseBody.Size() > 0 {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -854,12 +864,12 @@ func HTTPServerRequestDetails(event *collector.Event, request collector.HTTPServ
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -889,7 +899,7 @@ func LogRecordDetails(event *collector.Event, record slog.Record) templ.Componen
templ_7745c5c3_Var44 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -900,7 +910,7 @@ func LogRecordDetails(event *collector.Event, record slog.Record) templ.Componen
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var47 string
templ_7745c5c3_Var47, templ_7745c5c3_Err = templ.JoinStringErrs(record.Level)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 390, Col: 34}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 393, Col: 34}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var47))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var48 string
templ_7745c5c3_Var48, templ_7745c5c3_Err = templ.JoinStringErrs(record.Message)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 392, Col: 75}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 395, Col: 75}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var48))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var49 string
templ_7745c5c3_Var49, templ_7745c5c3_Err = templ.JoinStringErrs(formatTime(record.Time))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 397, Col: 51}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 400, Col: 51}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var49))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, " Attributes
| Key | Value |
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, "Attributes
| Key | Value |
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for attr := range iterSlogAttrs(record) {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, "| ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, " |
| ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var50 string
templ_7745c5c3_Var50, templ_7745c5c3_Err = templ.JoinStringErrs(attr.Key)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 416, Col: 78}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 419, Col: 78}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var50))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, " | ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, " | ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var51 string
templ_7745c5c3_Var51, templ_7745c5c3_Err = templ.JoinStringErrs(attr.Value.String())
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 417, Col: 89}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 420, Col: 89}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var51))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, " |
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, "
Context
Log recorded at ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 83, "
Context
Log recorded at ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var52 string
templ_7745c5c3_Var52, templ_7745c5c3_Err = templ.JoinStringErrs(formatTime(record.Time))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 429, Col: 60}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 432, Col: 60}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var52))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 83, ".
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, ".")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if event.Start != event.End {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "
Duration: ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "
Duration: ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var53 string
templ_7745c5c3_Var53, templ_7745c5c3_Err = templ.JoinStringErrs(formatDuration(event.End.Sub(event.Start)))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 431, Col: 90}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 434, Col: 90}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var53))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 86, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 86, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -1054,150 +1064,150 @@ func DBQueryDetails(event *collector.Event, query collector.DBQuery) templ.Compo
templ_7745c5c3_Var54 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, "Database Query
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, "Database Query
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var55 string
templ_7745c5c3_Var55, templ_7745c5c3_Err = templ.JoinStringErrs(query.Query)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 444, Col: 86}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 447, Col: 86}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var55))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 89, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if len(query.Args) > 0 {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 89, "
Arguments
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 90, "Arguments
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, arg := range query.Args {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 90, "- ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 91, "
- ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var56 string
templ_7745c5c3_Var56, templ_7745c5c3_Err = templ.JoinStringErrs(arg.Ordinal)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 453, Col: 62}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 456, Col: 62}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var56))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 91, "
- ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 92, "
- ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var57 string
templ_7745c5c3_Var57, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(arg.Value))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 454, Col: 65}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 457, Col: 65}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var57))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 92, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 93, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 93, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, "
Details
- Duration
- ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 95, "
Details
- Duration
- ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var58 string
templ_7745c5c3_Var58, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.2fms", float64(query.Duration.Microseconds())/1000))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 465, Col: 88}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 468, Col: 88}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var58))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 95, "
- Timestamp
- ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "
- Timestamp
- ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var59 string
templ_7745c5c3_Var59, templ_7745c5c3_Err = templ.JoinStringErrs(query.Timestamp.Format("2006-01-02 15:04:05.000"))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 468, Col: 71}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 471, Col: 71}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var59))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 97, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if query.Language != "" {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 97, "- Language
- ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 98, "
- Language
- ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var60 string
templ_7745c5c3_Var60, templ_7745c5c3_Err = templ.JoinStringErrs(query.Language)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 472, Col: 40}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 475, Col: 40}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var60))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 98, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 99, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 99, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 100, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if query.Error != nil {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 100, "
Error
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 101, "Error
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var61 string
templ_7745c5c3_Var61, templ_7745c5c3_Err = templ.JoinStringErrs(query.Error.Error())
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 481, Col: 84}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `dashboard/views/event-details.templ`, Line: 484, Col: 84}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var61))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 101, " ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 102, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 102, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 104, " || \"sql\",\n });\n queryContent.textContent = output;\n })();\n ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
diff --git a/dashboard/views/format_test.go b/dashboard/views/format_test.go
new file mode 100644
index 0000000..c4e84b6
--- /dev/null
+++ b/dashboard/views/format_test.go
@@ -0,0 +1,30 @@
+package views
+
+import (
+ "testing"
+ "time"
+)
+
+func TestFormatDuration(t *testing.T) {
+ tests := []struct {
+ name string
+ d time.Duration
+ want string
+ }{
+ {"zero", 0, "0μs"},
+ {"sub-millisecond", 500 * time.Microsecond, "500μs"},
+ {"just under a millisecond", 999 * time.Microsecond, "999μs"},
+ {"exactly one millisecond", time.Millisecond, "1ms"},
+ {"just under a second", 999 * time.Millisecond, "999ms"},
+ {"exactly one second", time.Second, "1.00s"},
+ {"multiple seconds", 2500 * time.Millisecond, "2.50s"},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := formatDuration(tt.d); got != tt.want {
+ t.Errorf("formatDuration(%v) = %q, want %q", tt.d, got, tt.want)
+ }
+ })
+ }
+}
diff --git a/devlog.go b/devlog.go
index bb7ec27..c5554a6 100644
--- a/devlog.go
+++ b/devlog.go
@@ -118,6 +118,13 @@ func (i *Instance) CollectDBQuery() func(ctx context.Context, dbQuery collector.
return i.dbQueryCollector.Collect
}
+// CollectHTTPClientRequest allows to record outgoing HTTP requests that were not
+// performed through the transport returned by CollectHTTPClient, e.g. responses
+// served from a cache.
+func (i *Instance) CollectHTTPClientRequest() func(ctx context.Context, req collector.HTTPClientRequest) {
+ return i.httpClientCollector.Collect
+}
+
// DashboardHandler creates a dashboard handler mounted at the given path prefix.
// Use functional options from the dashboard package to customize behavior:
//