Skip to content

Commit 7210e7b

Browse files
view: add support for rendering via different output streams (#9)
1 parent e7cc91b commit 7210e7b

File tree

15 files changed

+929
-129
lines changed

15 files changed

+929
-129
lines changed

README.md

+40
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
zns is a command-line utility for querying DNS records, displaying them in a human-readable, colored format that includes type, name, TTL, and value.
44

5+
* Supports various DNS record types
6+
* Colorized and tabular output for easy reading
7+
* Concurrent queries for improved performance
8+
* JSON output format for machine-readable results
9+
* Option to write output to a file
10+
511
## Installing
612

713
```sh
@@ -22,6 +28,40 @@ NS google.com. 93h48m24s ns1.google.com.
2228
SOA google.com. 55s ns1.google.com. dns-admin.google.com.
2329
```
2430

31+
## JSON output
32+
33+
```sh
34+
$ zns google.com --json | jq
35+
{
36+
"@domain": "google.com",
37+
"@level": "info",
38+
"@record": "142.250.179.206",
39+
"@timestamp": "2024-11-26T12:48:01.400203+01:00",
40+
"@ttl": "04m02s",
41+
"@type": "A",
42+
"@version": "dev",
43+
"@view": "json"
44+
}
45+
{
46+
"@domain": "google.com",
47+
"@level": "info",
48+
"@record": "ns2.google.com.",
49+
"@timestamp": "2024-11-26T12:48:01.401724+01:00",
50+
"@ttl": "93h46m51s",
51+
"@type": "NS",
52+
"@version": "dev",
53+
"@view": "json"
54+
}
55+
...
56+
```
57+
58+
## Writing to a file
59+
60+
```sh
61+
export ZNS_LOG_FILE=/tmp/zns.log
62+
$ zns google.com
63+
```
64+
2565
## Contributing
2666

2767
Contributions are highly appreciated and always welcome.

cmd/printer.go

-105
This file was deleted.

cmd/root.go

+49-9
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,26 @@ package cmd
22

33
import (
44
"fmt"
5+
"io"
56
"os"
67
"sort"
78
"strings"
89

910
"github.com/hashicorp/go-hclog"
1011
"github.com/hashicorp/go-multierror"
1112
"github.com/spf13/cobra"
13+
"github.com/znscli/zns/internal/arguments"
1214
"github.com/znscli/zns/internal/query"
15+
"github.com/znscli/zns/internal/view"
1316
)
1417

1518
var (
1619
version string
1720

18-
debug bool
21+
debug bool
22+
json bool
23+
noColor bool
24+
1925
server string
2026
qtype string
2127

@@ -30,19 +36,52 @@ var (
3036
os.Exit(1)
3137
}
3238

33-
// Determine log level based on environment variable and debug flag
39+
var color hclog.ColorOption
40+
if os.Getenv("NO_COLOR") != "" {
41+
noColor = true
42+
color = hclog.ColorOff
43+
} else {
44+
color = hclog.AutoColor
45+
}
46+
3447
logLevel := os.Getenv("ZNS_LOG_LEVEL")
3548
if debug {
36-
logLevel = "DEBUG" // Override log level to DEBUG if the debug flag is set
49+
logLevel = "DEBUG"
3750
}
3851

52+
var vt arguments.ViewType
53+
if json {
54+
vt = arguments.ViewJSON
55+
} else {
56+
vt = arguments.ViewHuman
57+
}
58+
59+
var w io.Writer = os.Stdout
60+
logFile := os.Getenv("ZNS_LOG_FILE")
61+
if logFile != "" {
62+
f, err := os.Create(logFile)
63+
if err != nil {
64+
panic(fmt.Sprintf("Failed to create log file: %v", err))
65+
}
66+
defer f.Close()
67+
w = f
68+
}
69+
70+
v := view.NewRenderer(vt, &view.View{
71+
Stream: &view.Stream{
72+
Writer: w,
73+
},
74+
})
75+
3976
logger := hclog.New(&hclog.LoggerOptions{
4077
Name: "zns",
78+
Output: w,
4179
Level: hclog.LevelFromString(logLevel),
42-
Color: hclog.AutoColor,
43-
ColorHeaderAndFields: true,
44-
DisableTime: true,
45-
})
80+
Color: color,
81+
ColorHeaderAndFields: !noColor,
82+
DisableTime: false,
83+
JSONFormat: json,
84+
}).With("@domain", args[0])
4685

