You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
169 lines
4.0 KiB
169 lines
4.0 KiB
package main |
|
|
|
import ( |
|
"log" |
|
"net" |
|
"os" |
|
"regexp" |
|
"strconv" |
|
"strings" |
|
"time" |
|
|
|
"github.com/hpcloud/tail" |
|
"github.com/prometheus/client_golang/prometheus" |
|
"github.com/satyrius/gonx" |
|
) |
|
|
|
type NginxExporter struct { |
|
AccessLogPath string |
|
|
|
responses *prometheus.SummaryVec |
|
error_responses *prometheus.SummaryVec |
|
} |
|
|
|
func NewNginxExporter(accessLogPath string) (*NginxExporter, error) { |
|
const subsystem = "http" |
|
|
|
return &NginxExporter{ |
|
AccessLogPath: accessLogPath, |
|
responses: prometheus.NewSummaryVec( |
|
prometheus.SummaryOpts{ |
|
Namespace: Namespace, |
|
Subsystem: subsystem, |
|
Name: "responses_sent_bytes", |
|
Help: "Summary of HTTP responses", |
|
}, |
|
[]string{"project", "network", "protocol"}), |
|
error_responses: prometheus.NewSummaryVec( |
|
prometheus.SummaryOpts{ |
|
Namespace: Namespace, |
|
Subsystem: subsystem, |
|
Name: "error_responses_sent_bytes", |
|
Help: "Summary of error HTTP responses", |
|
}, |
|
[]string{"project", "network", "protocol"}), |
|
}, nil |
|
} |
|
|
|
// Implements prometheus.Collector |
|
func (e *NginxExporter) Describe(ch chan<- *prometheus.Desc) { |
|
e.responses.Describe(ch) |
|
e.error_responses.Describe(ch) |
|
} |
|
|
|
// Implements prometheus.Collector |
|
func (e *NginxExporter) Collect(ch chan<- prometheus.Metric) { |
|
e.responses.Collect(ch) |
|
e.error_responses.Collect(ch) |
|
} |
|
|
|
var nginxProjectRe = regexp.MustCompile("(?i)^\\w+ /(?:pub/)?([^/?]+)/[^\\s]* HTTP") |
|
|
|
func (e *NginxExporter) processLogLine(line string) { |
|
lineReader := strings.NewReader(line) |
|
reader := gonx.NewReader( |
|
lineReader, |
|
`$remote_addr - $remote_user [$time_local] "$request" $status $bytes_sent "$http_referer" "$http_user_agent"`) |
|
|
|
if reader == nil { |
|
log.Printf("Failed to create reader for line: %s\n", line) |
|
return |
|
} |
|
|
|
// Read the entry on the line |
|
entry, err := reader.Read() |
|
if err != nil { |
|
log.Printf("Failed to get entry on line: %s\n", line) |
|
return |
|
} |
|
|
|
// Process |
|
sourceIP, err := entry.Field("remote_addr") |
|
network := "unknown" |
|
protocol := "unknown" |
|
|
|
if err == nil { |
|
ip := net.ParseIP(sourceIP) |
|
network = IdentifyNetwork(ip) |
|
protocol = IdentifyIPProtocol(ip) |
|
} else { |
|
log.Println(err) |
|
} |
|
|
|
request, err := entry.Field("request") |
|
project := "unknown" |
|
|
|
if err == nil { |
|
match := nginxProjectRe.FindStringSubmatch(request) |
|
|
|
if len(match) > 1 && IsMirroredProject(match[1]) { |
|
project = match[1] |
|
} else { |
|
if len(match) > 1 { log.Println(match[1], "not found") } |
|
project = "none" |
|
} |
|
} else { |
|
log.Println(err) |
|
} |
|
|
|
sizeStr, err := entry.Field("bytes_sent") |
|
if err != nil { |
|
sizeStr = "0" |
|
} |
|
size, _ := strconv.ParseFloat(sizeStr, 64) |
|
|
|
statusStr, err := entry.Field("status") |
|
success := false |
|
|
|
if err == nil { |
|
responseCode, _ := strconv.Atoi(statusStr) |
|
|
|
if responseCode >= 100 && responseCode <= 399 { |
|
success = true |
|
} |
|
} |
|
|
|
// Setup labels |
|
labels := prometheus.Labels{ |
|
"project": project, |
|
"network": network, |
|
"protocol": protocol, |
|
} |
|
|
|
// Increment totals |
|
e.responses.With(labels).Observe(size) |
|
|
|
// Ensure an error version is created |
|
errorObserver := e.error_responses.With(labels) |
|
|
|
if !success { |
|
errorObserver.Observe(size) |
|
} |
|
} |
|
|
|
func (e *NginxExporter) Monitor() { |
|
tailConfig := tail.Config{ |
|
Follow: true, |
|
ReOpen: true, |
|
Poll: true, |
|
Location: &tail.SeekInfo{ |
|
Offset: 0, |
|
Whence: os.SEEK_END, |
|
}, |
|
} |
|
t, err := tail.TailFile(e.AccessLogPath, tailConfig) |
|
|
|
// Continue to retry every 5 seconds |
|
for ; err != nil; { |
|
t, err = tail.TailFile(e.AccessLogPath, tailConfig) |
|
|
|
if err != nil { |
|
time.Sleep(time.Second * 5) |
|
log.Println("Error starting nginx tail (retrying in 5 seconds)", err) |
|
} |
|
} |
|
|
|
for line := range t.Lines { |
|
go e.processLogLine(line.Text) |
|
} |
|
}
|
|
|