/
parse.go
135 lines (124 loc) · 3.03 KB
/
parse.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package cmd
import (
"errors"
"fmt"
"io"
"os"
"strings"
"github.com/spf13/cobra"
parser "github.com/stephane-martin/w3c-extendedlog-parser"
)
var filenames = make([]string, 0)
var jsonExport bool
var csvExport bool
var suffix bool
func foreach(sl []string, f func(string) string) (ret []string) {
ret = make([]string, 0, len(sl))
for _, s := range sl {
ret = append(ret, f(s))
}
return ret
}
func sanitize(header string) (ret string) {
ret = strings.Replace(header, "(", "_", -1)
ret = strings.Replace(ret, ")", "_", -1)
ret = strings.Replace(ret, "-", "_", -1)
ret = strings.Replace(ret, "__", "_", -1)
ret = strings.Trim(ret, "_")
return ret
}
func suffixHeaders(header string) (ret string) {
switch parser.GuessType(header) {
case parser.MyDate:
return header + "_date"
case parser.MyIP:
return header + "_ip"
case parser.MyTime:
return header + "_time"
case parser.MyTimestamp:
return header + "_timestamp"
case parser.MyURI:
return header + "_uri"
case parser.Float64:
return header + "_float"
case parser.Int64:
return header + "_int"
case parser.Bool:
return header + "_bool"
case parser.String:
return header + "_str"
}
return header + "_str"
}
var parseCmd = &cobra.Command{
Use: "parse",
Short: "Parse an access log file and print the lines it as JSON or CSV",
Run: func(cmd *cobra.Command, args []string) {
if len(filenames) == 0 {
fatal(errors.New("specify the files to be parsed"))
}
if jsonExport && csvExport {
fatal(errors.New("--json and --csv are exclusive"))
}
if !jsonExport && !csvExport {
jsonExport = true
}
for _, fname := range filenames {
fname = strings.TrimSpace(fname)
f, err := os.Open(fname)
if err != nil {
fmt.Fprintf(os.Stderr, "Error opening '%s': %s\n", fname, err)
continue
}
err = doParse(f, os.Stdout, jsonExport, csvExport, suffix)
f.Close()
if err != nil {
fmt.Fprintln(os.Stderr, "Error building parser:", err)
}
}
},
}
func doParse(in io.Reader, out io.Writer, doJSON bool, doCSV bool, printSuffix bool) error {
p := parser.NewFileParser(in)
err := p.ParseHeader()
if err != nil {
return err
}
fieldNames := p.FieldNames()
if doCSV {
// print header line
if printSuffix {
fmt.Fprintln(out, strings.Join(
foreach(
foreach(fieldNames, suffixHeaders),
sanitize,
),
",",
))
} else {
fmt.Fprintln(out, strings.Join(foreach(fieldNames, sanitize), ","))
}
}
var l *parser.Line
for {
l, err = p.NextTo(l)
if l == nil || err != nil {
break
}
err = l.WriteTo(out, doJSON)
if err != nil {
return err
}
}
if err != nil && err != io.EOF {
return err
}
return nil
}
func init() {
rootCmd.AddCommand(parseCmd)
parseCmd.Flags().StringArrayVar(&filenames, "filename", []string{}, "the files to parse")
parseCmd.Flags().BoolVar(&jsonExport, "json", false, "print the logs as JSON")
parseCmd.Flags().BoolVar(&csvExport, "csv", false, "print the logs as CSV")
parseCmd.Flags().BoolVar(&suffix, "suffix", false, "when exporting to CSV, suffix the field names with data type")
}