1
0
mirror of https://github.com/amir20/dozzle.git synced 2025-12-21 21:33:18 +01:00

chore: logs swarm mode to beacon (#2918)

This commit is contained in:
Amir Raminfar
2024-04-26 14:30:32 -07:00
committed by GitHub
parent 7fb4dcce77
commit d7079c36e4
8 changed files with 60 additions and 127 deletions

View File

@@ -1,73 +0,0 @@
package analytics
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/http/httputil"
log "github.com/sirupsen/logrus"
)
func SendStartEvent(se StartEvent) error {
postBody := map[string]interface{}{
"client_id": se.ClientId,
"events": []map[string]interface{}{
{
"name": "start",
"params": se,
},
},
}
return doRequest(postBody)
}
func SendRequestEvent(re RequestEvent) error {
postBody := map[string]interface{}{
"client_id": re.ClientId,
"events": []map[string]interface{}{
{
"name": "request",
"params": re,
},
},
}
return doRequest(postBody)
}
func doRequest(body map[string]interface{}) error {
jsonValue, err := json.Marshal(body)
if err != nil {
return err
}
req, err := http.NewRequest("POST", "https://www.google-analytics.com/mp/collect", bytes.NewBuffer(jsonValue))
if err != nil {
return err
}
q := req.URL.Query()
q.Add("measurement_id", "G-S6NT05VXK9")
q.Add("api_secret", "7FFhe65HQK-bXvujpQMquQ")
req.URL.RawQuery = q.Encode()
response, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer response.Body.Close()
if response.StatusCode/100 != 2 {
dump, err := httputil.DumpResponse(response, true)
if err != nil {
return err
}
log.Debugf("%v", string(dump))
return fmt.Errorf("google analytics returned non-2xx status code: %v", response.Status)
}
return nil
}

View File

