forked from swfrench/nginx-log-exporter
/
main.go
151 lines (115 loc) · 4.34 KB
/
main.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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
package main
import (
"flag"
"fmt"
"log"
"log/syslog"
"net/http"
"strings"
"time"
"github.com/niedbalski/nginx-log-exporter/consumer"
"github.com/niedbalski/nginx-log-exporter/file"
"github.com/niedbalski/nginx-log-exporter/metrics"
"cloud.google.com/go/compute/metadata"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
exportAddress = flag.String("export_address", "0.0.0.0:9091", "Address to which we export the /metrics handler.")
accessLogPath = flag.String("access_log_path", "", "Path to access log file.")
accessLogFormat = flag.String("access_log_format", "JSON", "Format of log lines in the access log. Supported: JSON (see README) and CLF.")
logPollingPeriod = flag.Duration("log_polling_period", 30*time.Second, "Period between checks for new log lines.")
rotationCheckPeriod = flag.Duration("rotation_check_period", time.Minute, "Idle period between log rotation checks.")
useSyslog = flag.Bool("use_syslog", false, "If true, emit info logs to syslog.")
useMetadataServiceLabels = flag.Bool("use_metadata_service_labels", false, "If true, use the GCE instance metadata service to fetch \"instance_id\" and \"zone\" labels, which will be applied to all metrics.")
customLabels = flag.String("custom_labels", "", "A comma-separated, key=value list of additional labels to apply to all metrics.")
monitoredPaths = flag.String("monitored_paths", "", "A comma-separated list of paths for which response metrics will be exported at path/method granularity. Paths are matched verbatim to the start of the first non-path expression (query string, fragment, etc.). Elements must be non-empty and contain no whitespace.")
)
func parseCustomLabels() (map[string]string, error) {
labels := make(map[string]string)
if len(*customLabels) > 0 {
for _, elem := range strings.Split(*customLabels, ",") {
if pair := strings.Split(elem, "="); len(pair) == 2 {
labels[pair[0]] = pair[1]
} else {
return nil, fmt.Errorf("Could not parse key=value pair: %v", elem)
}
}
}
return labels, nil
}
func parseMonitoredPaths() ([]string, error) {
var paths []string
if len(*monitoredPaths) > 0 {
for _, elem := range strings.Split(*monitoredPaths, ",") {
if len(elem) > 0 && len(strings.Fields(elem)) == 1 {
paths = append(paths, elem)
} else {
return nil, fmt.Errorf("monitored paths must be non-empty and contain no whitespace")
}
}
}
return paths, nil
}
func getLabelsFromMetadataService() (map[string]string, error) {
if !metadata.OnGCE() {
return nil, fmt.Errorf("Metadata service is unavailable when not on GCE")
}
instance, err := metadata.InstanceName()
if err != nil {
return nil, fmt.Errorf("Could not retrieve instance name from metadata service: %v", err)
}
zone, err := metadata.Zone()
if err != nil {
return nil, fmt.Errorf("Could not retrieve zone name from metadata service: %v", err)
}
return map[string]string{
"instance_id": instance,
"zone": zone,
}, nil
}
func main() {
flag.Parse()
if *useSyslog {
w, err := syslog.New(syslog.LOG_INFO, "nginx_log_consumer")
if err != nil {
log.Fatalf("Could not create syslog writer: %v", err)
}
log.SetOutput(w)
}
t, err := file.NewTailer(*accessLogPath, *rotationCheckPeriod)
if err != nil {
log.Fatalf("Could not create tailer for %s: %v", *accessLogPath, err)
}
labels, err := parseCustomLabels()
if err != nil {
log.Fatalf("Could not parse custom labels: %v", err)
}
if *useMetadataServiceLabels {
metadataLabels, err := getLabelsFromMetadataService()
if err != nil {
log.Fatalf("Could not fetch labels from metadata service: %v", err)
}
for key := range metadataLabels {
labels[key] = metadataLabels[key]
}
}
paths, err := parseMonitoredPaths()
if err != nil {
log.Fatalf("Could not parse monitored paths: %v", err)
}
log.Printf("Creating metrics manager for with base labels: %v", labels)
m := metrics.NewManager(labels)
log.Printf("Starting prometheus exporter at %s", *exportAddress)
go func() {
http.Handle("/metrics", promhttp.Handler())
log.Fatal(http.ListenAndServe(*exportAddress, nil))
}()
c, err := consumer.NewConsumer(*logPollingPeriod, t, m, paths, *accessLogFormat)
if err != nil {
log.Fatalf("Could not create consumer: %v", err)
}
log.Printf("Starting consumer for %s", *accessLogPath)
if err := c.Run(); err != nil {
log.Fatalf("Failure consuming logs: %v", err)
}
}