Skip to content

Commit b9f8212

Browse files
committed
feat: update axiom-go and use tabular
1 parent 5a1d546 commit b9f8212

File tree

7 files changed

+384
-324
lines changed

7 files changed

+384
-324
lines changed

.golangci.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ linters:
55
disable-all: true
66
enable:
77
- bodyclose
8+
- copyloopvar
89
- dogsled
910
- dupl
1011
- errcheck
11-
- exportloopref
1212
- goconst
1313
- gofmt
1414
- goimports

go.mod

Lines changed: 85 additions & 83 deletions
Large diffs are not rendered by default.

go.sum

Lines changed: 186 additions & 184 deletions
Large diffs are not rendered by default.

internal/client/client.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ func New(ctx context.Context, baseURL, accessToken, orgID string, insecure bool)
2626
Timeout: time.Second * 30,
2727
KeepAlive: time.Second * 30,
2828
}).DialContext,
29-
IdleConnTimeout: time.Second * 90,
30-
ResponseHeaderTimeout: time.Second * 10,
29+
IdleConnTimeout: time.Minute,
30+
ResponseHeaderTimeout: time.Minute * 2,
3131
TLSHandshakeTimeout: time.Second * 10,
3232
ExpectContinueTimeout: time.Second * 1,
3333
ForceAttemptHTTP2: true,

