1
0
mirror of https://github.com/amir20/dozzle.git synced 2025-12-27 23:46:39 +01:00

feat: adds the ability to show a specific log from the past with a permanent link (#3958)

This commit is contained in:
Amir Raminfar
2025-06-11 16:23:48 -07:00
committed by GitHub
parent a8dced5c2b
commit d98000b35a
38 changed files with 669 additions and 232 deletions

View File

@@ -4,6 +4,7 @@ import (
"compress/gzip"
"context"
"errors"
"math"
"regexp"
"sort"
"strconv"
@@ -62,7 +63,6 @@ func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request)
return
}
buffer := utils.NewRingBuffer[*container.LogEvent](500)
delta := max(to.Sub(from), time.Second*3)
var regex *regexp.Regexp
@@ -82,8 +82,9 @@ func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request)
}
minimum := 0
if r.URL.Query().Has("minimum") {
minimum, err = strconv.Atoi(r.URL.Query().Get("minimum"))
buffer := utils.NewRingBuffer[*container.LogEvent](500)
if r.URL.Query().Has("min") {
minimum, err = strconv.Atoi(r.URL.Query().Get("min"))
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
@@ -93,6 +94,21 @@ func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request)
http.Error(w, errors.New("minimum must be between 0 and buffer size").Error(), http.StatusBadRequest)
return
}
buffer = utils.NewRingBuffer[*container.LogEvent](minimum)
}
maxStart := math.MaxInt
if r.URL.Query().Has("maxStart") {
maxStart, err = strconv.Atoi(r.URL.Query().Get("maxStart"))
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if maxStart < 1 || maxStart > buffer.Size {
http.Error(w, errors.New("invalid maxStart").Error(), http.StatusBadRequest)
return
}
}
levels := make(map[string]struct{})
@@ -120,7 +136,7 @@ func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request)
}
for {
if buffer.Len() > minimum {
if minimum > 0 && buffer.Len() >= minimum {
break
}
@@ -157,6 +173,10 @@ func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request)
break
}
if buffer.Len() >= maxStart {
break
}
support_web.EscapeHTMLValues(event)
buffer.Push(event)
}
@@ -166,13 +186,19 @@ func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request)
break
}
if minimum == 0 {
break
}
from = from.Add(-delta)
delta = delta * 2
}
log.Debug().Int("buffer_size", buffer.Len()).Msg("sending logs to client")
for _, event := range buffer.Data() {
data := buffer.Data()
for _, event := range data {
if err := encoder.Encode(event); err != nil {
log.Error().Err(err).Msg("error encoding log event")
return

View File

@@ -268,6 +268,7 @@ func Test_handler_between_dates_with_fill(t *testing.T) {
q.Add("stderr", "true")
q.Add("fill", "true")
q.Add("levels", "info")
q.Add("min", "10")
req.URL.RawQuery = q.Encode()
@@ -280,16 +281,22 @@ func Test_handler_between_dates_with_fill(t *testing.T) {
mockedClient.On("ContainerLogsBetweenDates", mock.Anything, id, from, to, container.STDALL).
Return(io.NopCloser(bytes.NewReader([]byte{})), nil).
Once()
mockedClient.On("ContainerLogsBetweenDates", mock.Anything, id, time.Date(2017, time.December, 31, 14, 0, 0, 0, time.UTC), to, container.STDALL).
Return(io.NopCloser(bytes.NewReader(data)), nil).
Once()
mockedClient.On("FindContainer", mock.Anything, id).Return(container.Container{ID: id}, nil)
mockedClient.On("Host").Return(container.Host{
ID: "localhost",
})
mockedClient.On("ContainerLogsBetweenDates", mock.Anything, id, time.Date(2017, time.December, 30, 18, 0, 0, 0, time.UTC), to, container.STDALL).
Return(io.NopCloser(bytes.NewReader(data)), nil).
Once()
mockedClient.On("FindContainer", mock.Anything, id).Return(container.Container{ID: id, Created: time.Date(2017, time.December, 31, 10, 0, 0, 0, time.UTC)}, nil)
mockedClient.On("Host").Return(container.Host{ID: "localhost"})
mockedClient.On("ListContainers", mock.Anything, mock.Anything).Return([]container.Container{
{ID: id, Name: "test", Host: "localhost", State: "running"},
}, nil)
mockedClient.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- container.ContainerEvent")).Return(nil)
handler := createDefaultHandler(mockedClient)