@@ -11,6 +11,7 @@ import (
) )
func SendBeacon(e BeaconEvent) error { func SendBeacon(e BeaconEvent) error {
log.Tracef("sending beacon: %+v", e)
jsonValue, err := json.Marshal(e) jsonValue, err := json.Marshal(e)
if err != nil { if err != nil {
return err return err

View File

@@ -1,22 +1,5 @@
package analytics package analytics
type StartEvent struct {
ClientId string `json:"-"`
Version string `json:"version"`
FilterLength int `json:"filterLength"`
RemoteHostLength int `json:"remoteHostLength"`
CustomAddress bool `json:"customAddress"`
CustomBase bool `json:"customBase"`
Protected bool `json:"protected"`
HasHostname bool `json:"hasHostname"`
}
type RequestEvent struct {
ClientId string `json:"-"`
TotalContainers int `json:"totalContainers"`
RunningContainers int `json:"runningContainers"`
}
type BeaconEvent struct { type BeaconEvent struct {
Name string `json:"name"` Name string `json:"name"`
Version string `json:"version"` Version string `json:"version"`
@@ -24,10 +7,10 @@ type BeaconEvent struct {
AuthProvider string `json:"authProvider"` AuthProvider string `json:"authProvider"`
FilterLength int `json:"filterLength"` FilterLength int `json:"filterLength"`
Clients int `json:"clients"` Clients int `json:"clients"`
HasDocumentation bool `json:"hasDocumentation"`
HasCustomAddress bool `json:"hasCustomAddress"` HasCustomAddress bool `json:"hasCustomAddress"`
HasCustomBase bool `json:"hasCustomBase"` HasCustomBase bool `json:"hasCustomBase"`
HasHostname bool `json:"hasHostname"` HasHostname bool `json:"hasHostname"`
RunningContainers int `json:"runningContainers"` RunningContainers int `json:"runningContainers"`
HasActions bool `json:"hasActions"` HasActions bool `json:"hasActions"`
IsSwarmMode bool `json:"isSwarmMode"`
} }

View File

@@ -18,6 +18,7 @@ import (
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/events" "github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client" "github.com/docker/docker/client"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@@ -67,16 +68,18 @@ type Client interface {
Ping(context.Context) (types.Ping, error) Ping(context.Context) (types.Ping, error)
Host() *Host Host() *Host
ContainerActions(action string, containerID string) error ContainerActions(action string, containerID string) error
IsSwarmMode() bool
} }
type _client struct { type httpClient struct {
cli DockerCLI cli DockerCLI
filters filters.Args filters filters.Args
host *Host host *Host
SwarmMode bool
} }
func NewClient(cli DockerCLI, filters filters.Args, host *Host) Client { func NewClient(cli DockerCLI, filters filters.Args, host *Host, swarm bool) Client {
return &_client{cli, filters, host} return &httpClient{cli, filters, host, swarm}
} }
// NewClientWithFilters creates a new instance of Client with docker filters // NewClientWithFilters creates a new instance of Client with docker filters
@@ -96,7 +99,10 @@ func NewClientWithFilters(f map[string][]string) (Client, error) {
return nil, err return nil, err
} }
return NewClient(cli, filterArgs, &Host{Name: "localhost", ID: "localhost"}), nil info, _ := cli.Info(context.Background())
swarm := info.Swarm.LocalNodeState != swarm.LocalNodeStateInactive
return NewClient(cli, filterArgs, &Host{Name: "localhost", ID: "localhost"}, swarm), nil
} }
func NewClientWithTlsAndFilter(f map[string][]string, host Host) (Client, error) { func NewClientWithTlsAndFilter(f map[string][]string, host Host) (Client, error) {
@@ -132,10 +138,13 @@ func NewClientWithTlsAndFilter(f map[string][]string, host Host) (Client, error)
return nil, err return nil, err
} }
return NewClient(cli, filterArgs, &host), nil info, _ := cli.Info(context.Background())
swarm := info.Swarm.LocalNodeState != swarm.LocalNodeStateInactive
return NewClient(cli, filterArgs, &host, swarm), nil
} }
func (d *_client) FindContainer(id string) (Container, error) { func (d *httpClient) FindContainer(id string) (Container, error) {
var container Container var container Container
containers, err := d.ListContainers() containers, err := d.ListContainers()
if err != nil { if err != nil {
@@ -163,7 +172,7 @@ func (d *_client) FindContainer(id string) (Container, error) {
return container, nil return container, nil
} }
func (d *_client) ContainerActions(action string, containerID string) error { func (d *httpClient) ContainerActions(action string, containerID string) error {
switch action { switch action {
case "start": case "start":
return d.cli.ContainerStart(context.Background(), containerID, container.StartOptions{}) return d.cli.ContainerStart(context.Background(), containerID, container.StartOptions{})
@@ -176,7 +185,7 @@ func (d *_client) ContainerActions(action string, containerID string) error {
} }
} }
func (d *_client) ListContainers() ([]Container, error) { func (d *httpClient) ListContainers() ([]Container, error) {
containerListOptions := container.ListOptions{ containerListOptions := container.ListOptions{
Filters: d.filters, Filters: d.filters,
All: true, All: true,
@@ -217,7 +226,7 @@ func (d *_client) ListContainers() ([]Container, error) {
return containers, nil return containers, nil
} }
func (d *_client) ContainerStats(ctx context.Context, id string, stats chan<- ContainerStat) error { func (d *httpClient) ContainerStats(ctx context.Context, id string, stats chan<- ContainerStat) error {
response, err := d.cli.ContainerStats(ctx, id, true) response, err := d.cli.ContainerStats(ctx, id, true)
if err != nil { if err != nil {
@@ -252,8 +261,6 @@ func (d *_client) ContainerStats(ctx context.Context, id string, stats chan<- Co
mem = float64(v.MemoryStats.PrivateWorkingSet) mem = float64(v.MemoryStats.PrivateWorkingSet)
} }
log.Tracef("containerId = %s, cpuPercent = %f, memPercent = %f, memUsage = %f, daemonOSType = %s", id, cpuPercent, memPercent, mem, daemonOSType)
if cpuPercent > 0 || mem > 0 { if cpuPercent > 0 || mem > 0 {
select { select {
case <-ctx.Done(): case <-ctx.Done():
@@ -269,7 +276,7 @@ func (d *_client) ContainerStats(ctx context.Context, id string, stats chan<- Co
} }
} }
func (d *_client) ContainerLogs(ctx context.Context, id string, since string, stdType StdType) (io.ReadCloser, error) { func (d *httpClient) ContainerLogs(ctx context.Context, id string, since string, stdType StdType) (io.ReadCloser, error) {
log.WithField("id", id).WithField("since", since).WithField("stdType", stdType).Debug("streaming logs for container") log.WithField("id", id).WithField("since", since).WithField("stdType", stdType).Debug("streaming logs for container")
if since != "" { if since != "" {
@@ -297,7 +304,7 @@ func (d *_client) ContainerLogs(ctx context.Context, id string, since string, st
return reader, nil return reader, nil
} }
func (d *_client) Events(ctx context.Context, messages chan<- ContainerEvent) error { func (d *httpClient) Events(ctx context.Context, messages chan<- ContainerEvent) error {
dockerMessages, err := d.cli.Events(ctx, types.EventsOptions{}) dockerMessages, err := d.cli.Events(ctx, types.EventsOptions{})
for { for {
@@ -320,7 +327,7 @@ func (d *_client) Events(ctx context.Context, messages chan<- ContainerEvent) er
} }
func (d *_client) ContainerLogsBetweenDates(ctx context.Context, id string, from time.Time, to time.Time, stdType StdType) (io.ReadCloser, error) { func (d *httpClient) ContainerLogsBetweenDates(ctx context.Context, id string, from time.Time, to time.Time, stdType StdType) (io.ReadCloser, error) {
options := container.LogsOptions{ options := container.LogsOptions{
ShowStdout: stdType&STDOUT != 0, ShowStdout: stdType&STDOUT != 0,
ShowStderr: stdType&STDERR != 0, ShowStderr: stdType&STDERR != 0,
@@ -339,14 +346,18 @@ func (d *_client) ContainerLogsBetweenDates(ctx context.Context, id string, from
return reader, nil return reader, nil
} }
func (d *_client) Ping(ctx context.Context) (types.Ping, error) { func (d *httpClient) Ping(ctx context.Context) (types.Ping, error) {
return d.cli.Ping(ctx) return d.cli.Ping(ctx)
} }
func (d *_client) Host() *Host { func (d *httpClient) Host() *Host {
return d.host return d.host
} }
func (d *httpClient) IsSwarmMode() bool {
return d.SwarmMode
}
var PARENTHESIS_RE = regexp.MustCompile(`\(([a-zA-Z]+)\)`) var PARENTHESIS_RE = regexp.MustCompile(`\(([a-zA-Z]+)\)`)
func findBetweenParentheses(s string) string { func findBetweenParentheses(s string) string {

View File

@@ -89,7 +89,7 @@ func (m *mockedProxy) ContainerRestart(ctx context.Context, containerID string,
func Test_dockerClient_ListContainers_null(t *testing.T) { func Test_dockerClient_ListContainers_null(t *testing.T) {
proxy := new(mockedProxy) proxy := new(mockedProxy)
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, nil) proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, nil)
client := &_client{proxy, filters.NewArgs(), &Host{ID: "localhost"}} client := &httpClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}, false}
list, err := client.ListContainers() list, err := client.ListContainers()
assert.Empty(t, list, "list should be empty") assert.Empty(t, list, "list should be empty")
@@ -101,7 +101,7 @@ func Test_dockerClient_ListContainers_null(t *testing.T) {
func Test_dockerClient_ListContainers_error(t *testing.T) { func Test_dockerClient_ListContainers_error(t *testing.T) {
proxy := new(mockedProxy) proxy := new(mockedProxy)
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, errors.New("test")) proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, errors.New("test"))
client := &_client{proxy, filters.NewArgs(), &Host{ID: "localhost"}} client := &httpClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}, false}
list, err := client.ListContainers() list, err := client.ListContainers()
assert.Nil(t, list, "list should be nil") assert.Nil(t, list, "list should be nil")
@@ -124,7 +124,7 @@ func Test_dockerClient_ListContainers_happy(t *testing.T) {
proxy := new(mockedProxy) proxy := new(mockedProxy)
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil) proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil)
client := &_client{proxy, filters.NewArgs(), &Host{ID: "localhost"}} client := &httpClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}, false}
list, err := client.ListContainers() list, err := client.ListContainers()
require.NoError(t, err, "error should not return an error.") require.NoError(t, err, "error should not return an error.")
@@ -151,7 +151,7 @@ func Test_dockerClient_ContainerLogs_happy(t *testing.T) {
options := container.LogsOptions{ShowStdout: true, ShowStderr: true, Follow: true, Tail: "300", Timestamps: true, Since: "since"} options := container.LogsOptions{ShowStdout: true, ShowStderr: true, Follow: true, Tail: "300", Timestamps: true, Since: "since"}
proxy.On("ContainerLogs", mock.Anything, id, options).Return(reader, nil) proxy.On("ContainerLogs", mock.Anything, id, options).Return(reader, nil)
client := &_client{proxy, filters.NewArgs(), &Host{ID: "localhost"}} client := &httpClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}, false}
logReader, _ := client.ContainerLogs(context.Background(), id, "since", STDALL) logReader, _ := client.ContainerLogs(context.Background(), id, "since", STDALL)
actual, _ := io.ReadAll(logReader) actual, _ := io.ReadAll(logReader)
@@ -165,7 +165,7 @@ func Test_dockerClient_ContainerLogs_error(t *testing.T) {
proxy.On("ContainerLogs", mock.Anything, id, mock.Anything).Return(nil, errors.New("test")) proxy.On("ContainerLogs", mock.Anything, id, mock.Anything).Return(nil, errors.New("test"))
client := &_client{proxy, filters.NewArgs(), &Host{ID: "localhost"}} client := &httpClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}, false}
reader, err := client.ContainerLogs(context.Background(), id, "", STDALL) reader, err := client.ContainerLogs(context.Background(), id, "", STDALL)
@@ -192,7 +192,7 @@ func Test_dockerClient_FindContainer_happy(t *testing.T) {
json := types.ContainerJSON{Config: &container.Config{Tty: false}} json := types.ContainerJSON{Config: &container.Config{Tty: false}}
proxy.On("ContainerInspect", mock.Anything, "abcdefghijkl").Return(json, nil) proxy.On("ContainerInspect", mock.Anything, "abcdefghijkl").Return(json, nil)
client := &_client{proxy, filters.NewArgs(), &Host{ID: "localhost"}} client := &httpClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}, false}
container, err := client.FindContainer("abcdefghijkl") container, err := client.FindContainer("abcdefghijkl")
require.NoError(t, err, "error should not be thrown") require.NoError(t, err, "error should not be thrown")
@@ -215,7 +215,7 @@ func Test_dockerClient_FindContainer_error(t *testing.T) {
proxy := new(mockedProxy) proxy := new(mockedProxy)
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil) proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil)
client := &_client{proxy, filters.NewArgs(), &Host{ID: "localhost"}} client := &httpClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}, false}
_, err := client.FindContainer("not_valid") _, err := client.FindContainer("not_valid")
require.Error(t, err, "error should be thrown") require.Error(t, err, "error should be thrown")
@@ -236,7 +236,7 @@ func Test_dockerClient_ContainerActions_happy(t *testing.T) {
} }
proxy := new(mockedProxy) proxy := new(mockedProxy)
client := &_client{proxy, filters.NewArgs(), &Host{ID: "localhost"}} client := &httpClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}, false}
json := types.ContainerJSON{Config: &container.Config{Tty: false}} json := types.ContainerJSON{Config: &container.Config{Tty: false}}
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil) proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil)
proxy.On("ContainerInspect", mock.Anything, "abcdefghijkl").Return(json, nil) proxy.On("ContainerInspect", mock.Anything, "abcdefghijkl").Return(json, nil)
@@ -272,7 +272,7 @@ func Test_dockerClient_ContainerActions_error(t *testing.T) {
} }
proxy := new(mockedProxy) proxy := new(mockedProxy)
client := &_client{proxy, filters.NewArgs(), &Host{ID: "localhost"}} client := &httpClient{proxy, filters.NewArgs(), &Host{ID: "localhost"}, false}
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil) proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil)
proxy.On("ContainerStart", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("test")) proxy.On("ContainerStart", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("test"))

View File

@@ -42,6 +42,7 @@ func (h *handler) streamEvents(w http.ResponseWriter, r *http.Request) {
allContainers := make([]docker.Container, 0) allContainers := make([]docker.Container, 0)
events := make(chan docker.ContainerEvent) events := make(chan docker.ContainerEvent)
stats := make(chan docker.ContainerStat) stats := make(chan docker.ContainerStat)
hasSwarm := false
for _, store := range h.stores { for _, store := range h.stores {
if containers, err := store.List(); err == nil { if containers, err := store.List(); err == nil {
@@ -55,6 +56,10 @@ func (h *handler) streamEvents(w http.ResponseWriter, r *http.Request) {
} }
store.SubscribeStats(ctx, stats) store.SubscribeStats(ctx, stats)
store.Subscribe(ctx, events) store.Subscribe(ctx, events)
if store.Client().IsSwarmMode() {
hasSwarm = true
}
} }
defer func() { defer func() {
@@ -67,6 +72,8 @@ func (h *handler) streamEvents(w http.ResponseWriter, r *http.Request) {
log.Errorf("error writing containers to event stream: %v", err) log.Errorf("error writing containers to event stream: %v", err)
} }
b.RunningContainers = len(allContainers) b.RunningContainers = len(allContainers)
b.IsSwarmMode = hasSwarm
f.Flush() f.Flush()
if !h.config.NoAnalytics { if !h.config.NoAnalytics {

View File

@@ -59,6 +59,10 @@ func (m *MockedClient) Host() *docker.Host {
return args.Get(0).(*docker.Host) return args.Get(0).(*docker.Host)
} }
func (m *MockedClient) IsSwarmMode() bool {
return false
}
func createHandler(client docker.Client, content fs.FS, config Config) *chi.Mux { func createHandler(client docker.Client, content fs.FS, config Config) *chi.Mux {
if client == nil { if client == nil {
client = new(MockedClient) client = new(MockedClient)

View File

@@ -29,7 +29,7 @@ func Test_valid_localhost(t *testing.T) {
fakeClientFactory := func(filter map[string][]string) (docker.Client, error) { fakeClientFactory := func(filter map[string][]string) (docker.Client, error) {
return docker.NewClient(client, filters.NewArgs(), &docker.Host{ return docker.NewClient(client, filters.NewArgs(), &docker.Host{
ID: "localhost", ID: "localhost",
}), nil }, false), nil
} }
args := args{} args := args{}
@@ -46,7 +46,7 @@ func Test_invalid_localhost(t *testing.T) {
fakeClientFactory := func(filter map[string][]string) (docker.Client, error) { fakeClientFactory := func(filter map[string][]string) (docker.Client, error) {
return docker.NewClient(client, filters.NewArgs(), &docker.Host{ return docker.NewClient(client, filters.NewArgs(), &docker.Host{
ID: "localhost", ID: "localhost",
}), nil }, false), nil
} }
args := args{} args := args{}
@@ -63,7 +63,7 @@ func Test_valid_remote(t *testing.T) {
fakeLocalClientFactory := func(filter map[string][]string) (docker.Client, error) { fakeLocalClientFactory := func(filter map[string][]string) (docker.Client, error) {
return docker.NewClient(local, filters.NewArgs(), &docker.Host{ return docker.NewClient(local, filters.NewArgs(), &docker.Host{
ID: "localhost", ID: "localhost",
}), nil }, false), nil
} }
remote := new(fakeCLI) remote := new(fakeCLI)
@@ -71,7 +71,7 @@ func Test_valid_remote(t *testing.T) {
fakeRemoteClientFactory := func(filter map[string][]string, host docker.Host) (docker.Client, error) { fakeRemoteClientFactory := func(filter map[string][]string, host docker.Host) (docker.Client, error) {
return docker.NewClient(remote, filters.NewArgs(), &docker.Host{ return docker.NewClient(remote, filters.NewArgs(), &docker.Host{
ID: "test", ID: "test",
}), nil }, false), nil
} }
args := args{ args := args{
@@ -93,7 +93,7 @@ func Test_valid_remote_and_local(t *testing.T) {
fakeLocalClientFactory := func(filter map[string][]string) (docker.Client, error) { fakeLocalClientFactory := func(filter map[string][]string) (docker.Client, error) {
return docker.NewClient(local, filters.NewArgs(), &docker.Host{ return docker.NewClient(local, filters.NewArgs(), &docker.Host{
ID: "localhost", ID: "localhost",
}), nil }, false), nil
} }
remote := new(fakeCLI) remote := new(fakeCLI)
@@ -101,7 +101,7 @@ func Test_valid_remote_and_local(t *testing.T) {
fakeRemoteClientFactory := func(filter map[string][]string, host docker.Host) (docker.Client, error) { fakeRemoteClientFactory := func(filter map[string][]string, host docker.Host) (docker.Client, error) {
return docker.NewClient(remote, filters.NewArgs(), &docker.Host{ return docker.NewClient(remote, filters.NewArgs(), &docker.Host{
ID: "test", ID: "test",
}), nil }, false), nil
} }
args := args{ args := args{
RemoteHost: []string{"tcp://test:2375"}, RemoteHost: []string{"tcp://test:2375"},
@@ -123,13 +123,13 @@ func Test_no_clients(t *testing.T) {
return docker.NewClient(local, filters.NewArgs(), &docker.Host{ return docker.NewClient(local, filters.NewArgs(), &docker.Host{
ID: "localhost", ID: "localhost",
}), nil }, false), nil
} }
fakeRemoteClientFactory := func(filter map[string][]string, host docker.Host) (docker.Client, error) { fakeRemoteClientFactory := func(filter map[string][]string, host docker.Host) (docker.Client, error) {
client := new(fakeCLI) client := new(fakeCLI)
return docker.NewClient(client, filters.NewArgs(), &docker.Host{ return docker.NewClient(client, filters.NewArgs(), &docker.Host{
ID: "test", ID: "test",
}), nil }, false), nil
} }
args := args{} args := args{}