internal/cmd/ingest/ingest.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -364,14 +364,14 @@ func run(ctx context.Context, opts *options, flushEverySet, batchSizeSet, csvFie
364364
if res.Ingested > 0 {
365365
fmt.Fprintf(opts.IO.ErrOut(), "%s Ingested %s\n",
366366
cs.SuccessIcon(),
367-
utils.Pluralize(cs, "event", int(res.Ingested)),
367+
utils.Pluralize(cs, "event", int(res.Ingested)), //nolint:gosec // Not relevant here.
368368
)
369369
}
370370

371371
if res.Failed > 0 {
372372
fmt.Fprintf(opts.IO.ErrOut(), "%s Failed to ingest %s:\n\n",
373373
cs.ErrorIcon(),
374-
utils.Pluralize(cs, "event", int(res.Failed)),
374+
utils.Pluralize(cs, "event", int(res.Failed)), //nolint:gosec // Not relevant here.
375375
)
376376
for _, fail := range res.Failures {
377377
fmt.Fprintf(opts.IO.ErrOut(), "%s: %s\n",

internal/cmd/query/query.go

Lines changed: 107 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ package query
22

33
import (
44
"context"
5-
"encoding/json"
65
"errors"
76
"fmt"
87
"io"
8+
"slices"
99
"time"
1010

1111
"github.com/AlecAivazis/survey/v2"
@@ -160,7 +160,7 @@ func run(ctx context.Context, opts *options) error {
160160
)
161161
if err != nil {
162162
return err
163-
} else if len(res.Matches) == 0 && len(res.Buckets.Totals) == 0 {
163+
} else if res.Status.RowsMatched == 0 || len(res.Tables) == 0 || len(res.Tables[0].Columns) == 0 {
164164
return errors.New("query returned no results")
165165
}
166166

@@ -172,88 +172,144 @@ func run(ctx context.Context, opts *options) error {
172172
}
173173
defer pagerStop()
174174

175-
// Deal with JSON output format.
176-
if opts.Format == iofmt.JSON.String() {
177-
var data any
178-
if len(res.Matches) > 0 {
179-
data = res.Matches
180-
} else if len(res.Buckets.Totals) > 0 {
181-
// For ungrouped buckets we just return the aggregated total.
182-
if len(res.Buckets.Totals[0].Group) == 0 {
183-
data = res.Buckets.Totals[0].Aggregations[0].Value
184-
} else {
185-
data = res.Buckets.Totals
186-
}
187-
}
188-
return iofmt.FormatToJSON(opts.IO.Out(), data, opts.IO.ColorEnabled())
189-
}
190-
191175
cs := opts.IO.ColorScheme()
192176

193177
headerText := cs.Bold(opts.Query)
194178
headerText += fmt.Sprintf(" processed in %s", cs.Gray(res.Status.ElapsedTime.String()))
195179
headerText = fmt.Sprintf("Result of query %s:\n\n", headerText)
196180

197-
// Deal with table output format for matches.
198-
if len(res.Matches) > 0 {
181+
table := res.Tables[0]
182+
183+
// Deal with JSON output format. It only works for non-aggregated results OR
184+
// an aggregated result which produces a single value (it has no groups).
185+
if opts.Format == iofmt.JSON.String() {
186+
if tableHasAggregation(table) {
187+
if len(table.Groups) > 1 || (len(table.Columns) > 1 && len(table.Columns[0]) > 1) {
188+
return errors.New("JSON output format is not supported for aggregated results with groups")
189+
}
190+
if opts.IO.IsStdoutTTY() {
191+
fmt.Fprint(opts.IO.Out(), headerText)
192+
}
193+
return iofmt.FormatToJSON(opts.IO.Out(), table.Columns[0][0], opts.IO.ColorEnabled())
194+
}
195+
199196
if opts.IO.IsStdoutTTY() {
200197
fmt.Fprint(opts.IO.Out(), headerText)
201198
}
202199

203-
for _, entry := range res.Matches {
204-
var data any
205-
switch opts.Format {
206-
case iofmt.JSON.String():
207-
data = entry
208-
default:
209-
fmt.Fprintf(opts.IO.Out(), "%s\t", cs.Gray(entry.Time.Format(time.RFC1123)))
210-
data = entry.Data
211-
}
212-
if err = iofmt.FormatToJSON(opts.IO.Out(), data, opts.IO.ColorEnabled()); err != nil {
200+
for i := range len(table.Columns[0]) {
201+
if err = iofmt.FormatToJSON(opts.IO.Out(), tableRowAtIndex(table, i), opts.IO.ColorEnabled()); err != nil {
213202
return err
214203
}
215204
}
216205

217206
return nil
218207
}
219208

220-
// Deal with table output format for grouped results.
221-
222-
// If we have no groups, we just print the aggregated total.
223-
if len(res.Buckets.Totals[0].Group) == 0 {
209+
// Deal with table output for a result with more than ten fields as it
210+
// wouldn't be readable in a table.
211+
if !tableHasAggregation(table) && len(table.Fields) > 10 {
224212
if opts.IO.IsStdoutTTY() {
225213
fmt.Fprint(opts.IO.Out(), headerText)
226214
}
227215

228-
return iofmt.FormatToJSON(opts.IO.Out(),
229-
res.Buckets.Totals[0].Aggregations[0].Value, opts.IO.ColorEnabled())
216+
hasTimeField := slices.ContainsFunc(table.Fields, func(field query.Field) bool {
217+
return field.Name == "_time"
218+
})
219+
hasSysTimeField := slices.ContainsFunc(table.Fields, func(field query.Field) bool {
220+
return field.Name == "_sysTime"
221+
})
222+
223+
for i := range len(table.Columns[0]) {
224+
row := tableRowAtIndex(table, i)
225+
if hasTimeField {
226+
ts, err := time.Parse(time.RFC3339Nano, fmt.Sprint(row["_time"]))
227+
if err != nil {
228+
return err
229+
}
230+
fmt.Fprintf(opts.IO.Out(), "%s\t", cs.Gray(ts.Format(time.RFC1123)))
231+
delete(row, "_time")
232+
}
233+
if hasSysTimeField {
234+
delete(row, "_sysTime")
235+
}
236+
if err = iofmt.FormatToJSON(opts.IO.Out(), row, opts.IO.ColorEnabled()); err != nil {
237+
return err
238+
}
239+
}
240+
241+
return nil
242+
}
243+
244+
// Deal with table output format for non-aggregated results.
245+
if !tableHasAggregation(table) {
246+
var header iofmt.HeaderBuilderFunc
247+
if opts.IO.IsStdoutTTY() {
248+
header = func(_ io.Writer, trb iofmt.TableRowBuilder) {
249+
fmt.Fprint(opts.IO.Out(), headerText)
250+
for _, field := range table.Fields {
251+
trb.AddField(field.Name, cs.Bold)
252+
}
253+
}
254+
}
255+
256+
contentRow := func(trb iofmt.TableRowBuilder, k int) {
257+
for _, column := range table.Columns {
258+
trb.AddField(fmt.Sprint(column[k]), nil)
259+
}
260+
}
261+
262+
return iofmt.FormatToTable(opts.IO, len(table.Columns[0]), header, nil, contentRow)
230263
}
231264

232-
// If we have groups, we print a table with the groups as columns and the
233-
// aggregated totals as rows.
234-
var (
235-
header iofmt.HeaderBuilderFunc
236-
columnNames = res.GroupBy
237-
)
265+
// Deal with table output format for aggregated results.
266+
var header iofmt.HeaderBuilderFunc
238267
if opts.IO.IsStdoutTTY() {
239268
header = func(_ io.Writer, trb iofmt.TableRowBuilder) {
240269
fmt.Fprint(opts.IO.Out(), headerText)
241-
for _, name := range columnNames {
242-
trb.AddField(name, cs.Bold)
270+
for _, field := range table.Fields {
271+
trb.AddField(field.Name, cs.Bold)
243272
}
244-
trb.AddField(res.Buckets.Totals[0].Aggregations[0].Alias, cs.Bold)
245273
}
246274
}
247275

248276
contentRow := func(trb iofmt.TableRowBuilder, k int) {
249-
total := res.Buckets.Totals[k]
250-
aggValue, _ := json.Marshal(total.Aggregations[0].Value)
277+
for _, column := range table.Columns {
278+
trb.AddField(fmt.Sprint(column[k]), nil)
279+
}
280+
}
281+
282+
var footer iofmt.HeaderBuilderFunc
283+
if opts.IO.IsStdoutTTY() && len(res.Tables) > 1 && res.Tables[1].Name == "_totals" {
284+
totalsTable := res.Tables[1]
285+
footer = func(_ io.Writer, trb iofmt.TableRowBuilder) {
286+
trb.AddField("Total", cs.Bold) // Account for the _time field present in the former table.
287+
for i, field := range totalsTable.Fields {
288+
var total string
289+
if field.Aggregation != nil {
290+
total = fmt.Sprint(totalsTable.Columns[i][0])
291+
}
292+
trb.AddField(total, nil)
293+
}
294+
}
295+
}
296+
297+
return iofmt.FormatToTable(opts.IO, len(table.Columns[0]), header, footer, contentRow)
298+
}
251299

252-
for _, name := range columnNames {
253-
trb.AddField(fmt.Sprint(total.Group[name]), nil)
300+
func tableHasAggregation(table query.Table) bool {
301+
for _, field := range table.Fields {
302+
if field.Aggregation != nil {
303+
return true
254304
}
255-
trb.AddField(string(aggValue), nil)
256305
}
306+
return false
307+
}
257308

258-
return iofmt.FormatToTable(opts.IO, len(res.Buckets.Totals), header, nil, contentRow)
309+
func tableRowAtIndex(table query.Table, rowIdx int) map[string]any {
310+
row := make(map[string]any, len(table.Fields))
311+
for i, field := range table.Fields {
312+
row[field.Name] = table.Columns[i][rowIdx]
313+
}
314+
return row
259315
}

internal/cmd/root/help.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ func rootHelpFunc(io *terminal.IO) func(*cobra.Command, []string) {
102102
continue
103103
}
104104

105-
s := padding.String(c.Name()+":", uint(c.NamePadding()+2)) + c.Short
105+
s := padding.String(c.Name()+":", uint(c.NamePadding()+2)) + c.Short //nolint:gosec // Not relevant here.
106106
if _, ok := c.Annotations["IsCore"]; ok {
107107
coreCommands = append(coreCommands, s)
108108
} else if _, ok := c.Annotations["IsManagement"]; ok {

0 commit comments

Comments
 (0)