Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix logs tail issue when there are no logs #744

Merged
merged 6 commits into from
Apr 20, 2023
Merged
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
50 changes: 25 additions & 25 deletions internal/cli/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ import (
"github.com/spf13/cobra"
)

// Besides the limitation of 100 log events per request to retrieve logs,
// we may only paginate through up to 1000 search results.
// https://auth0.com/docs/logs/retrieve-log-events-using-mgmt-api#limitations
const logsPerPageLimit = 100

var (
logsFilter = Flag{
Name: "Filter",
Expand Down Expand Up @@ -63,7 +68,7 @@ func listLogsCmd(cli *cli) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
list, err := getLatestLogs(cli, inputs.Num, inputs.Filter)
if err != nil {
return fmt.Errorf("An unexpected error occurred while getting logs: %v", err)
return fmt.Errorf("failed to get logs: %w", err)
}

hasFilter := inputs.Filter != ""
Expand Down Expand Up @@ -100,45 +105,43 @@ func tailLogsCmd(cli *cli) *cobra.Command {
auth0 logs tail --filter "type:f" # See the full list of type codes at https://auth0.com/docs/logs/log-event-type-codes
auth0 logs tail -n 100`,
RunE: func(cmd *cobra.Command, args []string) error {
lastLogID := ""
list, err := getLatestLogs(cli, inputs.Num, inputs.Filter)
if err != nil {
return fmt.Errorf("An unexpected error occurred while getting logs: %v", err)
return fmt.Errorf("failed to get logs: %w", err)
}

// TODO(cyx): This is a hack for now to make the
// streaming work faster.
//
// Create a `set` to detect duplicates clientside.
set := make(map[string]struct{})
list = dedupeLogs(list, set)
logsCh := make(chan []*management.Log)

var lastLogID string
if len(list) > 0 {
lastLogID = list[len(list)-1].GetLogID()
}

logsCh := make(chan []*management.Log)
// Create a `set` to detect duplicates clientside.
set := make(map[string]struct{})
list = dedupeLogs(list, set)

go func() {
// This is pretty important and allows
// us to close / terminate the command.
go func(lastLogID string) {
defer close(logsCh)

for {
queryParams := []management.RequestOption{
management.Query(fmt.Sprintf("log_id:[%s TO *]", lastLogID)),
management.Parameter("page", "0"),
management.Parameter("per_page", "100"),
management.Parameter("sort", "date:-1"),
}

if lastLogID != "" {
queryParams = append(queryParams, management.Query(fmt.Sprintf("log_id:[%s TO *]", lastLogID)))
}

if inputs.Filter != "" {
queryParams = append(queryParams, management.Query(inputs.Filter))
}

list, err = cli.api.Log.List(queryParams...)
list, err := cli.api.Log.List(queryParams...)
if err != nil {
cli.renderer.Errorf("An unexpected error occurred while getting logs: %v", err)
cli.renderer.Errorf("Failed to get latest logs: %v", err)
return
}

Expand All @@ -147,12 +150,12 @@ func tailLogsCmd(cli *cli) *cobra.Command {
lastLogID = list[len(list)-1].GetLogID()
}

if len(list) < 90 {
// Not a lot is happening, sleep on it
time.Sleep(1 * time.Second)
if len(list) < logsPerPageLimit {
// Not a lot is happening, sleep on it.
time.Sleep(time.Second)
}
}
}()
}(lastLogID)

cli.renderer.LogTail(list, logsCh, !cli.debug)
return nil
Expand All @@ -168,11 +171,8 @@ func tailLogsCmd(cli *cli) *cobra.Command {
func getLatestLogs(cli *cli, n int, filter string) ([]*management.Log, error) {
page := 0
perPage := n

if perPage > 1000 {
// Pagination max out at 1000 entries in total
// https://auth0.com/docs/logs/retrieve-log-events-using-mgmt-api#limitations
perPage = 1000
if perPage > logsPerPageLimit {
perPage = logsPerPageLimit
}

queryParams := []management.RequestOption{
Expand Down
93 changes: 93 additions & 0 deletions internal/cli/logs_test.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,109 @@
package cli

import (
"bytes"
"fmt"
"testing"
"time"

"github.com/auth0/go-auth0/management"
"github.com/golang/mock/gomock"

"github.com/stretchr/testify/assert"

"github.com/auth0/auth0-cli/internal/auth0"
"github.com/auth0/auth0-cli/internal/auth0/mock"
"github.com/auth0/auth0-cli/internal/display"
)

func TestTailLogsCommand(t *testing.T) {
t.Run("it returns an error when it fails to get the logs on the first request", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

logsAPI := mock.NewMockLogAPI(ctrl)
logsAPI.EXPECT().
List(gomock.Any()).
Return(nil, fmt.Errorf("generic error"))

cli := &cli{
api: &auth0.API{Log: logsAPI},
}

cmd := tailLogsCmd(cli)
cmd.SetArgs([]string{"--number", "90", "--filter", "user_id:123"})
err := cmd.Execute()

assert.EqualError(t, err, "failed to get logs: generic error")
})

t.Run("it returns an error when it fails to get the logs on the 3rd request", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

logsAPI := mock.NewMockLogAPI(ctrl)
logsAPI.EXPECT().
List(gomock.Any()).
Return(
[]*management.Log{
{
LogID: auth0.String("354234"),
Type: auth0.String("sapi"),
Description: auth0.String("Update branding settings"),
},
},
nil,
)

logsAPI.EXPECT().
List(gomock.Any()).
Return(
[]*management.Log{
{
LogID: auth0.String("354234"),
Type: auth0.String("sapi"),
Description: auth0.String("Update branding settings"),
},
{
LogID: auth0.String("354236"),
Type: auth0.String("sapi"),
Description: auth0.String("Update tenant settings"),
},
},
nil,
)

logsAPI.EXPECT().
List(gomock.Any()).
Return(nil, fmt.Errorf("generic error"))

expectedResult := `TYPE DESCRIPTION DATE CONNECTION CLIENT
API Operation Update branding settings Jan 01 00:00:00.000 N/A N/A
`

message := &bytes.Buffer{}
result := &bytes.Buffer{}
cli := &cli{
renderer: &display.Renderer{
Tenant: "auth0-cli-tests.eu.auth0.com",
MessageWriter: message,
ResultWriter: result,
},
api: &auth0.API{Log: logsAPI},
}

cmd := tailLogsCmd(cli)
cmd.SetArgs([]string{"--number", "90", "--filter", "user_id:123"})
err := cmd.Execute()
assert.NoError(t, err)

assert.Contains(t, message.String(), "auth0-cli-tests.eu.auth0.com") // Ensure we display the tenant name.
assert.Contains(t, message.String(), "logs") // Ensure header is set in output.
assert.Contains(t, message.String(), "Failed to get latest logs: generic error")
assert.Equal(t, expectedResult, result.String())
})
}

func TestDedupeLogs(t *testing.T) {
t.Run("removes duplicate logs and sorts by date asc", func(t *testing.T) {
logs := []*management.Log{
Expand Down