4786
// Log the debug state and current log level
4887
logger.Debug("Debug logging enabled", "debug", debug)
@@ -92,7 +131,7 @@ var (
92131

93132
for _, m := range messages {
94133
for _, record := range m.Answer {
95-
printRecord(args[0], record)
134+
v.Render(args[0], record)
96135
}
97136
}
98137
},
@@ -103,7 +142,8 @@ func init() {
103142
rootCmd.CompletionOptions.DisableDefaultCmd = true
104143
rootCmd.Flags().StringVarP(&server, "server", "s", "1.1.1.1", "DNS server to query")
105144
rootCmd.Flags().StringVarP(&qtype, "query-type", "q", "", "DNS query type")
106-
rootCmd.Flags().BoolVar(&debug, "debug", false, "Enable debug logging")
145+
rootCmd.Flags().BoolVar(&debug, "debug", false, "If set, debug output is printed")
146+
rootCmd.Flags().BoolVar(&json, "json", false, "If set, output is printed in JSON format.")
107147
}
108148

109149
func Execute() {

go.mod

+5-2
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,26 @@ go 1.23.1
44

55
require (
66
github.com/fatih/color v1.18.0
7+
github.com/google/go-cmp v0.6.0
78
github.com/hashicorp/go-hclog v1.6.3
89
github.com/hashicorp/go-multierror v1.1.1
9-
github.com/juju/ansiterm v1.0.0
1010
github.com/miekg/dns v1.1.62
1111
github.com/spf13/cobra v1.8.1
12+
github.com/stretchr/testify v1.7.2
1213
)
1314

1415
require (
16+
github.com/davecgh/go-spew v1.1.1 // indirect
1517
github.com/hashicorp/errwrap v1.0.0 // indirect
1618
github.com/inconshreveable/mousetrap v1.1.0 // indirect
17-
github.com/lunixbochs/vtclean v1.0.0 // indirect
1819
github.com/mattn/go-colorable v0.1.13 // indirect
1920
github.com/mattn/go-isatty v0.0.20 // indirect
21+
github.com/pmezard/go-difflib v1.0.0 // indirect
2022
github.com/spf13/pflag v1.0.5 // indirect
2123
golang.org/x/mod v0.18.0 // indirect
2224
golang.org/x/net v0.27.0 // indirect
2325
golang.org/x/sync v0.7.0 // indirect
2426
golang.org/x/sys v0.25.0 // indirect
2527
golang.org/x/tools v0.22.0 // indirect
28+
gopkg.in/yaml.v3 v3.0.1 // indirect
2629
)

go.sum

+3-12
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
55
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
66
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
77
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
8+
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
9+
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
810
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
911
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
1012
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
@@ -13,17 +15,7 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l
1315
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
1416
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
1517
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
16-
github.com/juju/ansiterm v1.0.0 h1:gmMvnZRq7JZJx6jkfSq9/+2LMrVEwGwt7UR6G+lmDEg=
17-
github.com/juju/ansiterm v1.0.0/go.mod h1:PyXUpnI3olx3bsPcHt98FGPX/KCFZ1Fi+hw1XLI6384=
18-
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
19-
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
20-
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
21-
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
22-
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
23-
github.com/lunixbochs/vtclean v1.0.0 h1:xu2sLAri4lGiovBDQKxl5mrXyESr3gUr5m5SM5+LVb8=
24-
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
2518
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
26-
github.com/mattn/go-colorable v0.1.10/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
2719
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
2820
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
2921
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
@@ -61,8 +53,7 @@ golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
6153
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
6254
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
6355
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
56+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
6457
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
65-
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
66-
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
6758
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
6859
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

internal/arguments/arguments.go

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package arguments
2+
3+
// ViewType represents which view layer to use.
4+
type ViewType rune
5+
6+
const (
7+
ViewNone ViewType = 0
8+
ViewHuman ViewType = 'H'
9+
ViewJSON ViewType = 'J'
10+
)
11+
12+
func (vt ViewType) String() string {
13+
switch vt {
14+
case ViewNone:
15+
return "none"
16+
case ViewHuman:
17+
return "human"
18+
case ViewJSON:
19+
return "json"
20+
default:
21+
return "unknown"
22+
}
23+
}

internal/query/query.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
package query
22

33
import (
4+
"sync"
5+
46
"github.com/hashicorp/go-hclog"
57
"github.com/hashicorp/go-multierror"
68
"github.com/miekg/dns"
7-
"sync"
89
)
910

1011
var (

0 commit comments

Comments
 (0)