diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 60b5fd4b36d82ee584c6860f845661e461271c06..0000000000000000000000000000000000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,87 +0,0 @@ ---- -version: 2.1 - - -orbs: - prometheus: prometheus/prometheus@0.1.0 - -executors: - # Whenever the Go version is updated here, .travis.yml and .promu.yml - # should also be updated. - golang: - docker: - - image: circleci/golang:1.12 - -jobs: - test: - executor: golang - steps: - - prometheus/setup_environment - - run: make - - prometheus/store_artifact: - file: prometheus-gitlab-notifier - - publish_master: - description: | - Build and publish container images from the master branch. - docker: - - image: circleci/golang - steps: - - prometheus/setup_build_environment - - prometheus/publish_images: - login_variable: DOCKER_LOGIN - organization: fusakla - password_variable: DOCKER_PASSWORD - registry: docker.io - - publish_release: - description: Build and publish binaries and container images for a given release tag. - docker: - - image: circleci/golang - steps: - - prometheus/setup_build_environment - - run: promu crossbuild tarballs - - run: promu checksum .tarballs - - run: promu release .tarballs - - store_artifacts: - destination: releases - path: .tarballs - - prometheus/publish_release_images: - login_variable: DOCKER_LOGIN - organization: fusakla - password_variable: DOCKER_PASSWORD - registry: docker.io - - - -workflows: - version: 2 - prometheus-gitlab-notifier: - jobs: - - test: - filters: - tags: - only: /.*/ - - prometheus/build: - name: build - filters: - branches: - only: master - tags: - only: /.*/ - - publish_master: - requires: - - test - - build - filters: - branches: - only: master - - publish_release: - requires: - - test - - build - filters: - tags: - only: /^v[0-9]+(\.[0-9]+){2}(-.+|[^-.]*)$/ - branches: - ignore: /.*/ diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 0000000000000000000000000000000000000000..0746c5ed5e832b9c3144192899d19197051320c7 --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,25 @@ +name: Go + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.17 + + - name: Build + run: go build -v ./... + + - name: Test + run: go test -v ./... diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000000000000000000000000000000000000..fdedd053bbd127a9e632d87dbaf885db06e463be --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,40 @@ + +name: goreleaser + +on: + push: + tags: + - '*' + +permissions: + contents: write + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - + name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.17 + + - name: Login to Docker Hub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - + name: Run GoReleaser + uses: goreleaser/goreleaser-action@v2 + with: + distribution: goreleaser + version: latest + args: release --rm-dist + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index fc2e3ad8ef4aba8253f124cc6ee0c615916f1809..1df889f94811b96db73e00a207f4d941e8657773 100644 --- a/.gitignore +++ b/.gitignore @@ -13,10 +13,11 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out .build +dist/ # Dependency directories (remove the comment below to include it) # vendor/ /conf/*.token -/prometheus-gitlab-notifier +/prometheus-gitlab-notifier/cmd/prometheus-gitlab-notifier diff --git a/.golangci.yml b/.golangci.yml deleted file mode 100644 index f9abe8d6ffe60de80fce45af93b4ab8188d9ef3e..0000000000000000000000000000000000000000 --- a/.golangci.yml +++ /dev/null @@ -1,6 +0,0 @@ -service: - golangci-lint-version: 1.12.4 - -linters-settings: - errcheck: - exclude: errcheck_excludes.txt diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000000000000000000000000000000000000..be68c666256be5b2c164c80103abb3f0568c760b --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,39 @@ +# This is an example .goreleaser.yml file with some sensible defaults. +# Make sure to check the documentation at https://goreleaser.com +project_name: prometheus-gitlab-notifier +before: + hooks: + - go mod tidy + +builds: + - main: "./cmd/{{.ProjectName}}" + env: + - CGO_ENABLED=0 + goos: + - linux + +dockers: + - image_templates: + - "fusakla/{{.ProjectName}}:latest" + - "fusakla/{{.ProjectName}}:{{ .Tag }}" + - "fusakla/{{.ProjectName}}:v{{ .Major }}" + - "fusakla/{{.ProjectName}}:v{{ .Major }}.{{ .Minor }}" + +archives: + - replacements: + linux: Linux + amd64: x86_64 + +checksum: + name_template: 'checksums.txt' + +snapshot: + name_template: "{{ incpatch .Version }}-next" + +changelog: + sort: asc + filters: + exclude: + - '^docs:' + - '^test:' + - '^chore:' diff --git a/.promu.yml b/.promu.yml deleted file mode 100644 index 73df6c36c49716e0cdf690e70c727f1df7394958..0000000000000000000000000000000000000000 --- a/.promu.yml +++ /dev/null @@ -1,25 +0,0 @@ -go: - # Whenever the Go version is updated here, .travis.yml and - # .circle/config.yml should also be updated. - version: 1.12 -repository: - path: github.com/fusakla/prometheus-gitlab-notifier -build: - binaries: - - name: prometheus-gitlab-notifier - path: ./cmd/prometheus-gitlab-notifier - flags: -a -tags netgo - ldflags: | - -X github.com/fusakla/prometheus-gitlab-notifier/metrics.appVersion={{.Version}} - -X github.com/fusakla/prometheus-gitlab-notifier/metrics.gitRevision={{.Revision}} - -X github.com/fusakla/prometheus-gitlab-notifier/metrics.gitBranch={{.Branch}} - -X github.com/fusakla/prometheus-gitlab-notifier/metrics.gitTag={{.Version}} -tarball: - files: - - LICENSE - - NOTICE -crossbuild: - platforms: - - linux/amd64 - - darwin/amd64 - - windows/amd64 diff --git a/CHANGELOG.md b/CHANGELOG.md index cfda75036c7f2c0d5c0a63ec2944b5e307d7d857..85cbc54841554d0ba8698cce32e17c8e98d71165 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,22 @@ ## Unreleased + +## 1.0.0 / 2022-01-18 + +### Fixed +- Fixed alert template loading + +### Added +- support for templating functions from [the sprig library](https://github.com/Masterminds/sprig) +- default value for the gitlab.url flag pointing to `https://gtilab.com` +- new flag `--log.json` to enable JSON logging + ### Changed - prometheus and alertmanager dependency versions +- Upgraded to Go 1.17 +- Migrated to goreleaser +- Switched to logrus library, default log format has changed +- Default issue template is now embedded in the binary ## 0.7.0 / 2019-08-13 diff --git a/Dockerfile b/Dockerfile index 63389a7d0fd1822c45b2ede6d98988b6773f2d41..6379c09fc638d81eb4769a782e27dfb7f0340f9a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,17 +1,11 @@ -FROM quay.io/prometheus/busybox:latest +FROM alpine:3.15.0 LABEL maintainer="FUSAKLA Martin Chodúr <m.chodur@seznam.cz>" -ARG ARCH="amd64" -ARG OS="linux" -COPY --chown=nobody:nogroup .build/${OS}-${ARCH}/prometheus-gitlab-notifier /bin/prometheus-gitlab-notifier -COPY --chown=nobody:nogroup conf/default_issue.tmpl /prometheus-gitlab-notifier/conf/ +COPY --chown=nobody:nogroup prometheus-gitlab-notifier /usr/bin/ COPY --chown=nobody:nogroup Dockerfile / EXPOSE 9629 -RUN mkdir -p /prometheus-gitlab-notifier && chown nobody:nogroup /prometheus-gitlab-notifier -WORKDIR /prometheus-gitlab-notifier - USER 65534 -ENTRYPOINT ["/bin/prometheus-gitlab-notifier"] +ENTRYPOINT ["/usr/bin/prometheus-gitlab-notifier"] diff --git a/Makefile b/Makefile deleted file mode 100644 index e9a6c89f50c6c32c9768ca2825c49b4f126c804f..0000000000000000000000000000000000000000 --- a/Makefile +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright 2016 The Prometheus Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Needs to be defined before including Makefile.common to auto-generate targets -DOCKER_ARCHS ?= amd64 - -include Makefile.common - -DOCKER_IMAGE_NAME ?= prometheus-gitlab-notifier - -assets: - @echo ">> writing assets" - @cd $(PREFIX)/asset && GO111MODULE=$(GO111MODULE) $(GO) generate && $(GOFMT) -w assets_vfsdata.go diff --git a/Makefile.common b/Makefile.common deleted file mode 100644 index db98993d699ce89ab333bba92d65615073c707b9..0000000000000000000000000000000000000000 --- a/Makefile.common +++ /dev/null @@ -1,277 +0,0 @@ -# Copyright 2018 The Prometheus Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -# A common Makefile that includes rules to be reused in different prometheus projects. -# !!! Open PRs only against the prometheus/prometheus/Makefile.common repository! - -# Example usage : -# Create the main Makefile in the root project directory. -# include Makefile.common -# customTarget: -# @echo ">> Running customTarget" -# - -# Ensure GOBIN is not set during build so that promu is installed to the correct path -unexport GOBIN - -GO ?= go -GOFMT ?= $(GO)fmt -FIRST_GOPATH := $(firstword $(subst :, ,$(shell $(GO) env GOPATH))) -GOOPTS ?= -GOHOSTOS ?= $(shell $(GO) env GOHOSTOS) -GOHOSTARCH ?= $(shell $(GO) env GOHOSTARCH) - -GO_VERSION ?= $(shell $(GO) version) -GO_VERSION_NUMBER ?= $(word 3, $(GO_VERSION)) -PRE_GO_111 ?= $(shell echo $(GO_VERSION_NUMBER) | grep -E 'go1\.(10|[0-9])\.') - -GOVENDOR := -GO111MODULE := -ifeq (, $(PRE_GO_111)) - ifneq (,$(wildcard go.mod)) - # Enforce Go modules support just in case the directory is inside GOPATH (and for Travis CI). - GO111MODULE := on - - ifneq (,$(wildcard vendor)) - # Always use the local vendor/ directory to satisfy the dependencies. - GOOPTS := $(GOOPTS) -mod=vendor - endif - endif -else - ifneq (,$(wildcard go.mod)) - ifneq (,$(wildcard vendor)) -$(warning This repository requires Go >= 1.11 because of Go modules) -$(warning Some recipes may not work as expected as the current Go runtime is '$(GO_VERSION_NUMBER)') - endif - else - # This repository isn't using Go modules (yet). - GOVENDOR := $(FIRST_GOPATH)/bin/govendor - endif -endif -PROMU := $(FIRST_GOPATH)/bin/promu -pkgs = ./... - -ifeq (arm, $(GOHOSTARCH)) - GOHOSTARM ?= $(shell GOARM= $(GO) env GOARM) - GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH)v$(GOHOSTARM) -else - GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH) -endif - -PROMU_VERSION ?= 0.5.0 -PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz - -GOLANGCI_LINT := -GOLANGCI_LINT_OPTS ?= -GOLANGCI_LINT_VERSION ?= v1.17.1 -# golangci-lint only supports linux, darwin and windows platforms on i386/amd64. -# windows isn't included here because of the path separator being different. -ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin)) - ifeq ($(GOHOSTARCH),$(filter $(GOHOSTARCH),amd64 i386)) - GOLANGCI_LINT := $(FIRST_GOPATH)/bin/golangci-lint - endif -endif - -PREFIX ?= $(shell pwd) -BIN_DIR ?= $(shell pwd) -DOCKER_IMAGE_TAG ?= $(subst /,-,$(shell git rev-parse --abbrev-ref HEAD)) -DOCKERFILE_PATH ?= ./Dockerfile -DOCKERBUILD_CONTEXT ?= ./ -DOCKER_REPO ?= prom - -DOCKER_ARCHS ?= amd64 - -BUILD_DOCKER_ARCHS = $(addprefix common-docker-,$(DOCKER_ARCHS)) -PUBLISH_DOCKER_ARCHS = $(addprefix common-docker-publish-,$(DOCKER_ARCHS)) -TAG_DOCKER_ARCHS = $(addprefix common-docker-tag-latest-,$(DOCKER_ARCHS)) - -ifeq ($(GOHOSTARCH),amd64) - ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux freebsd darwin windows)) - # Only supported on amd64 - test-flags := -race - endif -endif - -# This rule is used to forward a target like "build" to "common-build". This -# allows a new "build" target to be defined in a Makefile which includes this -# one and override "common-build" without override warnings. -%: common-% ; - -.PHONY: common-all -common-all: precheck style check_license lint unused build test - -.PHONY: common-style -common-style: - @echo ">> checking code style" - @fmtRes=$$($(GOFMT) -d $$(find . -path ./vendor -prune -o -name '*.go' -print)); \ - if [ -n "$${fmtRes}" ]; then \ - echo "gofmt checking failed!"; echo "$${fmtRes}"; echo; \ - echo "Please ensure you are using $$($(GO) version) for formatting code."; \ - exit 1; \ - fi - -.PHONY: common-check_license -common-check_license: - @echo ">> checking license header" - @licRes=$$(for file in $$(find . -type f -iname '*.go' ! -path './vendor/*') ; do \ - awk 'NR<=3' $$file | grep -Eq "(Copyright|generated|GENERATED)" || echo $$file; \ - done); \ - if [ -n "$${licRes}" ]; then \ - echo "license header checking failed:"; echo "$${licRes}"; \ - exit 1; \ - fi - -.PHONY: common-deps -common-deps: - @echo ">> getting dependencies" -ifdef GO111MODULE - GO111MODULE=$(GO111MODULE) $(GO) mod download -else - $(GO) get $(GOOPTS) -t ./... -endif - -.PHONY: common-test-short -common-test-short: - @echo ">> running short tests" - GO111MODULE=$(GO111MODULE) $(GO) test -short $(GOOPTS) $(pkgs) - -.PHONY: common-test -common-test: - @echo ">> running all tests" - GO111MODULE=$(GO111MODULE) $(GO) test $(test-flags) $(GOOPTS) $(pkgs) - -.PHONY: common-format -common-format: - @echo ">> formatting code" - GO111MODULE=$(GO111MODULE) $(GO) fmt $(pkgs) - -.PHONY: common-vet -common-vet: - @echo ">> vetting code" - GO111MODULE=$(GO111MODULE) $(GO) vet $(GOOPTS) $(pkgs) - -.PHONY: common-lint -common-lint: $(GOLANGCI_LINT) -ifdef GOLANGCI_LINT - @echo ">> running golangci-lint" -ifdef GO111MODULE -# 'go list' needs to be executed before staticcheck to prepopulate the modules cache. -# Otherwise staticcheck might fail randomly for some reason not yet explained. - GO111MODULE=$(GO111MODULE) $(GO) list -e -compiled -test=true -export=false -deps=true -find=false -tags= -- ./... > /dev/null - GO111MODULE=$(GO111MODULE) $(GOLANGCI_LINT) run $(GOLANGCI_LINT_OPTS) $(pkgs) -else - $(GOLANGCI_LINT) run $(pkgs) -endif -endif - -# For backward-compatibility. -.PHONY: common-staticcheck -common-staticcheck: lint - -.PHONY: common-unused -common-unused: $(GOVENDOR) -ifdef GOVENDOR - @echo ">> running check for unused packages" - @$(GOVENDOR) list +unused | grep . && exit 1 || echo 'No unused packages' -else -ifdef GO111MODULE - @echo ">> running check for unused/missing packages in go.mod" - GO111MODULE=$(GO111MODULE) $(GO) mod tidy -ifeq (,$(wildcard vendor)) - @git diff --exit-code -- go.sum go.mod -else - @echo ">> running check for unused packages in vendor/" - GO111MODULE=$(GO111MODULE) $(GO) mod vendor - @git diff --exit-code -- go.sum go.mod vendor/ -endif -endif -endif - -.PHONY: common-build -common-build: promu - @echo ">> building binaries" - GO111MODULE=$(GO111MODULE) $(PROMU) build --prefix $(PREFIX) - -.PHONY: common-tarball -common-tarball: promu - @echo ">> building release tarball" - $(PROMU) tarball --prefix $(PREFIX) $(BIN_DIR) - -.PHONY: common-docker $(BUILD_DOCKER_ARCHS) -common-docker: $(BUILD_DOCKER_ARCHS) -$(BUILD_DOCKER_ARCHS): common-docker-%: - docker build -t "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" \ - -f $(DOCKERFILE_PATH) \ - --build-arg ARCH="$*" \ - --build-arg OS="linux" \ - $(DOCKERBUILD_CONTEXT) - -.PHONY: common-docker-publish $(PUBLISH_DOCKER_ARCHS) -common-docker-publish: $(PUBLISH_DOCKER_ARCHS) -$(PUBLISH_DOCKER_ARCHS): common-docker-publish-%: - docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" - -.PHONY: common-docker-tag-latest $(TAG_DOCKER_ARCHS) -common-docker-tag-latest: $(TAG_DOCKER_ARCHS) -$(TAG_DOCKER_ARCHS): common-docker-tag-latest-%: - docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:latest" - -.PHONY: common-docker-manifest -common-docker-manifest: - DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)" $(foreach ARCH,$(DOCKER_ARCHS),$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$(ARCH):$(DOCKER_IMAGE_TAG)) - DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)" - -.PHONY: promu -promu: $(PROMU) - -$(PROMU): - $(eval PROMU_TMP := $(shell mktemp -d)) - curl -s -L $(PROMU_URL) | tar -xvzf - -C $(PROMU_TMP) - mkdir -p $(FIRST_GOPATH)/bin - cp $(PROMU_TMP)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM)/promu $(FIRST_GOPATH)/bin/promu - rm -r $(PROMU_TMP) - -.PHONY: proto -proto: - @echo ">> generating code from proto files" - @./scripts/genproto.sh - -ifdef GOLANGCI_LINT -$(GOLANGCI_LINT): - mkdir -p $(FIRST_GOPATH)/bin - curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/$(GOLANGCI_LINT_VERSION)/install.sh \ - | sed -e '/install -d/d' \ - | sh -s -- -b $(FIRST_GOPATH)/bin $(GOLANGCI_LINT_VERSION) -endif - -ifdef GOVENDOR -.PHONY: $(GOVENDOR) -$(GOVENDOR): - GOOS= GOARCH= $(GO) get -u github.com/kardianos/govendor -endif - -.PHONY: precheck -precheck:: - -define PRECHECK_COMMAND_template = -precheck:: $(1)_precheck - -PRECHECK_COMMAND_$(1) ?= $(1) $$(strip $$(PRECHECK_OPTIONS_$(1))) -.PHONY: $(1)_precheck -$(1)_precheck: - @if ! $$(PRECHECK_COMMAND_$(1)) 1>/dev/null 2>&1; then \ - echo "Execution of '$$(PRECHECK_COMMAND_$(1))' command failed. Is $(1) installed?"; \ - exit 1; \ - fi -endef diff --git a/README.md b/README.md index 216ddf4e8d5e2899533fc2a7a65453c20675cf13..405446f9349cfc2934f5bb0044008597a89dc47b 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,12 @@ Flags: To test it is running check logs or http://0.0.0.0:9629/readiness +### Try it out +You can send a test alert to it using the prepared alert JSON by running thin in root of this repo +```bash +curl -X POST -H "Content-Type: application/json" -d @./conf/alert.json http://localhost:9629/api/alertmanager +``` + ### Issue template Look of the resulting issue in Gitlab can be customized using [Go template](https://golang.org/pkg/text/template/). Default template can be found in [conf/default_issue.tmpl](conf/default_issue.tmpl). @@ -55,7 +61,7 @@ fails in the runtime, raw JSON of the alert will be pasted to the text of the is Example of the default template: - + ### Configure Alertmanager You just need to add the [`<webhook_config>`](https://prometheus.io/docs/alerting/configuration/#webhook_config) @@ -70,14 +76,14 @@ The Gitlab notifier allows to label the resulting issue based on the alert label It uses mostly Gitlab scoped labels in format `label::value`. The grouping labels of the alert are added to the issue automatically to allow identifying same alerts (more on that in [Grouping](#Grouping) section). -Additionally you can specify names of labels to be also added to the issue using flag `--dynamic.issue.label.name`. +Additionally, you can specify names of labels to be also added to the issue using flag `--dynamic.issue.label.name`. Last thing you can add are static labels which will be added to every issue using flag `--issue.label`, ### Grouping To avoid flooding gitlab with identical alerts if they happen to fire and resolve again and again, Gitlab notifier checks for issues witch the same grouping labels as the new incoming alert. -If if finds any still open issue younger than `1h` by default (can be controlled by flag `--group.interval`), +If if it finds any still open issue younger than `1h` by default (can be controlled by flag `--group.interval`), it only appends the rendered template to the end of the issue description and adds to the issue label `appended-alerts::<number>` witch count of how many times it was updated. diff --git a/VERSION b/VERSION deleted file mode 100644 index a918a2aa18d5bec6a8bb93891a7a63c243111796..0000000000000000000000000000000000000000 --- a/VERSION +++ /dev/null @@ -1 +0,0 @@ -0.6.0 diff --git a/cmd/prometheus-gitlab-notifier/default_issue.tmpl b/cmd/prometheus-gitlab-notifier/default_issue.tmpl new file mode 100644 index 0000000000000000000000000000000000000000..d05b890fbb085838ee609407b4603664fddcda3e --- /dev/null +++ b/cmd/prometheus-gitlab-notifier/default_issue.tmpl @@ -0,0 +1,31 @@ +{{define "alert"}} + - **`{{ index .Annotations "description" }}`** + - **Starts at**: {{ .StartsAt }} + - **Ends at**: {{ .EndsAt }} + - **Generator URL**: [{{ .GeneratorURL }}]({{ .GeneratorURL }}) + - **Labels**: `{{`{`}}{{ range $k,$v := .Labels }}{{$k}}="{{$v}}", {{end}}{{`}`}}` +{{end}} + + +# `{{ index .CommonLabels "severity" }}` alert `{{ index .CommonLabels "alertname" }}` occurred +**Title:** {{ index .CommonAnnotations "title" }} +**Alertmanager link:** [{{ .ExternalURL }}]({{ .ExternalURL }}) + +### Common labels: +{{- range $k,$v := .CommonLabels }} + - **`{{ $k }}`**: `{{ $v }}` +{{- end }} + +### Common annotations: +{{- range $k,$v := .CommonAnnotations }} + {{- if and (not (eq $k "title")) (not (eq $k "description")) }} + - **`{{ $k }}`**: `{{ $v }}` + {{- end }} +{{- end }} + +--- + +## Alerts +{{- range .Alerts }} + {{ template "alert" . }} +{{- end }} diff --git a/cmd/prometheus-gitlab-notifier/prometheus-gitlab-notifier.go b/cmd/prometheus-gitlab-notifier/prometheus-gitlab-notifier.go index 2b9fb0fb774879b2916979fa2c8d98d62f6c76fd..7660397dd0ea4b13fc8babed3ef13bf816973731 100644 --- a/cmd/prometheus-gitlab-notifier/prometheus-gitlab-notifier.go +++ b/cmd/prometheus-gitlab-notifier/prometheus-gitlab-notifier.go @@ -15,6 +15,7 @@ package main import ( "context" + _ "embed" "io/ioutil" "net/http" "os" @@ -24,6 +25,7 @@ import ( "text/template" "time" + "github.com/Masterminds/sprig" "github.com/alecthomas/kingpin" "github.com/fusakla/prometheus-gitlab-notifier/pkg/alertmanager" "github.com/fusakla/prometheus-gitlab-notifier/pkg/api" @@ -32,37 +34,37 @@ import ( "github.com/fusakla/prometheus-gitlab-notifier/pkg/metrics" "github.com/fusakla/prometheus-gitlab-notifier/pkg/prober" "github.com/fusakla/prometheus-gitlab-notifier/pkg/processor" - "github.com/go-kit/kit/log" - "github.com/go-kit/kit/log/level" "github.com/gorilla/mux" "github.com/pkg/errors" + log "github.com/sirupsen/logrus" ) -func setupLogger(debug bool) log.Logger { - l := log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout)) - l = log.With(l, "ts", log.DefaultTimestamp, "caller", log.DefaultCaller) +func setupLogger(debug bool, logJson bool) log.FieldLogger { + l := log.New() + l.SetOutput(os.Stdout) + if logJson { + l.SetFormatter(&log.JSONFormatter{}) + } if debug { - l = level.NewFilter(l, level.AllowDebug()) - } else { - l = level.NewFilter(l, level.AllowInfo()) + l.SetLevel(log.DebugLevel) } return l } -func waitForEmptyChannel(logger log.Logger, ch <-chan *alertmanager.Webhook) { - level.Info(logger).Log("msg", "waiting for all the alerts to be processed") +func waitForEmptyChannel(logger log.FieldLogger, ch <-chan *alertmanager.Webhook) { + logger.Info("waiting for all the alerts to be processed") for { if len(ch) > 0 { - level.Info(logger).Log("msg", "there are still alerts in the queue, waiting forthem to be processed", "queue_size", len(ch)) + logger.WithField("queue_size", len(ch)).Info("there are still alerts in the queue, waiting for them to be processed") time.Sleep(10 * time.Millisecond) continue } break } - level.Info(logger).Log("msg", "processing of the rest of alerts is done") + logger.Info("processing of the rest of alerts is done") } -func startServer(logger log.Logger, r http.Handler) (*http.Server, <-chan error) { +func startServer(logger log.FieldLogger, r http.Handler) (*http.Server, <-chan error) { errCh := make(chan error, 1) srv := &http.Server{ Handler: handler.Instrumented(logger, r), @@ -72,10 +74,10 @@ func startServer(logger log.Logger, r http.Handler) (*http.Server, <-chan error) } go func() { defer close(errCh) - level.Info(logger).Log("msg", "Starting prometheus-gitlab-notifier", "addr", "0.0.0.0:9629") + logger.WithField("addr", "0.0.0.0:9629").Info("Starting prometheus-gitlab-notifier") if err := srv.ListenAndServe(); err != nil { if err != http.ErrServerClosed { - level.Error(logger).Log("msg", "server failed", "error", err) + logger.WithField("err", err).Error("server failed") errCh <- err } } @@ -86,40 +88,55 @@ func startServer(logger log.Logger, r http.Handler) (*http.Server, <-chan error) var ( app = kingpin.New("prometheus-gitlab-notifier", "Web server listening for webhooks of alertmanager and creating an issue in Gitlab based on it.") debug = app.Flag("debug", "Enables debug logging.").Bool() + logJson = app.Flag("log.json", "Log in JSON format").Bool() serverAddr = app.Flag("server.addr", "Allows to change the address and port at which the server will listen for incoming connections.").Default("0.0.0.0:9629").String() - gitlabURL = app.Flag("gitlab.url", "URL of the Gitlab API.").Required().String() + gitlabURL = app.Flag("gitlab.url", "URL of the Gitlab API.").Default("https://gitlab.com").String() gitlabTokenFile = app.Flag("gitlab.token.file", "Path to file containing gitlab token.").Required().ExistingFile() projectId = app.Flag("project.id", "Id of project where to create the issues.").Required().Int() groupInterval = app.Flag("group.interval", "Duration how long back to check for opened issues with the same group labels to append the new alerts to (go duration syntax allowing 'ns', 'us' , 'ms', 's', 'm', 'h').").Default("1h").Duration() issueLabels = app.Flag("issue.label", "Labels to add to the created issue. (Can be passed multiple times)").Strings() dynamicIssueLabels = app.Flag("dynamic.issue.label.name", "Alert label, which is to be propagated to the resulting Gitlab issue as scoped label if present in the received alert. (Can be passed multiple times)").Strings() - issueTemplatePath = app.Flag("issue.template", "Path to the issue golang template file.").Default("conf/default_issue.tmpl").ExistingFile() + issueTemplatePath = app.Flag("issue.template", "Path to the issue golang template file.").ExistingFile() queueSizeLimit = app.Flag("queue.size.limit", "Limit of the alert queue size.").Default("100").Int() retryBackoff = app.Flag("retry.backoff", "Duration how long to wait till next retry (go duration syntax allowing 'ns', 'us' , 'ms', 's', 'm', 'h').").Default("5m").Duration() retryLimit = app.Flag("retry.limit", "Maximum number of retries for single alert. If exceeded it's thrown away.").Default("5").Int() gracefulShutdownWait = app.Flag("graceful.shutdown.wait.duration", "Duration how long to wait on graceful shutdown marked as not ready (go duration syntax allowing 'ns', 'us' , 'ms', 's', 'm', 'h').").Default("30s").Duration() ) -func main() { +//go:embed default_issue.tmpl +var defaultIssueTemplate []byte +func main() { + var err error kingpin.MustParse(app.Parse(os.Args[1:])) // Initiate logging. - logger := setupLogger(*debug) + logger := setupLogger(*debug, *logJson) + + tpl := template.New("base").Funcs(template.FuncMap(sprig.FuncMap())) + + templateContents := defaultIssueTemplate + if *issueTemplatePath != "" { + templateContents, err = os.ReadFile(*issueTemplatePath) + if err != nil { + logger.WithFields(log.Fields{"err": err, "file": issueTemplatePath}).Error("failed to read template file") + os.Exit(1) + } + } // Initiate Gitlab client. - gitlabIssueTextTemplate, err := template.ParseFiles(*issueTemplatePath) + gitlabIssueTextTemplate, err := tpl.Parse(string(templateContents)) if err != nil { - level.Error(logger).Log("msg", "invalid gitlab issue template", "file", *issueTemplatePath, "err", err) + logger.WithFields(log.Fields{"err": err, "file": issueTemplatePath}).Error("invalid gitlab issue template") os.Exit(1) } token, err := ioutil.ReadFile(*gitlabTokenFile) if err != nil { - level.Error(logger).Log("msg", "failed to read token file", "file", gitlabTokenFile, "err", err) + logger.WithFields(log.Fields{"err": err, "file": gitlabTokenFile}).Error("failed to read token file") os.Exit(1) } g, err := gitlab.New( - log.With(logger, "component", "gitlab"), + logger.WithField("component", "gitlab"), *gitlabURL, strings.TrimSpace(string(token)), *projectId, @@ -129,13 +146,13 @@ func main() { groupInterval, ) if err != nil { - level.Error(logger).Log("msg", "invalid gitlab configuration") + logger.WithField("err", err).Error("invalid gitlab configuration") os.Exit(1) } // Start processing all incoming alerts. alertChan := make(chan *alertmanager.Webhook, *queueSizeLimit) - proc := processor.New(log.With(logger, "component", "processor")) + proc := processor.New(logger.WithField("component", "processor")) processCtx, processCancelFunc := context.WithCancel(context.Background()) defer processCancelFunc() proc.Process(processCtx, g, alertChan, *retryLimit, *retryBackoff) @@ -144,20 +161,20 @@ func main() { r := mux.NewRouter() // Initialize the main API. webhookApi := api.NewInRouter( - log.With(logger, "component", "api"), + logger.WithField("component", "api"), r.PathPrefix("/api").Subrouter(), alertChan, ) // Initialize prober providing readiness and liveness checks. readinessProber := prober.NewInRouter( - log.With(logger, "component", "prober"), + logger.WithField("component", "prober"), r.PathPrefix("/").Subrouter(), ) // Initialize metrics handler to serve Prometheus metrics. metrics.HandleInRouter(r) // Start HTTP server - _, serverErrorChan := startServer(log.With(logger, "component", "server"), r) + _, serverErrorChan := startServer(logger.WithField("component", "server"), r) // Subscribe to system signals so we can react on them with graceful termination. gracefulStop := make(chan os.Signal, 2) @@ -172,11 +189,11 @@ func main() { waitForEmptyChannel(logger, alertChan) os.Exit(1) case sig := <-gracefulStop: - level.Info(logger).Log("msg", "received system signal for graceful shutdown", "signal", sig) + logger.WithField("signal", sig).Info("received system signal for graceful shutdown") // Mark server as not ready so no new connections will come. readinessProber.SetServerNotReady(errors.New("server is shutting down")) // Wait for specified time after marking server not ready so the environment can react on it. - level.Info(logger).Log("msg", "waiting for graceful shutdown", "duration", gracefulShutdownWait) + logger.WithField("duration", gracefulShutdownWait).Info("waiting for graceful shutdown") time.Sleep(*gracefulShutdownWait) // Stop receiving new alerts. webhookApi.Close() diff --git a/conf/default_issue.tmpl b/conf/default_issue.tmpl deleted file mode 100644 index d05b890fbb085838ee609407b4603664fddcda3e..0000000000000000000000000000000000000000 --- a/conf/default_issue.tmpl +++ /dev/null @@ -1,31 +0,0 @@ -{{define "alert"}} - - **`{{ index .Annotations "description" }}`** - - **Starts at**: {{ .StartsAt }} - - **Ends at**: {{ .EndsAt }} - - **Generator URL**: [{{ .GeneratorURL }}]({{ .GeneratorURL }}) - - **Labels**: `{{`{`}}{{ range $k,$v := .Labels }}{{$k}}="{{$v}}", {{end}}{{`}`}}` -{{end}} - - -# `{{ index .CommonLabels "severity" }}` alert `{{ index .CommonLabels "alertname" }}` occurred -**Title:** {{ index .CommonAnnotations "title" }} -**Alertmanager link:** [{{ .ExternalURL }}]({{ .ExternalURL }}) - -### Common labels: -{{- range $k,$v := .CommonLabels }} - - **`{{ $k }}`**: `{{ $v }}` -{{- end }} - -### Common annotations: -{{- range $k,$v := .CommonAnnotations }} - {{- if and (not (eq $k "title")) (not (eq $k "description")) }} - - **`{{ $k }}`**: `{{ $v }}` - {{- end }} -{{- end }} - ---- - -## Alerts -{{- range .Alerts }} - {{ template "alert" . }} -{{- end }} diff --git a/conf/default_issue.tmpl b/conf/default_issue.tmpl new file mode 120000 index 0000000000000000000000000000000000000000..37e48a78e8904b2bd4763a3c081cec6218c1a5e3 --- /dev/null +++ b/conf/default_issue.tmpl @@ -0,0 +1 @@ +../cmd/prometheus-gitlab-notifier/default_issue.tmpl \ No newline at end of file diff --git a/errcheck_excludes.txt b/errcheck_excludes.txt deleted file mode 100644 index 97b977141bb32b71933ad87d7c985bedf10728d7..0000000000000000000000000000000000000000 --- a/errcheck_excludes.txt +++ /dev/null @@ -1 +0,0 @@ -(github.com/go-kit/kit/log.Logger).Log diff --git a/go.mod b/go.mod index 3bcc3394dac82cb770fe441b0ce0bf77aff3653e..c3862d4fc82cd8faab02018153b8a7126b36301c 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,65 @@ module github.com/fusakla/prometheus-gitlab-notifier -go 1.12 +go 1.17 require ( + github.com/Masterminds/sprig v2.22.0+incompatible github.com/alecthomas/kingpin v2.2.6+incompatible - github.com/go-kit/kit v0.9.0 github.com/gorilla/mux v1.7.2 github.com/pkg/errors v0.8.1 github.com/prometheus/alertmanager v0.20.0 github.com/prometheus/client_golang v1.6.0 + github.com/sirupsen/logrus v1.4.2 github.com/xanzy/go-gitlab v0.18.0 ) + +require ( + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver v1.5.0 // indirect + github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect + github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 // indirect + github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cenkalti/backoff v0.0.0-20181003080854-62661b46c409 // indirect + github.com/cespare/xxhash v1.1.0 // indirect + github.com/cespare/xxhash/v2 v2.1.1 // indirect + github.com/go-kit/kit v0.9.0 // indirect + github.com/go-logfmt/logfmt v0.4.0 // indirect + github.com/gogo/protobuf v1.2.2-0.20190730201129-28a6bbf47e48 // indirect + github.com/golang/protobuf v1.4.0 // indirect + github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c // indirect + github.com/google/go-querystring v1.0.0 // indirect + github.com/google/uuid v1.1.1 // indirect + github.com/hashicorp/errwrap v1.0.0 // indirect + github.com/hashicorp/go-immutable-radix v1.0.0 // indirect + github.com/hashicorp/go-msgpack v0.5.3 // indirect + github.com/hashicorp/go-multierror v1.0.0 // indirect + github.com/hashicorp/go-sockaddr v1.0.2 // indirect + github.com/hashicorp/golang-lru v0.5.0 // indirect + github.com/hashicorp/memberlist v0.1.4 // indirect + github.com/huandu/xstrings v1.3.2 // indirect + github.com/imdario/mergo v0.3.12 // indirect + github.com/konsorten/go-windows-terminal-sequences v1.0.1 // indirect + github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/miekg/dns v1.0.14 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223 // indirect + github.com/oklog/run v1.0.0 // indirect + github.com/oklog/ulid v1.3.1 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.9.1 // indirect + github.com/prometheus/procfs v0.0.11 // indirect + github.com/satori/go.uuid v0.0.0-20160603004225-b111a074d5ef // indirect + github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect + github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect + github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd // indirect + golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56 // indirect + golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 // indirect + golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288 // indirect + golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f // indirect + google.golang.org/appengine v1.3.0 // indirect + google.golang.org/protobuf v1.21.0 // indirect + gopkg.in/yaml.v2 v2.3.0 // indirect +) diff --git a/go.sum b/go.sum index 5ec0f33f17877d41a5b7e5814e28b27190f24c1a..b843396e7c3283165ff17afcbc52e61d01f70fe4 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,9 @@ +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= +github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= @@ -99,6 +105,7 @@ github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASu github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.7.2 h1:zoNxOV7WjqXptQOVngLmcSQgXmgk4NMz1HibBchjl/I= github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= @@ -119,6 +126,10 @@ github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCO github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/memberlist v0.1.4 h1:gkyML/r71w3FL8gUi74Vk76avkj/9lYAY9lvg0OcoGs= github.com/hashicorp/memberlist v0.1.4/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= +github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -126,6 +137,7 @@ github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -146,8 +158,12 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5 github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= @@ -199,6 +215,7 @@ github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJ github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd h1:ug7PpSOB5RBPK1Kg6qskGBoP3Vnj/aNYFTznWvlkGo0= github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -280,5 +297,6 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/kubernetes/prometheus-gitlab-notifier-deployment.yaml b/kubernetes/prometheus-gitlab-notifier-deployment.yaml index 1bc58d49d59d264f9234eebc9cbd8ee3e38ee171..27154f88f9c427b5d20d4422e947308954605679 100644 --- a/kubernetes/prometheus-gitlab-notifier-deployment.yaml +++ b/kubernetes/prometheus-gitlab-notifier-deployment.yaml @@ -14,7 +14,7 @@ spec: spec: containers: - name: prometheus-gitlab-notifier - image: fusakla/prometheus-gitlab-notifier:0.6.0 + image: fusakla/prometheus-gitlab-notifier:latest args: - "--gitlab.url=https://gitlab.com/api/v4" - "--project.id=13766104" diff --git a/pkg/api/api.go b/pkg/api/api.go index 5acd4b8a7153396b220c03d52b52d1c6e0180ce6..3e4d53c2516bba119a6af44fa3996e98141ea807 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -22,14 +22,13 @@ import ( "github.com/fusakla/prometheus-gitlab-notifier/pkg/alertmanager" - "github.com/go-kit/kit/log" - "github.com/go-kit/kit/log/level" "github.com/gorilla/mux" "github.com/prometheus/alertmanager/notify/webhook" + log "github.com/sirupsen/logrus" ) // NewInRouter creates new Api instance which will register it's handlers in the given router. -func NewInRouter(logger log.Logger, r *mux.Router, ch chan<- *alertmanager.Webhook) *Api { +func NewInRouter(logger log.FieldLogger, r *mux.Router, ch chan<- *alertmanager.Webhook) *Api { api := &Api{ logger: logger, alertChan: ch, @@ -41,7 +40,7 @@ func NewInRouter(logger log.Logger, r *mux.Router, ch chan<- *alertmanager.Webho // Api defines handler functions for receiving Alertmanager endpoints. type Api struct { - logger log.Logger + logger log.FieldLogger alertChan chan<- *alertmanager.Webhook receiveAlerts bool receiveAlertsMtx sync.RWMutex @@ -65,7 +64,7 @@ func (a *Api) webhookHandler(w http.ResponseWriter, r *http.Request) { // Push the message to channel a.alertChan <- alertmanager.NewWebhookFromAlertmanagerMessage(message) - level.Debug(a.logger).Log("msg", "enqueued alert for processing", "group_key", message.GroupKey) + a.logger.WithField("group_key", message.GroupKey).Debug("enqueued alert for processing") w.WriteHeader(http.StatusOK) _, _ = io.WriteString(w, `Ok, Alert enqueued.`) diff --git a/pkg/gitlab/gitlab.go b/pkg/gitlab/gitlab.go index 69942c7de5c6635bfe3e6c93b71fccd239336e74..b97104c4dceb0cf58311320c00d8c8d330f28c03 100644 --- a/pkg/gitlab/gitlab.go +++ b/pkg/gitlab/gitlab.go @@ -26,16 +26,15 @@ import ( "github.com/fusakla/prometheus-gitlab-notifier/pkg/alertmanager" "github.com/fusakla/prometheus-gitlab-notifier/pkg/metrics" - "github.com/go-kit/kit/log" - "github.com/go-kit/kit/log/level" + log "github.com/sirupsen/logrus" "github.com/xanzy/go-gitlab" ) // New creates new Gitlab instance configured to work with specified gitlab instance, project and with given authentication. -func New(logger log.Logger, url string, token string, projectId int, issueTemplate *template.Template, issueLabels *[]string, dynamicIssueLabels *[]string, groupInterval *time.Duration) (*Gitlab, error) { +func New(logger log.FieldLogger, url string, token string, projectId int, issueTemplate *template.Template, issueLabels *[]string, dynamicIssueLabels *[]string, groupInterval *time.Duration) (*Gitlab, error) { cli := gitlab.NewClient(nil, token) if err := cli.SetBaseURL(url); err != nil { - level.Error(logger).Log("msg", "invalid Gitlab URL", "url", url, "err", "err") + logger.WithFields(log.Fields{"url": url, "err": "err"}).Error("invalid Gitlab URL") return nil, err } g := &Gitlab{ @@ -48,7 +47,7 @@ func New(logger log.Logger, url string, token string, projectId int, issueTempla logger: logger, } if err := g.ping(); err != nil { - level.Error(logger).Log("msg", "cannot reach the Gitlab", "url", url, "err", "err") + logger.WithFields(log.Fields{"url": url, "err": err}).Error("msg", "cannot reach the Gitlab") return nil, err } return g, nil @@ -62,7 +61,7 @@ type Gitlab struct { issueLabels *[]string dynamicIssueLabels *[]string groupInterval *time.Duration - logger log.Logger + logger log.FieldLogger } func (g *Gitlab) formatGitlabScopedLabel(key string, value string) string { @@ -99,14 +98,34 @@ func (g *Gitlab) renderIssueTemplate(msg *alertmanager.Webhook) (*bytes.Buffer, var issueText bytes.Buffer // Try to template the issue text template with the alert data. if err := g.issueTemplate.Execute(&issueText, msg.Data); err != nil { - // As a fallback we try to add raw JSON of the alert to the issue text so we don't miss an alert just because of template error. + // As a fallback we try to add raw JSON of the alert to the issue text, so we don't miss an alert just because of template error. metrics.ReportError("IssueTemplateError", "") - level.Error(g.logger).Log("msg", "failed to template issue text, using pure JSON instead", "err", err) + g.logger.WithFields(log.Fields{"err": err}).Error("failed to template issue text, using pure JSON instead") w := bufio.NewWriter(&issueText) - if err := json.NewEncoder(w).Encode(msg); err != nil { + _, err := w.WriteString("\n```json\n") + if err != nil { + metrics.ReportError("JSONWriteError", "") + g.logger.WithFields(log.Fields{"err": err}).Error("failed to write the alert to JSON") + return nil, err + } + e := json.NewEncoder(w) + e.SetIndent("", " ") + if err := e.Encode(msg); err != nil { // If even JSON marshalling fails we return error metrics.ReportError("JSONMarshalError", "") - level.Error(g.logger).Log("msg", "failed to marshall alert to JSON", "err", err) + g.logger.WithFields(log.Fields{"err": err}).Error("failed to marshall alert to JSON") + return nil, err + } + _, err = w.WriteString("\n```\n") + if err != nil { + metrics.ReportError("JSONWriteError", "") + g.logger.WithFields(log.Fields{"err": err}).Error("failed to write the alert to JSON") + return nil, err + } + err = w.Flush() + if err != nil { + metrics.ReportError("JSONWriteError", "") + g.logger.WithFields(log.Fields{"err": err}).Error("failed to write the alert to JSON") return nil, err } } @@ -127,7 +146,7 @@ func (g *Gitlab) getOpenIssuesSince(groupingLabels []string, sinceTime time.Time issues, response, err := g.client.Issues.ListIssues(&listOpts) if err != nil { metrics.ReportError("ListGitlabIssuesError", "gitlab") - level.Error(g.logger).Log("msg", "failed to list gitlab issues with", "opts", listOpts, "response", response, "err", err) + g.logger.WithFields(log.Fields{"opts": listOpts, "response": response, "err": err}).Error("failed to list gitlab issues with") return []*gitlab.Issue{}, err } return issues, nil @@ -151,10 +170,10 @@ func (g *Gitlab) createGitlabIssue(msg *alertmanager.Webhook, groupingLabels []s createdIssue, response, err := g.client.Issues.CreateIssue(g.projectId, options) if err != nil { metrics.ReportError("FailedToCreateGitlabIssue", "gitlab") - level.Error(g.logger).Log("msg", "failed to create gitlab issue", "err", err, "response", response) + g.logger.WithFields(log.Fields{"err": err, "response": response}).Error("failed to create gitlab issue") return err } - level.Info(g.logger).Log("msg", "created issue in gitlab", "gitlab_issue_id", createdIssue.IID, "alert_grouping_key", msg.GroupKey) + g.logger.WithFields(log.Fields{"gitlab_issue_id": createdIssue.IID, "alert_grouping_key": msg.GroupKey}).Info("created issue in gitlab") return nil } @@ -171,7 +190,7 @@ func (g *Gitlab) increaseAppendLabel(labels []string) []string { // Convert it to number if possible otherwise leave the old one as is count, err := strconv.Atoi(matched[2]) if err != nil { - level.Error(g.logger).Log("msg", "failed to parse gitlab issue label `appended-alerts`, leaving it unmodified", "label_value", l, "err", err) + g.logger.WithFields(log.Fields{"err": err, "label_value": l}).Error("failed to parse gitlab issue label `appended-alerts`, leaving it unmodified") newLabels = append(newLabels, l) continue } @@ -197,10 +216,10 @@ func (g *Gitlab) updateGitlabIssue(issue *gitlab.Issue, issueText *bytes.Buffer) issue, response, err := g.client.Issues.UpdateIssue(g.projectId, issue.IID, options) if err != nil { metrics.ReportError("FailedToUpdateGitlabIssue", "gitlab") - level.Error(g.logger).Log("msg", "failed to update gitlab issue, will try to create new", "err", err, "response", response) + g.logger.WithFields(log.Fields{"err": err, "response": response}).Error("failed to update gitlab issue, will try to create new") return err } - level.Info(g.logger).Log("msg", "updated issue in gitlab", "gitlab_issue_id", issue.IID) + g.logger.WithFields(log.Fields{"gitlab_issue_id": issue.IID}).Info("updated issue in gitlab") return nil } @@ -212,7 +231,7 @@ func (g *Gitlab) CreateIssue(msg *alertmanager.Webhook) error { // Check for existing issues with same grouping labels matchingIssues, err := g.getOpenIssuesSince(groupingLabels, g.getTimeBefore(g.groupInterval)) if err != nil { - level.Warn(g.logger).Log("msg", "listing of open issues to check for duplicates failed , opening a new one even though possible duplicate") + g.logger.Warn("listing of open issues to check for duplicates failed , opening a new one even though possible duplicate") } // Try to render the issue text template @@ -225,7 +244,7 @@ func (g *Gitlab) CreateIssue(msg *alertmanager.Webhook) error { // Issues are ordered by created date, we update the first so the newest one. issueToUpdate := matchingIssues[0] if err := g.updateGitlabIssue(issueToUpdate, issueText); err != nil { - level.Warn(g.logger).Log("msg", "updating an existing issue failed, opening a new one", "updated_issue_id", issueToUpdate.IID) + g.logger.WithField("updated_issue_id", issueToUpdate.IID).Warn("updating an existing issue failed, opening a new one") } else { return nil } @@ -238,11 +257,11 @@ func (g *Gitlab) CreateIssue(msg *alertmanager.Webhook) error { } func (g *Gitlab) ping() error { - level.Debug(g.logger).Log("msg", "trying to ping gitlab", "url", g.client.BaseURL()) + g.logger.WithField("url", g.client.BaseURL()).Debug("trying to ping gitlab") _, err := http.Head(g.client.BaseURL().String()) if err != nil { metrics.ReportError("FailedToPingGitlab", "gitlab") - level.Error(g.logger).Log("msg", "failed to ping gitlab with HEAD request", "err", err) + g.logger.WithField("err", err).Error("failed to ping gitlab with HEAD request") return err } return nil diff --git a/pkg/handler/handler.go b/pkg/handler/handler.go index 2159e7fa20cf61c1b9e9c8aa0f4a22bb69546b9b..3fad989f6840527337c356a0ea18d5044a1b02bf 100644 --- a/pkg/handler/handler.go +++ b/pkg/handler/handler.go @@ -19,9 +19,8 @@ import ( "time" "github.com/fusakla/prometheus-gitlab-notifier/pkg/metrics" - "github.com/go-kit/kit/log" - "github.com/go-kit/kit/log/level" "github.com/prometheus/client_golang/prometheus" + log "github.com/sirupsen/logrus" ) var ( @@ -56,13 +55,13 @@ func (w *instrumentedWriter) Write(b []byte) (int, error) { } // Instrumented returns instrumented handler which provides access logging and prometheus metrics for incoming requests. -func Instrumented(logger log.Logger, handler http.Handler) http.HandlerFunc { +func Instrumented(logger log.FieldLogger, handler http.Handler) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { start := time.Now() sw := instrumentedWriter{ResponseWriter: w} handler.ServeHTTP(&sw, r) duration := time.Since(start) - level.Info(logger).Log("msg", "access log", "uri", r.RequestURI, "method", r.Method, "status", sw.status, "remote_addr", r.RemoteAddr, "duration", duration) + logger.WithFields(log.Fields{"uri": r.RequestURI, "method": r.Method, "status": sw.status, "remote_addr": r.RemoteAddr, "duration": duration}).Info("served request") metricsEndpoint := r.URL.Path if sw.status == 404 { metricsEndpoint = "non-existing-endpoint" diff --git a/pkg/prober/prober.go b/pkg/prober/prober.go index 823a7d777dbdf75152f6b99f66e174ab18b9e829..e34944580d522aa4bfdb807af60ba8e34a4dccab 100644 --- a/pkg/prober/prober.go +++ b/pkg/prober/prober.go @@ -18,13 +18,12 @@ import ( "net/http" "sync" - "github.com/go-kit/kit/log" - "github.com/go-kit/kit/log/level" "github.com/gorilla/mux" + log "github.com/sirupsen/logrus" ) -// NewInRouter returns new Prober which registers it's endpoints in the Router to provide readiness and liveness endpoints. -func NewInRouter(logger log.Logger, router *mux.Router) *prober { +// NewInRouter returns new Prober which registers its endpoints in the Router to provide readiness and liveness endpoints. +func NewInRouter(logger log.FieldLogger, router *mux.Router) *prober { p := &prober{ logger: logger, serverReady: nil, @@ -35,7 +34,7 @@ func NewInRouter(logger log.Logger, router *mux.Router) *prober { // prober holds application readiness/liveness status and provides handlers for reporting it. type prober struct { - logger log.Logger + logger log.FieldLogger serverReadyMtx sync.RWMutex serverReady error } @@ -51,7 +50,7 @@ func (p *prober) livenessHandler(w http.ResponseWriter, r *http.Request) { } func (p *prober) writeFailedReadiness(w http.ResponseWriter, err error) { - level.Error(p.logger).Log("msg", "readiness probe failed", "err", err) + p.logger.WithField("err", err).Error("readiness probe failed") http.Error(w, err.Error(), http.StatusServiceUnavailable) } @@ -68,7 +67,7 @@ func (p *prober) readinessHandler(w http.ResponseWriter, r *http.Request) { func (p *prober) SetServerNotReady(err error) { p.serverReadyMtx.Lock() defer p.serverReadyMtx.Unlock() - level.Warn(p.logger).Log("msg", "Marking server as not ready", "reason", err) + p.logger.WithField("reason", err).Warn("Marking server as not ready") p.serverReady = err } diff --git a/pkg/processor/processor.go b/pkg/processor/processor.go index 815852df0a42c5fa21450b6dd5598f412f3f9d16..fc38db54bf8eb2fa70b3610516d7cc81e07bd253 100644 --- a/pkg/processor/processor.go +++ b/pkg/processor/processor.go @@ -19,9 +19,8 @@ import ( "github.com/fusakla/prometheus-gitlab-notifier/pkg/alertmanager" "github.com/fusakla/prometheus-gitlab-notifier/pkg/gitlab" - "github.com/go-kit/kit/log" - "github.com/go-kit/kit/log/level" "github.com/prometheus/client_golang/prometheus" + log "github.com/sirupsen/logrus" ) var ( @@ -41,14 +40,14 @@ func init() { } // New returns new processor which handles the alert queue and retrying. -func New(logger log.Logger) *processor { +func New(logger log.FieldLogger) *processor { return &processor{ logger: logger, } } type processor struct { - logger log.Logger + logger log.FieldLogger } // Process processes alerts from the given channel and creates Gitlab issues from them. @@ -64,10 +63,10 @@ func (p *processor) Process(ctx context.Context, gitlab *gitlab.Gitlab, alertCha if !ok { return } - level.Debug(p.logger).Log("msg", "fetched alert from queue for processing", "group_key", alert.GroupKey) + p.logger.WithField("group_key", alert.GroupKey).Debug("fetched alert from queue for processing") if err := gitlab.CreateIssue(alert); err != nil { if alert.RetryCount() >= retryLimit-1 { - level.Warn(p.logger).Log("msg", "alert exceeded maximum number of retries, dropping it", "group_key", alert.GroupKey, "retry_count", retryLimit) + p.logger.WithFields(log.Fields{"group_key": alert.GroupKey, "retry_count": retryLimit}).Warn("alert exceeded maximum number of retries, dropping it") continue } go func() { @@ -75,7 +74,7 @@ func (p *processor) Process(ctx context.Context, gitlab *gitlab.Gitlab, alertCha alert.Retry() alertChannel <- alert retryCount.Inc() - level.Warn(p.logger).Log("msg", "added alert to queue for retrying ", "group_key", alert.GroupKey, "retry_backoff", retryBackoff) + p.logger.WithFields(log.Fields{"group_key": alert.GroupKey, "retry_backoff": retryBackoff}).Warn("added alert to queue for retrying ") }() } processedItems.Inc()