Skip to content

Commit

Permalink
Fix logs tail issue when there are no logs (#744)
Browse files Browse the repository at this point in the history
  • Loading branch information
sergiught authored Apr 20, 2023
1 parent b31b615 commit 26f9c4c
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 25 deletions.
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

0 comments on commit 26f9c4c

Please sign in to comment.