diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml
index 249d4ec..ea60d6d 100644
--- a/.github/workflows/goreleaser.yml
+++ b/.github/workflows/goreleaser.yml
@@ -13,9 +13,10 @@ jobs:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- - uses: actions/setup-go@v3
+ - uses: actions/setup-go@v4
with:
go-version-file: go.mod
+ cache: false
- uses: docker/setup-qemu-action@v2
- uses: docker/setup-buildx-action@v2
- uses: docker/login-action@v1
diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index f6b6fa8..280696e 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -13,9 +13,10 @@ jobs:
with:
fetch-depth: 0
- name: Set up Go
- uses: actions/setup-go@v3
+ uses: actions/setup-go@v4
with:
go-version-file: go.mod
+ cache: false
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml
index 5ff87da..d993944 100644
--- a/.github/workflows/pr.yml
+++ b/.github/workflows/pr.yml
@@ -7,13 +7,14 @@ jobs:
test:
strategy:
matrix:
- go-version: [1.19.x, 1.20.x]
+ go-version: [1.20.x]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- - uses: actions/setup-go@v3
+ - uses: actions/setup-go@v4
with:
go-version: ${{ matrix.go-version }}
+ cache: false
- run: go test ./...
release_test:
@@ -21,9 +22,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- - uses: actions/setup-go@v3
+ - uses: actions/setup-go@v4
with:
go-version-file: go.mod
+ cache: false
- uses: docker/setup-qemu-action@v2
- uses: docker/setup-buildx-action@v2
- run: docker buildx ls
diff --git a/Makefile b/Makefile
index cfda49b..9576c67 100644
--- a/Makefile
+++ b/Makefile
@@ -1,19 +1,15 @@
-GO_VERSION:=1.16
-
.PHONY: build
build:
- goreleaser build --snapshot --single-target --rm-dist
+ goreleaser build --snapshot --single-target --clean
+
+.PHONY: run-dev
+run-dev:
+ go run dnsbl_exporter.go --log.debug
.PHONY: snapshot
snapshot:
- goreleaser build --snapshot --rm-dist
+ goreleaser build --snapshot --clean
.PHONY: test
test:
- docker run \
- -it \
- --rm \
- -v $(CURDIR):/src/github.com/Luzilla/dnsbl_exporter \
- -w /src/github.com/Luzilla/dnsbl_exporter \
- golang:$(GO_VERSION) \
- sh -c "go mod download && go test ./..."
+ act "pull_request" -j test
diff --git a/app/app.go b/app/app.go
index 5a6c3b3..a7606a7 100644
--- a/app/app.go
+++ b/app/app.go
@@ -1,132 +1,173 @@
package app
import (
+ "errors"
+ "fmt"
+ "io"
"net/http"
"os"
+ "strings"
- "github.com/Luzilla/dnsbl_exporter/collector"
"github.com/Luzilla/dnsbl_exporter/config"
+ "github.com/Luzilla/dnsbl_exporter/internal/index"
+ "github.com/Luzilla/dnsbl_exporter/internal/metrics"
+ "github.com/Luzilla/dnsbl_exporter/internal/prober"
+ "github.com/Luzilla/dnsbl_exporter/internal/setup"
"github.com/prometheus/client_golang/prometheus"
- "github.com/prometheus/client_golang/prometheus/promhttp"
- "github.com/urfave/cli"
-
- log "github.com/sirupsen/logrus"
+ "github.com/urfave/cli/v2"
+ "golang.org/x/exp/slog"
)
type DNSBLApp struct {
App *cli.App
}
+var (
+ appName, appVersion, appPath string
+ resolver string
+)
+
// NewApp ...
func NewApp(name string, version string) DNSBLApp {
-
- cli.VersionFlag = cli.BoolFlag{
- Name: "version, V",
- Usage: "Print the version information.",
- }
-
- app := cli.NewApp()
- app.Name = name
- app.Version = version
- app.Flags = []cli.Flag{
- cli.StringFlag{
- Name: "config.dns-resolver",
- Value: "127.0.0.1:53",
- Usage: "IP address[:port] of the resolver to use.",
- EnvVar: "DNSBL_EXP_RESOLVER",
+ appName = name
+ appVersion = version
+
+ a := cli.NewApp()
+ a.Name = appName
+ a.Version = appVersion
+ a.Flags = []cli.Flag{
+ &cli.StringFlag{
+ Name: "config.dns-resolver",
+ Value: "127.0.0.1:53",
+ Usage: "IP address[:port] of the resolver to use.",
+ EnvVars: []string{"DNSBL_EXP_RESOLVER"},
+ Destination: &resolver,
},
- cli.StringFlag{
- Name: "config.rbls",
- Value: "./rbls.ini",
- Usage: "Configuration file which contains RBLs",
- EnvVar: "DNSBL_EXP_RBLS",
+ &cli.StringFlag{
+ Name: "config.rbls",
+ Value: "./rbls.ini",
+ Usage: "Configuration file which contains RBLs",
+ EnvVars: []string{"DNSBL_EXP_RBLS"},
},
- cli.StringFlag{
- Name: "config.targets",
- Value: "./targets.ini",
- Usage: "Configuration file which contains the targets to check.",
- EnvVar: "DNSBL_EXP_TARGETS",
+ &cli.StringFlag{
+ Name: "config.targets",
+ Value: "./targets.ini",
+ Usage: "Configuration file which contains the targets to check.",
+ EnvVars: []string{"DNSBL_EXP_TARGETS"},
},
- cli.StringFlag{
- Name: "web.listen-address",
- Value: ":9211",
- Usage: "Address to listen on for web interface and telemetry.",
- EnvVar: "DNSBL_EXP_LISTEN",
+ &cli.StringFlag{
+ Name: "web.listen-address",
+ Value: ":9211",
+ Usage: "Address to listen on for web interface and telemetry.",
+ EnvVars: []string{"DNSBL_EXP_LISTEN"},
},
- cli.StringFlag{
- Name: "web.telemetry-path",
- Value: "/metrics",
- Usage: "Path under which to expose metrics.",
+ &cli.StringFlag{
+ Name: "web.telemetry-path",
+ Value: "/metrics",
+ Usage: "Path under which to expose metrics.",
+ Destination: &appPath,
+ Action: func(cCtx *cli.Context, v string) error {
+ if !strings.HasPrefix(v, "/") {
+ return cli.Exit("Missing / to prefix the path: --web.telemetry-path", 2)
+ }
+ return nil
+ },
},
- cli.BoolFlag{
+ &cli.BoolFlag{
Name: "web.include-exporter-metrics",
Usage: "Include metrics about the exporter itself (promhttp_*, process_*, go_*).",
+ Value: false,
},
- cli.BoolFlag{
+ &cli.BoolFlag{
Name: "log.debug",
Usage: "Enable more output in the logs, otherwise INFO.",
+ Value: false,
},
- cli.StringFlag{
+ &cli.StringFlag{
Name: "log.output",
Value: "stdout",
Usage: "Destination of our logs: stdout, stderr",
+ Action: func(cCtx *cli.Context, v string) error {
+ if v != "stdout" && v != "stderr" {
+ return cli.Exit("We currently support only stdout and stderr: --log.output", 2)
+ }
+ return nil
+ },
},
}
return DNSBLApp{
- App: app,
+ App: a,
}
}
-func (app *DNSBLApp) Bootstrap() {
- app.App.Action = func(ctx *cli.Context) error {
+func (a *DNSBLApp) Bootstrap() {
+ a.App.Action = func(ctx *cli.Context) error {
// setup logging
+ fmt.Println("VERSION: " + appVersion)
+ handler := &slog.HandlerOptions{}
+ var writer io.Writer
+
+ if ctx.Bool("log.debug") {
+ handler.Level = slog.LevelDebug
+ }
+
switch ctx.String("log.output") {
case "stdout":
- log.SetOutput(os.Stdout)
+ writer = os.Stdout
case "stderr":
- log.SetOutput(os.Stderr)
- default:
- cli.ShowAppHelp(ctx)
- return cli.NewExitError("We currently support only stdout and stderr: --log.output", 2)
+ writer = os.Stderr
}
- if ctx.Bool("log.debug") {
- log.SetLevel(log.DebugLevel)
+
+ log := slog.New(handler.NewTextHandler(writer))
+
+ c := config.Config{
+ Logger: log.With("area", "config"),
}
- cfgRbls, err := config.LoadFile(ctx.String("config.rbls"), "rbl")
+ cfgRbls, err := c.LoadFile(ctx.String("config.rbls"))
if err != nil {
return err
}
- cfgTargets, err := config.LoadFile(ctx.String("config.targets"), "targets")
+ err = c.ValidateConfig(cfgRbls, "rbl")
+ if err != nil {
+ return fmt.Errorf("unable to load the rbls from the config: %w", err)
+ }
+
+ cfgTargets, err := c.LoadFile(ctx.String("config.targets"))
if err != nil {
return err
}
- http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
- w.Write([]byte(`
-
` + app.App.Name + `
-
- ` + app.App.Name + ` @ ` + app.App.Version + `
- Metrics
- Code on Github
-
- `))
- })
+ err = c.ValidateConfig(cfgTargets, "targets")
+ if err != nil {
+ if !errors.Is(err, config.ErrNoServerEntries) && !errors.Is(err, config.ErrNoSuchSection) {
+ return err
+ }
+ log.Info("starting exporter without targets — check the /prober endpoint or correct the .ini file")
+ }
+
+ iHandler := index.IndexHandler{
+ Name: appName,
+ Version: appVersion,
+ Path: appPath,
+ }
+
+ http.HandleFunc("/", iHandler.Handler)
- rbls := config.GetRbls(cfgRbls)
- targets := config.GetTargets(cfgTargets)
+ rbls := c.GetRbls(cfgRbls)
+ targets := c.GetTargets(cfgTargets)
- registry := createRegistry()
+ registry := setup.CreateRegistry()
- collector := createCollector(rbls, targets, ctx.String("config.dns-resolver"))
- registry.MustRegister(collector)
+ rblCollector := setup.CreateCollector(rbls, targets, resolver, log.With("area", "metrics"))
+ registry.MustRegister(rblCollector)
- registryExporter := createRegistry()
+ registryExporter := setup.CreateRegistry()
if ctx.Bool("web.include-exporter-metrics") {
- log.Infoln("Exposing exporter metrics")
+ log.Info("Exposing exporter metrics")
registryExporter.MustRegister(
prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{}),
@@ -134,20 +175,21 @@ func (app *DNSBLApp) Bootstrap() {
)
}
- handler := promhttp.HandlerFor(
- prometheus.Gatherers{
- registry,
- registryExporter,
- },
- promhttp.HandlerOpts{
- ErrorHandling: promhttp.ContinueOnError,
- Registry: registry,
- },
- )
+ mHandler := metrics.MetricsHandler{
+ Registry: registry,
+ RegistryExporter: registryExporter,
+ }
- http.Handle(ctx.String("web.telemetry-path"), handler)
+ http.Handle(ctx.String("web.telemetry-path"), mHandler.Handler())
+
+ pHandler := prober.ProberHandler{
+ Resolver: resolver,
+ Rbls: rbls,
+ Logger: log.With("area", "prober"),
+ }
+ http.Handle("/prober", pHandler)
- log.Infoln("Starting on: ", ctx.String("web.listen-address"))
+ log.Info("Starting on: " + ctx.String("web.listen-address"))
err = http.ListenAndServe(ctx.String("web.listen-address"), nil)
if err != nil {
return err
@@ -157,14 +199,6 @@ func (app *DNSBLApp) Bootstrap() {
}
}
-func (app *DNSBLApp) Run(args []string) error {
- return app.App.Run(args)
-}
-
-func createCollector(rbls []string, targets []string, resolver string) *collector.RblCollector {
- return collector.NewRblCollector(rbls, targets, resolver)
-}
-
-func createRegistry() *prometheus.Registry {
- return prometheus.NewRegistry()
+func (a *DNSBLApp) Run(args []string) error {
+ return a.App.Run(args)
}
diff --git a/collector/collector.go b/collector/collector.go
index 5195dd9..815d1c0 100644
--- a/collector/collector.go
+++ b/collector/collector.go
@@ -2,12 +2,13 @@ package collector
import (
"sync"
+ "time"
"github.com/Luzilla/dnsbl_exporter/pkg/dns"
"github.com/Luzilla/dnsbl_exporter/pkg/rbl"
x "github.com/miekg/dns"
"github.com/prometheus/client_golang/prometheus"
- log "github.com/sirupsen/logrus"
+ "golang.org/x/exp/slog"
)
const namespace = "luzilla"
@@ -19,45 +20,61 @@ type RblCollector struct {
blacklistedMetric *prometheus.Desc
errorsMetrics *prometheus.Desc
listedMetric *prometheus.Desc
+ targetsMetric *prometheus.Desc
+ durationMetric *prometheus.Desc
rbls []string
resolver string
targets []string
+ logger *slog.Logger
}
-func buildFQName(metric string) string {
+func BuildFQName(metric string) string {
return prometheus.BuildFQName(namespace, subsystem, metric)
}
// NewRblCollector ... creates the collector
-func NewRblCollector(rbls []string, targets []string, resolver string) *RblCollector {
+func NewRblCollector(rbls []string, targets []string, resolver string, logger *slog.Logger) *RblCollector {
return &RblCollector{
configuredMetric: prometheus.NewDesc(
- buildFQName("used"),
+ BuildFQName("used"),
"The number of RBLs to check IPs against (configured via rbls.ini)",
nil,
nil,
),
blacklistedMetric: prometheus.NewDesc(
- buildFQName("ips_blacklisted"),
+ BuildFQName("ips_blacklisted"),
"Blacklisted IPs",
[]string{"rbl", "ip", "hostname"},
nil,
),
errorsMetrics: prometheus.NewDesc(
- buildFQName("errors"),
+ BuildFQName("errors"),
"The number of errors which occurred testing the RBLs",
[]string{"rbl"},
nil,
),
listedMetric: prometheus.NewDesc(
- buildFQName("listed"),
+ BuildFQName("listed"),
"The number of listings in RBLs (this is bad)",
[]string{"rbl"},
nil,
),
+ targetsMetric: prometheus.NewDesc(
+ BuildFQName("targets"),
+ "The number of targets that are being probed (configured via targets.ini or ?target=)",
+ nil,
+ nil,
+ ),
+ durationMetric: prometheus.NewDesc(
+ BuildFQName("duration"),
+ "The scrape's duration (in seconds)",
+ nil,
+ nil,
+ ),
rbls: rbls,
resolver: resolver,
targets: targets,
+ logger: logger,
}
}
@@ -78,23 +95,27 @@ func (c *RblCollector) Collect(ch chan<- prometheus.Metric) {
float64(len(c.rbls)),
)
+ ch <- prometheus.MustNewConstMetric(
+ c.targetsMetric,
+ prometheus.GaugeValue,
+ float64(len(hosts)),
+ )
+
+ start := time.Now()
+
// this should be a map of blacklist and a counter (for listings)
var listed sync.Map
// iterate over hosts -> resolve to ip, check
for _, host := range hosts {
+ logger := c.logger.With("target", host)
- log.Debugln("Checking ...", host)
+ logger.Debug("Starting check")
- r := rbl.New(dns.New(new(x.Client), c.resolver))
+ r := rbl.New(dns.New(new(x.Client), c.resolver, logger), logger)
r.Update(host, c.rbls)
for _, result := range r.Results {
- // this is an "error" from the RBL
- if result.Error {
- log.Errorln(result.Text)
- }
-
metricValue := 0
val, _ := listed.LoadOrStore(result.Rbl, 0)
@@ -103,9 +124,13 @@ func (c *RblCollector) Collect(ch chan<- prometheus.Metric) {
listed.Store(result.Rbl, val.(int)+1)
}
+ logger.Debug(result.Rbl+" listed?", slog.Int("v", metricValue))
+
labelValues := []string{result.Rbl, result.Address, host}
+ // this is an "error" from the RBL
if result.Error {
+ logger.Error(result.Text)
ch <- prometheus.MustNewConstMetric(
c.errorsMetrics,
prometheus.GaugeValue,
@@ -123,6 +148,8 @@ func (c *RblCollector) Collect(ch chan<- prometheus.Metric) {
}
}
+ c.logger.Debug("building listed metric")
+
for _, rbl := range c.rbls {
val, _ := listed.LoadOrStore(rbl, 0)
ch <- prometheus.MustNewConstMetric(
@@ -133,4 +160,12 @@ func (c *RblCollector) Collect(ch chan<- prometheus.Metric) {
)
}
+ c.logger.Debug("finished")
+
+ ch <- prometheus.MustNewConstMetric(
+ c.durationMetric,
+ prometheus.GaugeValue,
+ time.Since(start).Seconds(),
+ )
+
}
diff --git a/config/config.go b/config/config.go
index dd95f5e..fee1992 100644
--- a/config/config.go
+++ b/config/config.go
@@ -3,19 +3,26 @@ package config
import (
"errors"
- log "github.com/sirupsen/logrus"
+ "golang.org/x/exp/slog"
"gopkg.in/ini.v1"
)
+var ErrNoSuchSection = errors.New("section does not exist")
+var ErrNoServerEntries = errors.New("please add a few server= entries to your .ini")
+
+type Config struct {
+ Logger *slog.Logger
+}
+
// ValidateConfig validate the supplied configuration, e.g. check if we have "server="
-func ValidateConfig(cfg *ini.File, section string) error {
+func (c *Config) ValidateConfig(cfg *ini.File, section string) error {
configSection, err := cfg.GetSection(section)
if err != nil {
- return errors.New("Section does not exists")
+ return ErrNoSuchSection
}
if !configSection.HasKey("server") {
- return errors.New("Please add a few server= entries to your rbls.ini")
+ return ErrNoServerEntries
}
return nil
@@ -31,28 +38,23 @@ func loadConfig(path string) (*ini.File, error) {
}
// GetRbls returns all rbls from the config
-func GetRbls(cfg *ini.File) []string {
+func (c *Config) GetRbls(cfg *ini.File) []string {
return cfg.Section("rbl").Key("server").ValueWithShadows()
}
// GetTargets returns all targets from the config
-func GetTargets(cfg *ini.File) []string {
+func (c *Config) GetTargets(cfg *ini.File) []string {
return cfg.Section("targets").Key("server").ValueWithShadows()
}
// LoadFile ...
-func LoadFile(path string, section string) (*ini.File, error) {
- log.Debugln("Loading configuration...", path)
+func (c *Config) LoadFile(path string) (*ini.File, error) {
+ c.Logger.Debug("Loading configuration file: " + path)
cfg, err := loadConfig(path)
if err != nil {
return nil, err
}
- err = ValidateConfig(cfg, section)
- if err != nil {
- return nil, err
- }
-
return cfg, err
}
diff --git a/config/config_test.go b/config/config_test.go
index 2bbdb09..f48bc7d 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -1,53 +1,64 @@
-package config
+package config_test
import (
- "errors"
+ "os"
"testing"
+
+ "github.com/Luzilla/dnsbl_exporter/config"
+ "github.com/stretchr/testify/assert"
+ "golang.org/x/exp/slog"
)
func TestLoadConfig(t *testing.T) {
t.Parallel()
tests := []struct {
- File string
- Key string
- Error error
+ file string
+ key string
+ success bool
}{
{
- File: "./../targets.ini",
- Key: "targets",
- Error: nil,
- },
- {
- File: "./../rbls.ini",
- Key: "rbl",
- Error: nil,
+ file: "./../targets.ini",
+ key: "targets",
+ success: true,
},
{
- File: "./does-not-exists.ini",
- Key: "foo",
- Error: errors.New("Section does not exists"),
+ file: "./../rbls.ini",
+ key: "rbl",
+ success: true,
},
{
- File: "./../targets.ini",
- Key: "blah",
- Error: errors.New("Section does not exists"),
+ file: "./does-not-exists.ini",
+ key: "foo",
+ success: false,
},
}
for _, tt := range tests {
- tt := tt
- t.Run(tt.File, func(t *testing.T) {
- _, err := LoadFile(tt.File, tt.Key)
- if tt.Error == nil {
- if err != nil {
- t.Errorf("Could not load '%s' with key '%s': %s", tt.File, tt.Key, err)
- }
+ tc := tt
+ t.Run(tc.file, func(t *testing.T) {
+ c := &config.Config{
+ Logger: slog.New(slog.NewTextHandler(os.Stderr)),
+ }
+ _, err := c.LoadFile(tc.file)
+ if tc.success {
+ assert.NoError(t, err, "tc: "+tc.file)
} else {
- if err == nil {
- t.Errorf("Expected error for: '%s', but got none.", tt.File)
- }
+ assert.Error(t, err, "tc: "+tc.file)
}
})
}
}
+
+func TestValidateConfig(t *testing.T) {
+ c := &config.Config{
+ Logger: slog.New(slog.NewTextHandler(os.Stderr)),
+ }
+
+ cfg, err := c.LoadFile("./../targets.ini")
+ assert.NoError(t, err)
+
+ // ensure we return an error when the config section does not exist
+ err = c.ValidateConfig(cfg, "blah")
+ assert.Error(t, err)
+}
diff --git a/dnsbl_exporter.go b/dnsbl_exporter.go
index af51dc1..35917dd 100644
--- a/dnsbl_exporter.go
+++ b/dnsbl_exporter.go
@@ -1,17 +1,15 @@
package main
import (
+ "fmt"
"os"
"github.com/Luzilla/dnsbl_exporter/app"
-
- log "github.com/sirupsen/logrus"
)
// The following are customized during build
var exporterName string = "dnsbl-exporter"
var exporterVersion string
-var exporterRev string
func main() {
dnsbl := app.NewApp(exporterName, exporterVersion)
@@ -19,7 +17,8 @@ func main() {
err := dnsbl.Run(os.Args)
if err != nil {
- log.Fatal(err)
+ fmt.Println("error: " + err.Error())
+ os.Exit(1)
}
}
diff --git a/go.mod b/go.mod
index 4651699..627f6d0 100644
--- a/go.mod
+++ b/go.mod
@@ -1,14 +1,15 @@
module github.com/Luzilla/dnsbl_exporter
-go 1.19
+go 1.20
require github.com/prometheus/client_golang v1.15.1
require (
github.com/Luzilla/godnsbl v1.0.0
github.com/miekg/dns v1.1.54
- github.com/sirupsen/logrus v1.9.0
- github.com/urfave/cli v1.22.13
+ github.com/stretchr/testify v1.8.2
+ github.com/urfave/cli/v2 v2.25.1
+ golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53
gopkg.in/ini.v1 v1.67.0
)
@@ -16,15 +17,21 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
+ github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/protobuf v1.5.3 // indirect
+ github.com/kr/text v0.2.0 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.42.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect
+ github.com/rogpeppe/go-internal v1.10.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
+ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
golang.org/x/mod v0.7.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/tools v0.3.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/go.sum b/go.sum
index 34d8a94..a8be19d 100644
--- a/go.sum
+++ b/go.sum
@@ -1,4 +1,3 @@
-github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/Luzilla/godnsbl v1.0.0 h1:enXCzPjVI/u/bvfqcHDjnU9cSaK7u7UTE2UP7M5/h+Y=
github.com/Luzilla/godnsbl v1.0.0/go.mod h1:IMX8t+FUxO3OSwt55zgpYtetfYD07Sfg+Z9YAeFHHfs=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -7,6 +6,7 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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=
@@ -17,6 +17,9 @@ github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
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/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/miekg/dns v1.1.54 h1:5jon9mWcb0sFJGpnI99tOMhCPyJ+RPVz5b63MQG0VWI=
@@ -31,27 +34,29 @@ github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI
github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
+github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
+github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
-github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
-github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-github.com/urfave/cli v1.22.13 h1:wsLILXG8qCJNse/qAgLNf23737Cx05GflHg/PJGe1Ok=
-github.com/urfave/cli v1.22.13/go.mod h1:VufqObjsMTF2BBwKawpx9R8eAneNEWhoO0yx8Vd+FkE=
+github.com/urfave/cli/v2 v2.25.1 h1:zw8dSP7ghX0Gmm8vugrs6q9Ku0wzweqPyshy+syu9Gw=
+github.com/urfave/cli/v2 v2.25.1/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
+github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
+github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
+golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 h1:5llv2sWeaMSnA3w2kS57ouQQ4pudlXrR0dCgw51QK9o=
+golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
-golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/tools v0.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM=
@@ -62,9 +67,9 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/internal/index/index.go b/internal/index/index.go
new file mode 100644
index 0000000..4923be2
--- /dev/null
+++ b/internal/index/index.go
@@ -0,0 +1,23 @@
+package index
+
+import (
+ "net/http"
+)
+
+type IndexHandler struct {
+ Name string
+ Version string
+ Path string
+}
+
+func (i IndexHandler) Handler(w http.ResponseWriter, r *http.Request) {
+ w.Write([]byte(`
+ ` + i.Name + `
+
+ ` + i.Name + ` @ ` + i.Version + `
+ Metrics
+ Prober (multi-target export pattern)
+ Code on Github
+
+ `))
+}
diff --git a/internal/index/index_test.go b/internal/index/index_test.go
new file mode 100644
index 0000000..cfc294b
--- /dev/null
+++ b/internal/index/index_test.go
@@ -0,0 +1,31 @@
+package index_test
+
+import (
+ "io"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/Luzilla/dnsbl_exporter/internal/index"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestIndexHandler(t *testing.T) {
+ iHandler := index.IndexHandler{
+ Name: "test",
+ Version: "1.2.3",
+ Path: "/something",
+ }
+
+ req := httptest.NewRequest(http.MethodGet, "/", nil)
+ w := httptest.NewRecorder()
+ iHandler.Handler(w, req)
+ res := w.Result()
+ defer res.Body.Close()
+ data, err := io.ReadAll(res.Body)
+ assert.NoError(t, err)
+
+ assert.Contains(t, string(data), "test")
+ assert.Contains(t, string(data), "1.2.3")
+ assert.Contains(t, string(data), "/something")
+}
diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go
new file mode 100644
index 0000000..6f7cc11
--- /dev/null
+++ b/internal/metrics/metrics.go
@@ -0,0 +1,26 @@
+package metrics
+
+import (
+ "net/http"
+
+ "github.com/prometheus/client_golang/prometheus"
+ "github.com/prometheus/client_golang/prometheus/promhttp"
+)
+
+type MetricsHandler struct {
+ Registry *prometheus.Registry
+ RegistryExporter *prometheus.Registry
+}
+
+func (m MetricsHandler) Handler() http.Handler {
+ return promhttp.HandlerFor(
+ prometheus.Gatherers{
+ m.Registry,
+ m.RegistryExporter,
+ },
+ promhttp.HandlerOpts{
+ ErrorHandling: promhttp.ContinueOnError,
+ Registry: m.Registry,
+ },
+ )
+}
diff --git a/internal/prober/prober.go b/internal/prober/prober.go
new file mode 100644
index 0000000..86a7964
--- /dev/null
+++ b/internal/prober/prober.go
@@ -0,0 +1,35 @@
+package prober
+
+import (
+ "net/http"
+
+ "github.com/Luzilla/dnsbl_exporter/internal/setup"
+ "github.com/prometheus/client_golang/prometheus/promhttp"
+ "golang.org/x/exp/slog"
+)
+
+type ProberHandler struct {
+ Resolver string
+ Rbls []string
+ Logger *slog.Logger
+}
+
+func (p ProberHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ if !r.URL.Query().Has("target") {
+ p.Logger.Error("missing ?target parameter")
+ http.Error(w, "missing ?target parameter", http.StatusBadRequest)
+ return
+ }
+
+ targets := make([]string, 0, 1)
+ targets = append(targets, r.URL.Query().Get("target"))
+
+ registry := setup.CreateRegistry()
+ collector := setup.CreateCollector(p.Rbls, targets, p.Resolver, p.Logger)
+ registry.MustRegister(collector)
+
+ h := promhttp.HandlerFor(registry, promhttp.HandlerOpts{
+ ErrorHandling: promhttp.ContinueOnError,
+ })
+ h.ServeHTTP(w, r)
+}
diff --git a/internal/setup/setup.go b/internal/setup/setup.go
new file mode 100644
index 0000000..ad02bbb
--- /dev/null
+++ b/internal/setup/setup.go
@@ -0,0 +1,15 @@
+package setup
+
+import (
+ "github.com/Luzilla/dnsbl_exporter/collector"
+ "github.com/prometheus/client_golang/prometheus"
+ "golang.org/x/exp/slog"
+)
+
+func CreateCollector(rbls []string, targets []string, resolver string, logger *slog.Logger) *collector.RblCollector {
+ return collector.NewRblCollector(rbls, targets, resolver, logger)
+}
+
+func CreateRegistry() *prometheus.Registry {
+ return prometheus.NewRegistry()
+}
diff --git a/pkg/dns/dns.go b/pkg/dns/dns.go
index dd4d9d3..bb3a5b0 100644
--- a/pkg/dns/dns.go
+++ b/pkg/dns/dns.go
@@ -4,18 +4,20 @@ import (
"net"
x "github.com/miekg/dns"
- log "github.com/sirupsen/logrus"
+ "golang.org/x/exp/slog"
)
type DNSUtil struct {
client *x.Client
resolver string
+ logger *slog.Logger
}
-func New(client *x.Client, resolver string) *DNSUtil {
+func New(client *x.Client, resolver string, logger *slog.Logger) *DNSUtil {
return &DNSUtil{
client: client,
resolver: resolver,
+ logger: logger,
}
}
@@ -31,7 +33,7 @@ func (d *DNSUtil) GetARecords(target string) ([]string, error) {
if err == nil && len(result.Answer) > 0 {
for _, ans := range result.Answer {
if t, ok := ans.(*x.A); ok {
- log.Debugf("We have an A-Record %s for %s", t.A.String(), target)
+ d.logger.Debug("We have an A-Record", slog.String("target", target), slog.String("v", t.A.String()))
list = append(list, t.A.String())
}
}
@@ -67,7 +69,7 @@ func (d *DNSUtil) makeQuery(msg *x.Msg) (*x.Msg, error) {
}
result, rt, err := d.client.Exchange(msg, net.JoinHostPort(host, port))
- log.Debugln("Roundtrip", rt) // fixme -> histogram
+ d.logger.Debug("Roundtrip", slog.Float64("v", rt.Seconds())) // fixme -> histogram
return result, err
}
diff --git a/pkg/rbl/rbl.go b/pkg/rbl/rbl.go
index f44c34c..e59ca13 100644
--- a/pkg/rbl/rbl.go
+++ b/pkg/rbl/rbl.go
@@ -7,7 +7,7 @@ import (
"github.com/Luzilla/dnsbl_exporter/pkg/dns"
"github.com/Luzilla/godnsbl"
- log "github.com/sirupsen/logrus"
+ "golang.org/x/exp/slog"
)
// Rblresult extends godnsbl and adds RBL name
@@ -25,13 +25,15 @@ type Rblresult struct {
type Rbl struct {
Results []Rblresult
util *dns.DNSUtil
+ logger *slog.Logger
}
// NewRbl ... factory
-func New(util *dns.DNSUtil) Rbl {
+func New(util *dns.DNSUtil, logger *slog.Logger) Rbl {
var results []Rblresult
rbl := Rbl{
+ logger: logger,
util: util,
Results: results,
}
@@ -49,7 +51,7 @@ func (rbl *Rbl) Update(ip string, rbls []string) {
go func(source string, ip string) {
defer wg.Done()
- log.Debugf("Working blacklist %s (ip: %s)", source, ip)
+ rbl.logger.Debug("Next up", slog.String("rbl", source), slog.String("ip", ip))
results := rbl.lookup(source, ip)
if len(results) == 0 {
@@ -66,7 +68,7 @@ func (rbl *Rbl) Update(ip string, rbls []string) {
func (rbl *Rbl) query(ip string, blacklist string, result *Rblresult) {
result.Listed = false
- log.Debugf("Trying to query blacklist '%s' for %s", blacklist, ip)
+ rbl.logger.Debug("About to query RBL", slog.String("rbl", blacklist), slog.String("ip", ip))
lookup := fmt.Sprintf("%s.%s", ip, blacklist)
@@ -94,13 +96,13 @@ func (rbl *Rbl) lookup(rblList string, targetHost string) []Rblresult {
if addr == nil {
ipsA, err := rbl.util.GetARecords(targetHost)
if err != nil {
- log.Errorln(err)
+ rbl.logger.Error(err.Error())
return rbl.Results
}
ips = ipsA
} else {
- log.Infoln("We had an ip", addr.String())
+ rbl.logger.Info("We had an ip", slog.String("ip", addr.String()))
ips = append(ips, addr.String())
}
@@ -113,7 +115,7 @@ func (rbl *Rbl) lookup(rblList string, targetHost string) []Rblresult {
// attempt to "validate" the IP
ValidIPAddress := net.ParseIP(ip)
if ValidIPAddress == nil {
- log.Errorf("Unable to parse IP: %s", ip)
+ rbl.logger.Error("Unable to parse IP", slog.String("ip", ip))
continue
}
diff --git a/pkg/rbl/rbl_test.go b/pkg/rbl/rbl_test.go
index ad48f29..918b2d3 100644
--- a/pkg/rbl/rbl_test.go
+++ b/pkg/rbl/rbl_test.go
@@ -7,16 +7,15 @@ import (
"github.com/Luzilla/dnsbl_exporter/pkg/dns"
"github.com/Luzilla/dnsbl_exporter/pkg/rbl"
x "github.com/miekg/dns"
- log "github.com/sirupsen/logrus"
+ "golang.org/x/exp/slog"
)
func TestUpdate(t *testing.T) {
- log.SetLevel(log.DebugLevel)
- log.SetOutput(os.Stdout)
+ logger := slog.New(slog.NewTextHandler(os.Stderr))
- d := dns.New(new(x.Client), "0.0.0.0:53")
+ d := dns.New(new(x.Client), "0.0.0.0:53", logger)
- r := rbl.New(d)
+ r := rbl.New(d, logger)
r.Update("this.is.not.an.ip", []string{"cbl.abuseat.org"})
if len(r.Results) > 0 {