commit dca9f3dd7c3dc2b828174361f4be39a5a62ed73f Author: Justin Hawkins Date: Thu Jan 4 20:27:46 2024 +1030 first commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..cf85358 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# caddy_prometheus_exporter diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2e1388a --- /dev/null +++ b/go.mod @@ -0,0 +1,21 @@ +module github.com/tardisx/caddy_prometheus_exporter + +go 1.21.4 + +require ( + github.com/nxadm/tail v1.4.11 + github.com/prometheus/client_golang v1.18.0 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.45.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect + golang.org/x/sys v0.15.0 // indirect + google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a3de408 --- /dev/null +++ b/go.sum @@ -0,0 +1,33 @@ +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= +github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= +github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= +github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= +github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= +github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= diff --git a/main.go b/main.go new file mode 100644 index 0000000..2907494 --- /dev/null +++ b/main.go @@ -0,0 +1,133 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "log" + "log/slog" + "net/http" + "os" + + "github.com/nxadm/tail" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +type CaddyLogline struct { + Level string `json:"level"` + Ts float64 `json:"ts"` + Logger string `json:"logger"` + Msg string `json:"msg"` + Request struct { + RemoteIP string `json:"remote_ip"` + RemotePort string `json:"remote_port"` + ClientIP string `json:"client_ip"` + Proto string `json:"proto"` + Method string `json:"method"` + Host string `json:"host"` + URI string `json:"uri"` + Headers struct { + SecFetchSite []string `json:"Sec-Fetch-Site"` + Accept []string `json:"Accept"` + AcceptLanguage []string `json:"Accept-Language"` + Connection []string `json:"Connection"` + UpgradeInsecureRequests []string `json:"Upgrade-Insecure-Requests"` + SecFetchMode []string `json:"Sec-Fetch-Mode"` + UserAgent []string `json:"User-Agent"` + SecFetchDest []string `json:"Sec-Fetch-Dest"` + AcceptEncoding []string `json:"Accept-Encoding"` + } `json:"headers"` + } `json:"request"` + BytesRead int `json:"bytes_read"` + UserID string `json:"user_id"` + Duration float64 `json:"duration"` + Size int `json:"size"` + Status int `json:"status"` + RespHeaders struct { + Server []string `json:"Server"` + Etag []string `json:"Etag"` + ContentType []string `json:"Content-Type"` + LastModified []string `json:"Last-Modified"` + AcceptRanges []string `json:"Accept-Ranges"` + ContentLength []string `json:"Content-Length"` + } `json:"resp_headers"` +} + +func main() { + requestsCounter := prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: "", + Subsystem: "", + Name: "requests", + Help: "", + ConstLabels: map[string]string{}, + }, []string{"method", "status_code", "host"}) + + requestsDuration := prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: "", + Subsystem: "", + Name: "requests_duration", + Help: "", + ConstLabels: map[string]string{}, + }, []string{"method", "status_code", "host"}) + + requestsSize := prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: "", + Subsystem: "", + Name: "requests_size", + Help: "", + ConstLabels: map[string]string{}, + }, []string{"method", "status_code", "host"}) + + prometheus.MustRegister(requestsCounter, requestsDuration, requestsSize) + + http.Handle("/metrics", promhttp.Handler()) + go func() { + log.Fatal(http.ListenAndServe("127.0.0.1:8191", nil)) + }() + + i := len(os.Args) + if i < 2 { + panic("need names") + } + agg := make(chan *tail.Line) + + for _, fn := range os.Args[1:] { + slog.With("file", fn).Info("opening") + t, err := tail.TailFile(fn, tail.Config{ + Follow: true, + ReOpen: true, + CompleteLines: true, + Location: &tail.SeekInfo{ + Offset: 0, + Whence: io.SeekEnd, + }, + }) + if err != nil { + panic(err) + } + go func(c chan *tail.Line) { + for msg := range c { + agg <- msg + } + }(t.Lines) + } + + for line := range agg { + js := CaddyLogline{} + err := json.Unmarshal([]byte(line.Text), &js) + if err != nil { + slog.With(err).Error("could not unmarshal") + continue + } + + if js.Msg != "handled request" { + continue + } + + requestsCounter.WithLabelValues(js.Request.Method, fmt.Sprint(js.Status), js.Request.Host).Inc() + requestsDuration.WithLabelValues(js.Request.Method, fmt.Sprint(js.Status), js.Request.Host).Add(js.Duration) + requestsSize.WithLabelValues(js.Request.Method, fmt.Sprint(js.Status), js.Request.Host).Add(float64(js.Size)) + + } +}