Skip to content

Commit

Permalink
Adding prometheus output with pushgateway option
Browse files Browse the repository at this point in the history
  • Loading branch information
jgarnier committed Nov 4, 2022
1 parent 5d52c15 commit 3f61439
Show file tree
Hide file tree
Showing 12 changed files with 429 additions and 5 deletions.
2 changes: 1 addition & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ func rootRunE(cmd *cobra.Command, args []string) error {
}
p := parser.NewParser(cfg.Rules, ignorer)

print, err := printer.NewPrinter(outputName, output.Stdout)
print, err := printer.NewPrinter(outputName, output.Stdout, cfg)
if err != nil {
return err
}
Expand Down
45 changes: 45 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,51 @@ Format used to populate results into the popular [SonarQube](https://www.sonarqu
!!! note
`<sonarqubeseverity>` is mapped from severity, such that an error in `woke` is translated to a `MAJOR`, warning to a `MINOR`, and info to `INFO`

### Prometheus

!!! example ""
`woke -o prometheus`

Outputs the results as a [`prometheus metrics`](https://prometheus.io/docs/practices/naming/) one per line.

#### Structure

!!! info inline end
Actual output from woke will be consolidated Prometheus metrics. Having this output as a file could be used after through [textfile-collector / node exporter](https://github.com/prometheus/node_exporter#textfile-collector)

```text
woke_result{file="CHANGELOG.md:62:24-30", term="master", repo="sample", something="else"} 1
woke_result{file="README.md:1:241-247", term="master", repo="sample", something="else"} 1
```

If pushgateway parameter is provided, it will push woke result as [`prometheus metrics`](https://prometheus.io/docs/practices/naming/) to a [`pushgateway instance`](https://prometheus.io/docs/practices/pushing/).

#### Configuration

As per the sample output above, this will put labels in the prometheus metric with:

- file: same as previous outputs format, file name with line and start/end position
- term: this is currently using the rule name
- repo & something: this is based on a list of labels

Here is an example of the configuration file:

```yaml
rules:
- name: master
terms:
- master
alternatives:
- primary
- main
outputs:
prometheus:
labels:
repo: sample
something: else
pushgateway: "http://pushgateway.example.org:9091"
```
### Checkstyle
!!! example ""
Expand Down
9 changes: 9 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,28 @@ require (

require (
github.com/acomagu/bufpipe v1.0.3 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/go-git/gcfg v1.5.0 // indirect
github.com/go-git/go-git/v5 v5.4.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.13.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/spf13/afero v1.8.2 // indirect
github.com/spf13/cast v1.5.0 // indirect
Expand All @@ -42,6 +50,7 @@ require (
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 // indirect
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/ini.v1 v1.66.4 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
125 changes: 124 additions & 1 deletion go.sum

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,21 @@ import (
"gopkg.in/yaml.v2"
)

type Prometheus struct {
Labels map[string]string `yaml:"labels,omitempty"`
PushgatewayURL string `yaml:"pushgateway"`
}

// Config contains a list of rules
type Config struct {
Rules []*rule.Rule `yaml:"rules"`
IgnoreFiles []string `yaml:"ignore_files"`
SuccessExitMessage *string `yaml:"success_exit_message"`
IncludeNote bool `yaml:"include_note"`
ExcludeCategories []string `yaml:"exclude_categories"`
Outputs struct {
Prometheus *Prometheus `yaml:"prometheus"`
} `yaml:"outputs"`
}

// NewConfig returns a new Config
Expand Down
10 changes: 9 additions & 1 deletion pkg/printer/printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/get-woke/woke/pkg/result"

env "github.com/caitlinelfring/go-env-default"
config "github.com/get-woke/woke/pkg/config"
"github.com/rs/zerolog/log"
)

Expand Down Expand Up @@ -35,6 +36,10 @@ const (
// https://docs.sonarqube.org/latest/analysis/generic-issue/
OutFormatSonarQube = "sonarqube"

// OutFormatPrometheus outputs in prometheus format
// https://prometheus.io/docs/instrumenting/writing_exporters/
OutFormatPrometheus = "prometheus"

// OutFormatCheckstyle outputs in checkstyle format.
// https://github.com/checkstyle/checkstyle
OutFormatCheckstyle = "checkstyle"
Expand All @@ -47,14 +52,15 @@ var OutFormats = []string{
OutFormatGitHubActions,
OutFormatJSON,
OutFormatSonarQube,
OutFormatPrometheus,
OutFormatCheckstyle,
}

// OutFormatsString is all OutFormats, as a comma-separated string
var OutFormatsString = strings.Join(OutFormats, ",")

// NewPrinter returns a valid new Printer from a string, or an error if the printer is invalid
func NewPrinter(f string, w io.Writer) (Printer, error) {
func NewPrinter(f string, w io.Writer, c *config.Config) (Printer, error) {
var p Printer
switch f {
case OutFormatText:
Expand All @@ -67,6 +73,8 @@ func NewPrinter(f string, w io.Writer) (Printer, error) {
p = NewJSON(w)
case OutFormatSonarQube:
p = NewSonarQube(w)
case OutFormatPrometheus:
p = NewPrometheus(w, c.Outputs.Prometheus)
case OutFormatCheckstyle:
p = NewCheckstyle(w)
default:
Expand Down
5 changes: 3 additions & 2 deletions pkg/printer/printer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"io"
"testing"

config "github.com/get-woke/woke/pkg/config"
"github.com/stretchr/testify/assert"
)

Expand All @@ -21,11 +22,11 @@ func TestCreatePrinter(t *testing.T) {
}

for _, test := range tests {
p, err := NewPrinter(test.OutFormat, io.Discard)
p, err := NewPrinter(test.OutFormat, io.Discard, new(config.Config))
assert.NoError(t, err)
assert.IsType(t, test.Type, p)
}

_, err := NewPrinter("invalid-printer", io.Discard)
_, err := NewPrinter("invalid-printer", io.Discard, new(config.Config))
assert.Errorf(t, err, "invalid-printer is not a valid printer type")
}
93 changes: 93 additions & 0 deletions pkg/printer/prometheus.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/**
* Copyright 2022 Cisco and its affiliates
* All rights reserved.
**/

package printer

import (
"fmt"
"io"

config "github.com/get-woke/woke/pkg/config"
"github.com/get-woke/woke/pkg/result"
"github.com/prometheus/client_golang/prometheus"
push "github.com/prometheus/client_golang/prometheus/push"
"github.com/rs/zerolog/log"
)

// Prometheus is a output format with prometheus metrics
type Prometheus struct {
writer io.Writer
labels map[string]string
pushgatewayURL string
}

// NewPrometheus returns a Prometheus Printer with color optionally disabled
func NewPrometheus(w io.Writer, c *config.Prometheus) *Prometheus {
return &Prometheus{
writer: w,
labels: c.Labels,
pushgatewayURL: c.PushgatewayURL,
}
}

func (t *Prometheus) PrintSuccessExitMessage() bool {
return true
}

// Print prints the file results or send it to pushgateway if URL provided
func (t *Prometheus) Print(fs *result.FileResults) error {

for _, r := range fs.Results {
pos := fmt.Sprintf("%d:%d-%d",
r.GetStartPosition().Line,
r.GetStartPosition().Column,
r.GetEndPosition().Column)

labelString := ""

if len(t.labels) > 0 {
first := true
for k, v := range t.labels {
if first {
labelString += ", "
first = false
}
labelString += k + "=" + v + ", "
}
labelString = labelString[:len(labelString)-2]
}
fmt.Fprintf(t.writer, "woke_result{file=\"%s:%s\", term=\"%s\"%s} 1 \n",
fs.Filename, pos, r.GetRuleName(), labelString)

if t.pushgatewayURL != "" {
pusher := push.New(t.pushgatewayURL, "woke")
woke_result := prometheus.NewGauge(prometheus.GaugeOpts{
Name: "woke_result",
Help: "Inclusive woke result",
})
for k, v := range t.labels {
pusher = pusher.Grouping(k, v)
}
pusher = pusher.Grouping("term", r.GetRuleName()).
Grouping("instance", fs.Filename+":"+pos).
Collector(woke_result)

if err := pusher.Push(); err != nil {
log.Error().Err(err).Msg("Could not push woke_result to Pushgateway")
} else {
log.Debug().Msg("push woke_result to Pushgateway done")
}
}

}

return nil
}

func (t *Prometheus) Start() {
}

func (t *Prometheus) End() {
}
48 changes: 48 additions & 0 deletions pkg/printer/prometheus_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Copyright 2022 Cisco and its affiliates
* All rights reserved.
**/

package printer

import (
"bytes"
"fmt"
config "github.com/get-woke/woke/pkg/config"
"github.com/stretchr/testify/assert"
"testing"
)

func TestPrometheus_Print(t *testing.T) {
buf := new(bytes.Buffer)
p := NewPrometheus(buf, new(config.Config))
res := generateFileResult()
assert.NoError(t, p.Print(res))
got := buf.String()
fmt.Printf("buf: %s", buf)
fmt.Printf("res: %s", res.Results[0].String())
expected := fmt.Sprintf("woke_result{file=\"foo.txt:1:6-15\", term=\"%s\"} 1 \n", res.Results[0].GetRuleName())
assert.Equal(t, expected, got)
}

func TestPrometheus_Start(t *testing.T) {
buf := new(bytes.Buffer)
p := NewPrometheus(buf, new(config.Config))
p.Start()
got := buf.String()
assert.Equal(t, ``, got)
}

func TestPrometheus_End(t *testing.T) {
buf := new(bytes.Buffer)
p := NewPrometheus(buf, new(config.Config))
p.End()
got := buf.String()
assert.Equal(t, ``, got)
}

func TestPrometheus_PrintSuccessExitMessage(t *testing.T) {
buf := new(bytes.Buffer)
p := NewPrometheus(buf, new(config.Config))
assert.Equal(t, true, p.PrintSuccessExitMessage())
}
Loading

0 comments on commit 3f61439

Please sign in to comment.