diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3675d4d..50f18c6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -32,7 +32,7 @@ jobs: - uses: wangyoucao577/go-release-action@master with: github_token: ${{ secrets.GITHUB_TOKEN }} - goversion: 1.18 + goversion: 1.21 goos: ${{ matrix.goos }} goarch: ${{ matrix.goarch }} release_tag: ${{ github.event.release.tag_name || github.event.inputs.tag }} diff --git a/Dockerfile b/Dockerfile index 3ee9873..cec7fda 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.18 as build +FROM golang:1.21 as build WORKDIR /go/artifactory_exporter ADD . /go/artifactory_exporter diff --git a/artifactory/client.go b/artifactory/client.go index 55df691..4fc3f38 100644 --- a/artifactory/client.go +++ b/artifactory/client.go @@ -2,10 +2,9 @@ package artifactory import ( "crypto/tls" + "log/slog" "net/http" - "github.com/go-kit/log" - "github.com/peimanja/artifactory_exporter/config" ) @@ -16,7 +15,7 @@ type Client struct { cred config.Credentials optionalMetrics config.OptionalMetrics client *http.Client - logger log.Logger + logger *slog.Logger } // NewClient returns an initialized Artifactory HTTP Client. diff --git a/artifactory/federation.go b/artifactory/federation.go index 252618a..097cd3e 100644 --- a/artifactory/federation.go +++ b/artifactory/federation.go @@ -2,8 +2,6 @@ package artifactory import ( "encoding/json" - - "github.com/go-kit/log/level" ) const federationMirrorsLagEndpoint = "federation/status/mirrorsLag" @@ -34,7 +32,7 @@ type MirrorLags struct { // FetchMirrorLags makes the API call to federation/status/mirrorsLag endpoint and returns []MirrorLag func (c *Client) FetchMirrorLags() (MirrorLags, error) { var mirrorLags MirrorLags - level.Debug(c.logger).Log("msg", "Fetching mirror lags") + c.logger.Debug("Fetching mirror lags") resp, err := c.FetchHTTP(federationMirrorsLagEndpoint) if err != nil { if err.(*APIError).status == 404 { @@ -45,7 +43,7 @@ func (c *Client) FetchMirrorLags() (MirrorLags, error) { mirrorLags.NodeId = resp.NodeId if err := json.Unmarshal(resp.Body, &mirrorLags.MirrorLags); err != nil { - level.Error(c.logger).Log("msg", "There was an issue when try to unmarshal mirror lags respond") + c.logger.Error("There was an issue when try to unmarshal mirror lags respond") return mirrorLags, &UnmarshalError{ message: err.Error(), endpoint: federationMirrorsLagEndpoint, @@ -71,7 +69,7 @@ type UnavailableMirrors struct { // FetchUnavailableMirrors makes the API call to federation/status/unavailableMirrors endpoint and returns []UnavailableMirror func (c *Client) FetchUnavailableMirrors() (UnavailableMirrors, error) { var unavailableMirrors UnavailableMirrors - level.Debug(c.logger).Log("msg", "Fetching unavailable mirrors") + c.logger.Debug("Fetching unavailable mirrors") resp, err := c.FetchHTTP(federationUnavailableMirrorsEndpoint) if err != nil { if err.(*APIError).status == 404 { @@ -82,7 +80,7 @@ func (c *Client) FetchUnavailableMirrors() (UnavailableMirrors, error) { unavailableMirrors.NodeId = resp.NodeId if err := json.Unmarshal(resp.Body, &unavailableMirrors.UnavailableMirrors); err != nil { - level.Error(c.logger).Log("msg", "There was an issue when try to unmarshal unavailable mirrors respond") + c.logger.Error("There was an issue when try to unmarshal unavailable mirrors respond") return unavailableMirrors, &UnmarshalError{ message: err.Error(), endpoint: federationUnavailableMirrorsEndpoint, diff --git a/artifactory/openmetrics.go b/artifactory/openmetrics.go index e3ca87c..0a91d36 100644 --- a/artifactory/openmetrics.go +++ b/artifactory/openmetrics.go @@ -1,9 +1,5 @@ package artifactory -import ( - "github.com/go-kit/log/level" -) - const openMetricsEndpoint = "v1/metrics" type OpenMetrics struct { @@ -14,7 +10,7 @@ type OpenMetrics struct { // FetchOpenMetrics makes the API call to open metrics endpoint and returns all the open metrics func (c *Client) FetchOpenMetrics() (OpenMetrics, error) { var openMetrics OpenMetrics - level.Debug(c.logger).Log("msg", "Fetching openMetrics") + c.logger.Debug("Fetching openMetrics") resp, err := c.FetchHTTP(openMetricsEndpoint) if err != nil { if err.(*APIError).status == 404 { @@ -23,7 +19,10 @@ func (c *Client) FetchOpenMetrics() (OpenMetrics, error) { return openMetrics, err } - level.Debug(c.logger).Log("msg", "OpenMetrics from Artifactory", "body", string(resp.Body)) + c.logger.Debug( + "OpenMetrics from Artifactory", + "body", string(resp.Body), + ) openMetrics.NodeId = resp.NodeId openMetrics.PromMetrics = string(resp.Body) diff --git a/artifactory/replication.go b/artifactory/replication.go index b4773a5..4a4b0e5 100644 --- a/artifactory/replication.go +++ b/artifactory/replication.go @@ -3,8 +3,6 @@ package artifactory import ( "encoding/json" "fmt" - - "github.com/go-kit/log/level" ) const replicationEndpoint = "replications" @@ -38,7 +36,7 @@ type ReplicationStatus struct { // FetchReplications makes the API call to replication endpoint and returns []Replication func (c *Client) FetchReplications() (Replications, error) { var replications Replications - level.Debug(c.logger).Log("msg", "Fetching replications stats") + c.logger.Debug("Fetching replications stats") resp, err := c.FetchHTTP(replicationEndpoint) if err != nil { if err.(*APIError).status == 404 { @@ -49,7 +47,7 @@ func (c *Client) FetchReplications() (Replications, error) { replications.NodeId = resp.NodeId if err := json.Unmarshal(resp.Body, &replications.Replications); err != nil { - level.Error(c.logger).Log("msg", "There was an issue when try to unmarshal replication respond") + c.logger.Error("There was an issue when try to unmarshal replication respond") return replications, &UnmarshalError{ message: err.Error(), endpoint: replicationEndpoint, @@ -57,7 +55,7 @@ func (c *Client) FetchReplications() (Replications, error) { } if c.optionalMetrics.ReplicationStatus { - level.Debug(c.logger).Log("msg", "Fetching replications status") + c.logger.Debug("Fetching replications status") for i, replication := range replications.Replications { var status ReplicationStatus if replication.Enabled { @@ -66,7 +64,7 @@ func (c *Client) FetchReplications() (Replications, error) { return replications, err } if err := json.Unmarshal(statusResp.Body, &status); err != nil { - level.Error(c.logger).Log("msg", "There was an issue when try to unmarshal replication status respond") + c.logger.Error("There was an issue when try to unmarshal replication status respond") return replications, &UnmarshalError{ message: err.Error(), endpoint: fmt.Sprintf("%s/%s", replicationStatusEndpoint, replication.RepoKey), diff --git a/artifactory/security.go b/artifactory/security.go index 9e75f2a..5adef03 100644 --- a/artifactory/security.go +++ b/artifactory/security.go @@ -2,8 +2,6 @@ package artifactory import ( "encoding/json" - - "github.com/go-kit/log/level" ) const ( @@ -24,14 +22,14 @@ type Users struct { // FetchUsers makes the API call to users endpoint and returns []User func (c *Client) FetchUsers() (Users, error) { var users Users - level.Debug(c.logger).Log("msg", "Fetching users stats") + c.logger.Debug("Fetching users stats") resp, err := c.FetchHTTP(usersEndpoint) if err != nil { return users, err } users.NodeId = resp.NodeId if err := json.Unmarshal(resp.Body, &users.Users); err != nil { - level.Error(c.logger).Log("msg", "There was an issue when try to unmarshal users respond") + c.logger.Error("There was an issue when try to unmarshal users respond") return users, &UnmarshalError{ message: err.Error(), endpoint: usersEndpoint, @@ -53,14 +51,14 @@ type Groups struct { // FetchGroups makes the API call to groups endpoint and returns []Group func (c *Client) FetchGroups() (Groups, error) { var groups Groups - level.Debug(c.logger).Log("msg", "Fetching groups stats") + c.logger.Debug("Fetching groups stats") resp, err := c.FetchHTTP(groupsEndpoint) if err != nil { return groups, err } groups.NodeId = resp.NodeId if err := json.Unmarshal(resp.Body, &groups.Groups); err != nil { - level.Error(c.logger).Log("msg", "There was an issue when try to unmarshal groups respond") + c.logger.Error("There was an issue when try to unmarshal groups respond") return groups, &UnmarshalError{ message: err.Error(), endpoint: groupsEndpoint, diff --git a/artifactory/storageinfo.go b/artifactory/storageinfo.go index dff0001..2ec5780 100644 --- a/artifactory/storageinfo.go +++ b/artifactory/storageinfo.go @@ -2,8 +2,6 @@ package artifactory import ( "encoding/json" - - "github.com/go-kit/log/level" ) const ( @@ -43,14 +41,14 @@ type StorageInfo struct { // FetchStorageInfo makes the API call to storageinfo endpoint and returns StorageInfo func (c *Client) FetchStorageInfo() (StorageInfo, error) { var storageInfo StorageInfo - level.Debug(c.logger).Log("msg", "Fetching storage info stats") + c.logger.Debug("Fetching storage info stats") resp, err := c.FetchHTTP(storageInfoEndpoint) if err != nil { return storageInfo, err } storageInfo.NodeId = resp.NodeId if err := json.Unmarshal(resp.Body, &storageInfo); err != nil { - level.Error(c.logger).Log("msg", "There was an issue when try to unmarshal storageInfo respond") + c.logger.Error("There was an issue when try to unmarshal storageInfo respond") return storageInfo, &UnmarshalError{ message: err.Error(), endpoint: storageInfoEndpoint, diff --git a/artifactory/system.go b/artifactory/system.go index 575f89f..d3f5fb4 100644 --- a/artifactory/system.go +++ b/artifactory/system.go @@ -2,8 +2,6 @@ package artifactory import ( "encoding/json" - - "github.com/go-kit/log/level" ) const ( @@ -20,7 +18,7 @@ type HealthStatus struct { // FetchHealth returns true if the ping endpoint returns "OK" func (c *Client) FetchHealth() (HealthStatus, error) { health := HealthStatus{Healthy: false} - level.Debug(c.logger).Log("msg", "Fetching health stats") + c.logger.Debug("Fetching health stats") resp, err := c.FetchHTTP(pingEndpoint) if err != nil { return health, err @@ -28,7 +26,7 @@ func (c *Client) FetchHealth() (HealthStatus, error) { health.NodeId = resp.NodeId bodyString := string(resp.Body) if bodyString == "OK" { - level.Debug(c.logger).Log("msg", "System ping returned OK") + c.logger.Debug("System ping returned OK") health.Healthy = true return health, nil } @@ -47,14 +45,14 @@ type BuildInfo struct { // FetchBuildInfo makes the API call to version endpoint and returns BuildInfo func (c *Client) FetchBuildInfo() (BuildInfo, error) { var buildInfo BuildInfo - level.Debug(c.logger).Log("msg", "Fetching build stats") + c.logger.Debug("Fetching build stats") resp, err := c.FetchHTTP(versionEndpoint) if err != nil { return buildInfo, err } buildInfo.NodeId = resp.NodeId if err := json.Unmarshal(resp.Body, &buildInfo); err != nil { - level.Error(c.logger).Log("msg", "There was an issue when try to unmarshal buildInfo respond") + c.logger.Error("There was an issue when try to unmarshal buildInfo respond") return buildInfo, &UnmarshalError{ message: err.Error(), endpoint: versionEndpoint, @@ -74,14 +72,14 @@ type LicenseInfo struct { // FetchLicense makes the API call to license endpoint and returns LicenseInfo func (c *Client) FetchLicense() (LicenseInfo, error) { var licenseInfo LicenseInfo - level.Debug(c.logger).Log("msg", "Fetching license stats") + c.logger.Debug("Fetching license stats") resp, err := c.FetchHTTP(licenseEndpoint) if err != nil { return licenseInfo, err } licenseInfo.NodeId = resp.NodeId if err := json.Unmarshal(resp.Body, &licenseInfo); err != nil { - level.Error(c.logger).Log("msg", "There was an issue when try to unmarshal licenseInfo respond") + c.logger.Error("There was an issue when try to unmarshal licenseInfo respond") return licenseInfo, &UnmarshalError{ message: err.Error(), endpoint: licenseEndpoint, diff --git a/artifactory/utils.go b/artifactory/utils.go index 9a90caa..5b954b2 100644 --- a/artifactory/utils.go +++ b/artifactory/utils.go @@ -6,8 +6,12 @@ import ( "fmt" "io/ioutil" "net/http" + "slices" +) - "github.com/go-kit/log/level" +const ( + logMsgErrAPICall = "There was an error making API call" + logMsgErrUnmarshall = "There was an error when trying to unmarshal the API Error" ) // APIErrors represents Artifactory API Error response @@ -20,10 +24,28 @@ type ApiResponse struct { NodeId string } +var ( + httpSuccCodes = []int{ // https://go.dev/src/net/http/status.go + http.StatusOK, // 200 + http.StatusCreated, // 201 + http.StatusAccepted, // 202 + http.StatusNonAuthoritativeInfo, // 203 + http.StatusNoContent, // 204 + http.StatusResetContent, // 205 + http.StatusPartialContent, // 206 + http.StatusMultiStatus, // 207 + http.StatusAlreadyReported, // 208 + http.StatusIMUsed, // 226 + } +) + func (c *Client) makeRequest(method string, path string, body []byte) (*http.Response, error) { req, err := http.NewRequest(method, path, bytes.NewBuffer(body)) if err != nil { - level.Error(c.logger).Log("msg", "There was an error creating request", "err", err.Error()) + c.logger.Error( + "There was an error creating request", + "err", err.Error(), + ) return nil, err } switch c.authMethod { @@ -37,57 +59,88 @@ func (c *Client) makeRequest(method string, path string, body []byte) (*http.Res return c.client.Do(req) } +func (c *Client) procRespErr(resp *http.Response, fPath string) (*ApiResponse, error) { + var apiErrors APIErrors + bodyBytes, _ := ioutil.ReadAll(resp.Body) + if err := json.Unmarshal(bodyBytes, &apiErrors); err != nil { + c.logger.Error( + logMsgErrUnmarshall, + "err", err.Error(), + ) + return nil, &UnmarshalError{ + message: err.Error(), + endpoint: fPath, + } + } + c.logger.Error( + logMsgErrAPICall, + "endpoint", fPath, + "err", fmt.Sprintf("%v", apiErrors.Errors), + "status", resp.StatusCode, + ) + return nil, &APIError{ + message: fmt.Sprintf("%v", apiErrors.Errors), + endpoint: fPath, + // status: resp.StatusCode, // Maybe it would be worth returning it too? As with http.StatusNotFound. + } +} + // FetchHTTP is a wrapper function for making all Get API calls func (c *Client) FetchHTTP(path string) (*ApiResponse, error) { var response ApiResponse fullPath := fmt.Sprintf("%s/api/%s", c.URI, path) - level.Debug(c.logger).Log("msg", "Fetching http", "path", fullPath) + c.logger.Debug( + "Fetching http", + "path", fullPath, + ) resp, err := c.makeRequest("GET", fullPath, nil) if err != nil { - level.Error(c.logger).Log("msg", "There was an error making API call", "endpoint", fullPath, "err", err.Error()) + c.logger.Error( + logMsgErrAPICall, + "endpoint", fullPath, + "err", err.Error(), + ) return nil, err } response.NodeId = resp.Header.Get("x-artifactory-node-id") defer resp.Body.Close() - if resp.StatusCode == 404 { + if resp.StatusCode == http.StatusNotFound { var apiErrors APIErrors bodyBytes, _ := ioutil.ReadAll(resp.Body) if err := json.Unmarshal(bodyBytes, &apiErrors); err != nil { - level.Error(c.logger).Log("msg", "There was an error when trying to unmarshal the API Error", "err", err) + c.logger.Error( + logMsgErrUnmarshall, + "err", err, + ) return nil, &UnmarshalError{ message: err.Error(), endpoint: fullPath, } } - level.Warn(c.logger).Log("msg", "The endpoint does not exist", "endpoint", fullPath, "err", fmt.Sprintf("%v", apiErrors.Errors), "status", 404) + c.logger.Warn( + "The endpoint does not exist", + "endpoint", fullPath, + "err", fmt.Sprintf("%v", apiErrors.Errors), + "status", http.StatusNotFound, + ) return nil, &APIError{ message: fmt.Sprintf("%v", apiErrors.Errors), endpoint: fullPath, - status: 404, + status: http.StatusNotFound, } } - if !(resp.StatusCode >= 200 && resp.StatusCode < 300) { - var apiErrors APIErrors - bodyBytes, _ := ioutil.ReadAll(resp.Body) - if err := json.Unmarshal(bodyBytes, &apiErrors); err != nil { - level.Error(c.logger).Log("msg", "There was an error when trying to unmarshal the API Error", "err", err) - return nil, &UnmarshalError{ - message: err.Error(), - endpoint: fullPath, - } - } - level.Error(c.logger).Log("msg", "There was an error making API call", "endpoint", fullPath, "err", fmt.Sprintf("%v", apiErrors.Errors), "status") - return nil, &APIError{ - message: fmt.Sprintf("%v", apiErrors.Errors), - endpoint: fullPath, - } + if !slices.Contains(httpSuccCodes, resp.StatusCode) { + return c.procRespErr(resp, fullPath) } bodyBytes, err := ioutil.ReadAll(resp.Body) if err != nil { - level.Error(c.logger).Log("msg", "There was an error reading response body", "err", err.Error()) + c.logger.Error( + "There was an error reading response body", + "err", err.Error(), + ) return nil, err } response.Body = bodyBytes @@ -99,33 +152,31 @@ func (c *Client) FetchHTTP(path string) (*ApiResponse, error) { func (c *Client) QueryAQL(query []byte) (*ApiResponse, error) { var response ApiResponse fullPath := fmt.Sprintf("%s/api/search/aql", c.URI) - level.Debug(c.logger).Log("msg", "Running AQL query", "path", fullPath) + c.logger.Debug( + "Running AQL query", + "path", fullPath, + ) resp, err := c.makeRequest("POST", fullPath, query) if err != nil { - level.Error(c.logger).Log("msg", "There was an error making API call", "endpoint", fullPath, "err", err.Error()) + c.logger.Error( + logMsgErrAPICall, + "endpoint", fullPath, + "err", err.Error(), + ) return nil, err } response.NodeId = resp.Header.Get("x-artifactory-node-id") defer resp.Body.Close() - if !(resp.StatusCode >= 200 && resp.StatusCode < 300) { - var apiErrors APIErrors - bodyBytes, _ := ioutil.ReadAll(resp.Body) - if err := json.Unmarshal(bodyBytes, &apiErrors); err != nil { - return nil, &UnmarshalError{ - message: err.Error(), - endpoint: fullPath, - } - } - level.Error(c.logger).Log("msg", "There was an error making API call", "endpoint", fullPath, "err", fmt.Sprintf("%v", apiErrors.Errors), "status") - return nil, &APIError{ - message: fmt.Sprintf("%v", apiErrors.Errors), - endpoint: fullPath, - } + if !slices.Contains(httpSuccCodes, resp.StatusCode) { + return c.procRespErr(resp, fullPath) } bodyBytes, err := ioutil.ReadAll(resp.Body) if err != nil { - level.Error(c.logger).Log("msg", "There was an error reading response body", "err", err.Error()) + c.logger.Error( + "There was an error reading response body", + "err", err.Error(), + ) return nil, err } response.Body = bodyBytes diff --git a/artifactory_exporter.go b/artifactory_exporter.go index 9a754ba..6eba244 100644 --- a/artifactory_exporter.go +++ b/artifactory_exporter.go @@ -5,32 +5,46 @@ import ( "net/http" "os" - "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/prometheus/common/log" "github.com/prometheus/common/version" "github.com/peimanja/artifactory_exporter/collector" "github.com/peimanja/artifactory_exporter/config" + "github.com/peimanja/artifactory_exporter/logger" ) func main() { conf, err := config.NewConfig() if err != nil { - log.Errorf("Error creating the config. err: %s", err) + logger.New(logger.EmptyConfig).Error( + "Error creating the config.", + "err", err.Error(), + ) os.Exit(1) } exporter, err := collector.NewExporter(conf) if err != nil { - level.Error(conf.Logger).Log("msg", "Error creating an exporter", "err", err) + conf.Logger.Error( + "Error creating an exporter", + "err", err.Error(), + ) os.Exit(1) } prometheus.MustRegister(exporter) - level.Info(conf.Logger).Log("msg", "Starting artifactory_exporter", "version", version.Info()) - level.Info(conf.Logger).Log("msg", "Build context", "context", version.BuildContext()) - level.Info(conf.Logger).Log("msg", "Listening on address", "address", conf.ListenAddress) + conf.Logger.Info( + "Starting artifactory_exporter", + "version", version.Info(), + ) + conf.Logger.Info( + "Build context", + "context", version.BuildContext(), + ) + conf.Logger.Info( + "Listening on address", + "address", conf.ListenAddress, + ) http.Handle(conf.MetricsPath, promhttp.Handler()) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(` @@ -50,7 +64,10 @@ func main() { fmt.Fprintf(w, "OK") }) if err := http.ListenAndServe(conf.ListenAddress, nil); err != nil { - level.Error(conf.Logger).Log("msg", "Error starting HTTP server", "err", err) + conf.Logger.Error( + "Error starting HTTP server", + "err", err.Error(), + ) os.Exit(1) } } diff --git a/collector/artifacts.go b/collector/artifacts.go index 6a20610..f5ba64e 100644 --- a/collector/artifacts.go +++ b/collector/artifacts.go @@ -3,7 +3,7 @@ package collector import ( "encoding/json" "fmt" - "github.com/go-kit/log/level" + "github.com/prometheus/client_golang/prometheus" ) @@ -20,14 +20,21 @@ type artifactQueryResult struct { func (e *Exporter) findArtifacts(period string, queryType string) (artifactQueryResult, error) { var query string artifacts := artifactQueryResult{} - level.Debug(e.logger).Log("msg", "Finding all artifacts", "period", period, "queryType", queryType) + e.logger.Debug( + "Finding all artifacts", + "period", period, + "queryType", queryType, + ) switch queryType { case "created": query = fmt.Sprintf("items.find({\"modified\" : {\"$last\" : \"%s\"}}).include(\"name\", \"repo\")", period) case "downloaded": query = fmt.Sprintf("items.find({\"stat.downloaded\" : {\"$last\" : \"%s\"}}).include(\"name\", \"repo\")", period) default: - level.Error(e.logger).Log("err", "Query Type is not supported", "query", queryType) + e.logger.Error( + "Query Type is not supported", + "query", queryType, + ) return artifacts, fmt.Errorf("Query Type is not supported: %s", queryType) } resp, err := e.client.QueryAQL([]byte(query)) @@ -37,7 +44,12 @@ func (e *Exporter) findArtifacts(period string, queryType string) (artifactQuery } artifacts.NodeId = resp.NodeId if err := json.Unmarshal(resp.Body, &artifacts); err != nil { - level.Warn(e.logger).Log("msg", "There was an error when trying to unmarshal AQL response", "queryType", queryType, "period", period, "error", err) + e.logger.Warn( + "There was an error when trying to unmarshal AQL response", + "queryType", queryType, + "period", period, + "error", err.Error(), + ) e.jsonParseFailures.Inc() return artifacts, err } @@ -117,22 +129,64 @@ func (e *Exporter) exportArtifacts(repoSummaries []repoSummary, ch chan<- promet for metricName, metric := range artifactsMetrics { switch metricName { case "created1m": - level.Debug(e.logger).Log("msg", "Registering metric", "metric", metricName, "repo", repoSummary.Name, "type", repoSummary.Type, "package_type", repoSummary.PackageType, "value", repoSummary.TotalCreate1m) + e.logger.Debug( + logDbgMsgRegMetric, + "metric", metricName, + "repo", repoSummary.Name, + "type", repoSummary.Type, + "package_type", repoSummary.PackageType, + "value", repoSummary.TotalCreate1m, + ) ch <- prometheus.MustNewConstMetric(metric, prometheus.GaugeValue, repoSummary.TotalCreate1m, repoSummary.Name, repoSummary.Type, repoSummary.PackageType, repoSummary.NodeId) case "created5m": - level.Debug(e.logger).Log("msg", "Registering metric", "metric", metricName, "repo", repoSummary.Name, "type", repoSummary.Type, "package_type", repoSummary.PackageType, "value", repoSummary.TotalCreated5m) + e.logger.Debug( + logDbgMsgRegMetric, + "metric", metricName, + "repo", repoSummary.Name, + "type", repoSummary.Type, + "package_type", repoSummary.PackageType, + "value", repoSummary.TotalCreated5m, + ) ch <- prometheus.MustNewConstMetric(metric, prometheus.GaugeValue, repoSummary.TotalCreated5m, repoSummary.Name, repoSummary.Type, repoSummary.PackageType, repoSummary.NodeId) case "created15m": - level.Debug(e.logger).Log("msg", "Registering metric", "metric", metricName, "repo", repoSummary.Name, "type", repoSummary.Type, "package_type", repoSummary.PackageType, "value", repoSummary.TotalCreated15m) + e.logger.Debug( + logDbgMsgRegMetric, + "metric", metricName, + "repo", repoSummary.Name, + "type", repoSummary.Type, + "package_type", repoSummary.PackageType, + "value", repoSummary.TotalCreated15m, + ) ch <- prometheus.MustNewConstMetric(metric, prometheus.GaugeValue, repoSummary.TotalCreated15m, repoSummary.Name, repoSummary.Type, repoSummary.PackageType, repoSummary.NodeId) case "downloaded1m": - level.Debug(e.logger).Log("msg", "Registering metric", "metric", metricName, "repo", repoSummary.Name, "type", repoSummary.Type, "package_type", repoSummary.PackageType, "value", repoSummary.TotalDownloaded1m) + e.logger.Debug( + logDbgMsgRegMetric, + "metric", metricName, + "repo", repoSummary.Name, + "type", repoSummary.Type, + "package_type", repoSummary.PackageType, + "value", repoSummary.TotalDownloaded1m, + ) ch <- prometheus.MustNewConstMetric(metric, prometheus.GaugeValue, repoSummary.TotalDownloaded1m, repoSummary.Name, repoSummary.Type, repoSummary.PackageType, repoSummary.NodeId) case "downloaded5m": - level.Debug(e.logger).Log("msg", "Registering metric", "metric", metricName, "repo", repoSummary.Name, "type", repoSummary.Type, "package_type", repoSummary.PackageType, "value", repoSummary.TotalDownloaded5m) + e.logger.Debug( + logDbgMsgRegMetric, + "metric", metricName, + "repo", repoSummary.Name, + "type", repoSummary.Type, + "package_type", repoSummary.PackageType, + "value", repoSummary.TotalDownloaded5m, + ) ch <- prometheus.MustNewConstMetric(metric, prometheus.GaugeValue, repoSummary.TotalDownloaded5m, repoSummary.Name, repoSummary.Type, repoSummary.PackageType, repoSummary.NodeId) case "downloaded15m": - level.Debug(e.logger).Log("msg", "Registering metric", "metric", metricName, "repo", repoSummary.Name, "type", repoSummary.Type, "package_type", repoSummary.PackageType, "value", repoSummary.TotalDownloaded15m) + e.logger.Debug( + logDbgMsgRegMetric, + "metric", metricName, + "repo", repoSummary.Name, + "type", repoSummary.Type, + "package_type", repoSummary.PackageType, + "value", repoSummary.TotalDownloaded15m, + ) ch <- prometheus.MustNewConstMetric(metric, prometheus.GaugeValue, repoSummary.TotalDownloaded15m, repoSummary.Name, repoSummary.Type, repoSummary.PackageType, repoSummary.NodeId) } } diff --git a/collector/common.go b/collector/common.go new file mode 100644 index 0000000..24bd2f7 --- /dev/null +++ b/collector/common.go @@ -0,0 +1,5 @@ +package collector + +const ( + logDbgMsgRegMetric = "Registering metric" +) diff --git a/collector/exporter.go b/collector/exporter.go index 638db63..10c2720 100644 --- a/collector/exporter.go +++ b/collector/exporter.go @@ -1,9 +1,9 @@ package collector import ( + "log/slog" "sync" - "github.com/go-kit/log" "github.com/prometheus/client_golang/prometheus" "github.com/peimanja/artifactory_exporter/artifactory" @@ -19,7 +19,7 @@ type Exporter struct { up prometheus.Gauge totalScrapes, totalAPIErrors, jsonParseFailures prometheus.Counter - logger log.Logger + logger *slog.Logger } // NewExporter returns an initialized Exporter. diff --git a/collector/federation.go b/collector/federation.go index 64f4462..aafd2a0 100644 --- a/collector/federation.go +++ b/collector/federation.go @@ -1,7 +1,6 @@ package collector import ( - "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" ) @@ -16,12 +15,19 @@ func (e *Exporter) exportFederationMirrorLags(ch chan<- prometheus.Metric) error } if len(federationMirrorLags.MirrorLags) == 0 { - level.Debug(e.logger).Log("msg", "No federation mirror lags found") + e.logger.Debug("No federation mirror lags found") return nil } for _, mirrorLag := range federationMirrorLags.MirrorLags { - level.Debug(e.logger).Log("msg", "Registering metric", "metric", "federationMirrorLag", "repo", mirrorLag.LocalRepoKey, "remote_url", mirrorLag.RemoteUrl, "remote_name", mirrorLag.RemoteRepoKey, "value", mirrorLag.LagInMS) + e.logger.Debug( + "Registering metric", + "metric", "federationMirrorLag", + "repo", mirrorLag.LocalRepoKey, + "remote_url", mirrorLag.RemoteUrl, + "remote_name", mirrorLag.RemoteRepoKey, + "value", mirrorLag.LagInMS, + ) ch <- prometheus.MustNewConstMetric(federationMetrics["mirrorLag"], prometheus.GaugeValue, float64(mirrorLag.LagInMS), mirrorLag.LocalRepoKey, mirrorLag.RemoteUrl, mirrorLag.RemoteRepoKey, federationMirrorLags.NodeId) } @@ -37,12 +43,19 @@ func (e *Exporter) exportFederationUnavailableMirrors(ch chan<- prometheus.Metri } if len(federationUnavailableMirrors.UnavailableMirrors) == 0 { - level.Debug(e.logger).Log("msg", "No federation unavailable mirrors found") + e.logger.Debug("No federation unavailable mirrors found") return nil } for _, unavailableMirror := range federationUnavailableMirrors.UnavailableMirrors { - level.Debug(e.logger).Log("msg", "Registering metric", "metric", "federationUnavailableMirror", "status", unavailableMirror.Status, "repo", unavailableMirror.LocalRepoKey, "remote_url", unavailableMirror.RemoteUrl, "remote_name", unavailableMirror.RemoteRepoKey) + e.logger.Debug( + "Registering metric", + "metric", "federationUnavailableMirror", + "status", unavailableMirror.Status, + "repo", unavailableMirror.LocalRepoKey, + "remote_url", unavailableMirror.RemoteUrl, + "remote_name", unavailableMirror.RemoteRepoKey, + ) ch <- prometheus.MustNewConstMetric(federationMetrics["unavailableMirror"], prometheus.GaugeValue, 1, unavailableMirror.Status, unavailableMirror.LocalRepoKey, unavailableMirror.RemoteUrl, unavailableMirror.RemoteRepoKey, federationUnavailableMirrors.NodeId) } diff --git a/collector/openMetrics.go b/collector/openMetrics.go index d6727b9..c72c3b9 100644 --- a/collector/openMetrics.go +++ b/collector/openMetrics.go @@ -3,30 +3,29 @@ package collector import ( "strings" - "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" ioPrometheusClient "github.com/prometheus/client_model/go" "github.com/prometheus/common/expfmt" ) func (e *Exporter) exportOpenMetrics(ch chan<- prometheus.Metric) error { - // Fetch Open Metrics openMetrics, err := e.client.FetchOpenMetrics() if err != nil { - level.Error(e.logger).Log("msg", "There was an issue when try to fetch openMetrics") + e.logger.Error("There was an issue when try to fetch openMetrics") e.totalAPIErrors.Inc() return err } - level.Debug(e.logger).Log("msg", "OpenMetrics from Artifactory util", "body", openMetrics.PromMetrics) + e.logger.Debug( + "OpenMetrics from Artifactory util", + "body", openMetrics.PromMetrics, + ) - // assign openMetrics.Metric to a string variable openMetricsString := openMetrics.PromMetrics parser := expfmt.TextParser{} metrics, err := parser.TextToMetricFamilies(strings.NewReader(openMetricsString)) if err != nil { - // handle the error return err } diff --git a/collector/replication.go b/collector/replication.go index ed65b28..cc79e9a 100644 --- a/collector/replication.go +++ b/collector/replication.go @@ -3,7 +3,6 @@ package collector import ( "strings" - "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" ) @@ -11,12 +10,15 @@ func (e *Exporter) exportReplications(ch chan<- prometheus.Metric) error { // Fetch Replications stats replications, err := e.client.FetchReplications() if err != nil { - level.Error(e.logger).Log("msg", "Couldn't scrape Artifactory when fetching replications", "err", err) + e.logger.Error( + "Couldn't scrape Artifactory when fetching replications", + "err", err.Error(), + ) e.totalAPIErrors.Inc() return err } if len(replications.Replications) == 0 { - level.Debug(e.logger).Log("msg", "No replications stats found") + e.logger.Debug("No replications stats found") return nil } for _, replication := range replications.Replications { @@ -29,7 +31,16 @@ func (e *Exporter) exportReplications(ch chan<- prometheus.Metric) error { rURL := strings.ToLower(replication.URL) cronExp := replication.CronExp status := replication.Status - level.Debug(e.logger).Log("msg", "Registering metric", "metric", metricName, "repo", replication.RepoKey, "type", rType, "url", rURL, "cron", cronExp, "status", status, "value", enabled) + e.logger.Debug( + "Registering metric", + "metric", metricName, + "repo", replication.RepoKey, + "type", rType, + "url", rURL, + "cron", cronExp, + "status", status, + "value", enabled, + ) ch <- prometheus.MustNewConstMetric(metric, prometheus.GaugeValue, enabled, repo, rType, rURL, cronExp, status, replications.NodeId) } } diff --git a/collector/security.go b/collector/security.go index 343964a..a300e24 100644 --- a/collector/security.go +++ b/collector/security.go @@ -3,7 +3,6 @@ package collector import ( "fmt" - "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" "github.com/peimanja/artifactory_exporter/artifactory" @@ -18,7 +17,7 @@ type user struct { type realmUserCounts map[string]float64 func (e *Exporter) countUsersPerRealm(users []artifactory.User) realmUserCounts { - level.Debug(e.logger).Log("msg", "Counting users") + e.logger.Debug("Counting users") usersPerRealm := realmUserCounts{} for _, user := range users { usersPerRealm[user.Realm]++ @@ -30,7 +29,10 @@ func (e *Exporter) exportUsersCount(metricName string, metric *prometheus.Desc, // Fetch Artifactory Users users, err := e.client.FetchUsers() if err != nil { - level.Error(e.logger).Log("msg", "Couldn't scrape Artifactory when fetching security/users", "err", err) + e.logger.Error( + "Couldn't scrape Artifactory when fetching security/users", + "err", err.Error(), + ) e.totalAPIErrors.Inc() return err } @@ -43,11 +45,16 @@ func (e *Exporter) exportUsersCount(metricName string, metric *prometheus.Desc, if totalUserCount == 0 { e.jsonParseFailures.Inc() - level.Error(e.logger).Log("err", "There was an issue getting users respond") + e.logger.Error("There was an issue getting users respond") return fmt.Errorf("There was an issue getting users respond") } for realm, count := range usersPerRealm { - level.Debug(e.logger).Log("msg", "Registering metric", "metric", metricName, "realm", realm, "value", count) + e.logger.Debug( + "Registering metric", + "metric", metricName, + "realm", realm, + "value", count, + ) ch <- prometheus.MustNewConstMetric(metric, prometheus.GaugeValue, count, realm, users.NodeId) } return nil @@ -62,12 +69,19 @@ func (e *Exporter) exportGroups(metricName string, metric *prometheus.Desc, ch c // Fetch Artifactory groups groups, err := e.client.FetchGroups() if err != nil { - level.Error(e.logger).Log("msg", "Couldn't scrape Artifactory when fetching security/users", "err", err) + e.logger.Error( + "Couldn't scrape Artifactory when fetching security/users", + "err", err.Error(), + ) e.totalAPIErrors.Inc() return err } - level.Debug(e.logger).Log("msg", "Registering metric", "metric", metricName, "value", float64(len(groups.Groups))) + e.logger.Debug( + "Registering metric", + "metric", metricName, + "value", float64(len(groups.Groups)), // What for log as float?Int is not precise enough? + ) ch <- prometheus.MustNewConstMetric(metric, prometheus.GaugeValue, float64(len(groups.Groups)), groups.NodeId) return nil } diff --git a/collector/storage.go b/collector/storage.go index d8750f2..9f77ded 100644 --- a/collector/storage.go +++ b/collector/storage.go @@ -3,13 +3,12 @@ package collector import ( "strings" - "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" "github.com/peimanja/artifactory_exporter/artifactory" ) -const calculateValueError = "There was an issue calculating the value" +const msgErrCalcVal = "There was an issue calculating the value" func (e *Exporter) exportCount(metricName string, metric *prometheus.Desc, count string, nodeId string, ch chan<- prometheus.Metric) { if count == "" { @@ -19,10 +18,18 @@ func (e *Exporter) exportCount(metricName string, metric *prometheus.Desc, count value, err := e.removeCommas(count) if err != nil { e.jsonParseFailures.Inc() - level.Error(e.logger).Log("msg", calculateValueError, "metric", metricName, "err", err) + e.logger.Error( + msgErrCalcVal, + "metric", metricName, + "err", err.Error(), + ) return } - level.Debug(e.logger).Log("msg", "Registering metric", "metric", metricName, "value", value) + e.logger.Debug( + logDbgMsgRegMetric, + "metric", metricName, + "value", value, + ) ch <- prometheus.MustNewConstMetric(metric, prometheus.GaugeValue, value, nodeId) } @@ -34,10 +41,18 @@ func (e *Exporter) exportSize(metricName string, metric *prometheus.Desc, size s value, err := e.bytesConverter(size) if err != nil { e.jsonParseFailures.Inc() - level.Error(e.logger).Log("msg", calculateValueError, "metric", metricName, "err", err) + e.logger.Error( + msgErrCalcVal, + "metric", metricName, + "err", err.Error(), + ) return } - level.Debug(e.logger).Log("msg", "Registering metric", "metric", metricName, "value", value) + e.logger.Debug( + logDbgMsgRegMetric, + "metric", metricName, + "value", value, + ) ch <- prometheus.MustNewConstMetric(metric, prometheus.GaugeValue, value, nodeId) } @@ -49,10 +64,18 @@ func (e *Exporter) exportFilestore(metricName string, metric *prometheus.Desc, s value, err := e.bytesConverter(size) if err != nil { e.jsonParseFailures.Inc() - level.Debug(e.logger).Log("msg", calculateValueError, "metric", metricName, "err", err) + e.logger.Warn( + msgErrCalcVal, + "metric", metricName, + "err", err.Error(), + ) return } - level.Debug(e.logger).Log("msg", "Registering metric", "metric", metricName, "value", value) + e.logger.Debug( + logDbgMsgRegMetric, + "metric", metricName, + "value", value, + ) ch <- prometheus.MustNewConstMetric(metric, prometheus.GaugeValue, value, fileStoreType, fileStoreDir, nodeId) } @@ -78,7 +101,7 @@ func (e *Exporter) extractRepo(storageInfo artifactory.StorageInfo) ([]repoSumma var err error rs := repoSummary{} repoSummaryList := []repoSummary{} - level.Debug(e.logger).Log("msg", "Extracting repo summaries") + e.logger.Debug("Extracting repo summaries") for _, repo := range storageInfo.RepositoriesSummaryList { if repo.RepoKey == "TOTAL" { continue @@ -91,7 +114,11 @@ func (e *Exporter) extractRepo(storageInfo artifactory.StorageInfo) ([]repoSumma rs.PackageType = strings.ToLower(repo.PackageType) rs.UsedSpace, err = e.bytesConverter(repo.UsedSpace) if err != nil { - level.Debug(e.logger).Log("msg", "There was an issue parsing repo UsedSpace", "repo", repo.RepoKey, "err", err) + e.logger.Warn( + "There was an issue parsing repo UsedSpace", + "repo", repo.RepoKey, + "err", err.Error(), + ) e.jsonParseFailures.Inc() return repoSummaryList, err } @@ -100,7 +127,11 @@ func (e *Exporter) extractRepo(storageInfo artifactory.StorageInfo) ([]repoSumma } else { rs.Percentage, err = e.removeCommas(repo.Percentage) if err != nil { - level.Debug(e.logger).Log("msg", "There was an issue parsing repo Percentage", "repo", repo.RepoKey, "err", err) + e.logger.Warn( + "There was an issue parsing repo Percentage", + "repo", repo.RepoKey, + "err", err.Error(), + ) e.jsonParseFailures.Inc() return repoSummaryList, err } @@ -116,19 +147,54 @@ func (e *Exporter) exportRepo(repoSummaries []repoSummary, ch chan<- prometheus. for metricName, metric := range storageMetrics { switch metricName { case "repoUsed": - level.Debug(e.logger).Log("msg", "Registering metric", "metric", metricName, "repo", repoSummary.Name, "type", repoSummary.Type, "package_type", repoSummary.PackageType, "value", repoSummary.UsedSpace) + e.logger.Debug( + logDbgMsgRegMetric, + "metric", metricName, + "repo", repoSummary.Name, + "type", repoSummary.Type, + "package_type", repoSummary.PackageType, + "value", repoSummary.UsedSpace, + ) ch <- prometheus.MustNewConstMetric(metric, prometheus.GaugeValue, repoSummary.UsedSpace, repoSummary.Name, repoSummary.Type, repoSummary.PackageType, repoSummary.NodeId) case "repoFolders": - level.Debug(e.logger).Log("msg", "Registering metric", "metric", metricName, "repo", repoSummary.Name, "type", repoSummary.Type, "package_type", repoSummary.PackageType, "value", repoSummary.FoldersCount) + e.logger.Debug( + logDbgMsgRegMetric, + "metric", metricName, + "repo", repoSummary.Name, + "type", repoSummary.Type, + "package_type", repoSummary.PackageType, + "value", repoSummary.FoldersCount, + ) ch <- prometheus.MustNewConstMetric(metric, prometheus.GaugeValue, repoSummary.FoldersCount, repoSummary.Name, repoSummary.Type, repoSummary.PackageType, repoSummary.NodeId) case "repoItems": - level.Debug(e.logger).Log("msg", "Registering metric", "metric", metricName, "repo", repoSummary.Name, "type", repoSummary.Type, "package_type", repoSummary.PackageType, "value", repoSummary.ItemsCount) + e.logger.Debug( + logDbgMsgRegMetric, + "metric", metricName, + "repo", repoSummary.Name, + "type", repoSummary.Type, + "package_type", repoSummary.PackageType, + "value", repoSummary.ItemsCount, + ) ch <- prometheus.MustNewConstMetric(metric, prometheus.GaugeValue, repoSummary.ItemsCount, repoSummary.Name, repoSummary.Type, repoSummary.PackageType, repoSummary.NodeId) case "repoFiles": - level.Debug(e.logger).Log("msg", "Registering metric", "metric", metricName, "repo", repoSummary.Name, "type", repoSummary.Type, "package_type", repoSummary.PackageType, "value", repoSummary.FilesCount) + e.logger.Debug( + logDbgMsgRegMetric, + "metric", metricName, + "repo", repoSummary.Name, + "type", repoSummary.Type, + "package_type", repoSummary.PackageType, + "value", repoSummary.FilesCount, + ) ch <- prometheus.MustNewConstMetric(metric, prometheus.GaugeValue, repoSummary.FilesCount, repoSummary.Name, repoSummary.Type, repoSummary.PackageType, repoSummary.NodeId) case "repoPercentage": - level.Debug(e.logger).Log("msg", "Registering metric", "metric", metricName, "repo", repoSummary.Name, "type", repoSummary.Type, "package_type", repoSummary.PackageType, "value", repoSummary.Percentage) + e.logger.Debug( + logDbgMsgRegMetric, + "metric", metricName, + "repo", repoSummary.Name, + "type", repoSummary.Type, + "package_type", repoSummary.PackageType, + "value", repoSummary.Percentage, + ) ch <- prometheus.MustNewConstMetric(metric, prometheus.GaugeValue, repoSummary.Percentage, repoSummary.Name, repoSummary.Type, repoSummary.PackageType, repoSummary.NodeId) } } diff --git a/collector/system.go b/collector/system.go index d7c3cb9..774d91b 100644 --- a/collector/system.go +++ b/collector/system.go @@ -4,7 +4,6 @@ import ( "strings" "time" - "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" "github.com/peimanja/artifactory_exporter/artifactory" @@ -13,13 +12,19 @@ import ( func (e *Exporter) exportSystem(license artifactory.LicenseInfo, ch chan<- prometheus.Metric) error { health, err := e.client.FetchHealth() if err != nil { - level.Error(e.logger).Log("msg", "Couldn't scrape Artifactory when fetching system/ping", "err", err) + e.logger.Error( + "Couldn't scrape Artifactory when fetching system/ping", + "err", err.Error(), + ) e.totalAPIErrors.Inc() return err } buildInfo, err := e.client.FetchBuildInfo() if err != nil { - level.Error(e.logger).Log("msg", "Couldn't scrape Artifactory when fetching system/version", "err", err) + e.logger.Error( + "Couldn't scrape Artifactory when fetching system/version", + "err", err.Error(), + ) e.totalAPIErrors.Inc() return err } @@ -39,7 +44,10 @@ func (e *Exporter) exportSystem(license artifactory.LicenseInfo, ch chan<- prome validThrough = timeNow default: if validThroughTime, err := time.Parse("Jan 2, 2006", license.ValidThrough); err != nil { - level.Warn(e.logger).Log("msg", "Couldn't parse Artifactory license ValidThrough", "err", err) + e.logger.Warn( + "Couldn't parse Artifactory license ValidThrough", + "err", err.Error(), + ) validThrough = timeNow } else { validThrough = float64(validThroughTime.Unix()) diff --git a/collector/utils.go b/collector/utils.go index 07dfbc7..6764cc0 100644 --- a/collector/utils.go +++ b/collector/utils.go @@ -5,51 +5,79 @@ import ( "regexp" "strconv" "strings" +) - "github.com/go-kit/log/level" +const ( + mulKB = 1024 + mulMB = mulKB * 1024 + mulGB = mulMB * 1024 + mulTB = mulGB * 1024 ) +// A one-time regex compilation is far cheaper +// than repeating on each function entry. +// `MustCompile` is used because regex value is hardcoded +// i.e. may have been previously verified by the author. +var regNumber = regexp.MustCompile("[^0-9.]+") + func (e *Exporter) removeCommas(str string) (float64, error) { - level.Debug(e.logger).Log("msg", "Removing other characters to extract number from string") - reg, err := regexp.Compile("[^0-9.]+") - if err != nil { - return 0, err - } + e.logger.Debug("Removing other characters to extract number from string") + /* + * I am very concerned about the “magic” used here. + * The code does not in any way explain why this particular + * method of extracting content from the text was adopted. + * Kacper Perschke + */ strArray := strings.Fields(str) - strTrimmed := reg.ReplaceAllString(strArray[0], "") - convertedStr, err := strconv.ParseFloat(strTrimmed, 64) + strTrimmed := regNumber.ReplaceAllString(strArray[0], "") + num, err := strconv.ParseFloat(strTrimmed, 64) if err != nil { + e.logger.Debug( + "Had problem extracting number", + "string", str, + "err", err.Error(), + ) return 0, err } - level.Debug(e.logger).Log("msg", "Successfully converted string to number", "string", str, "number", convertedStr) - return convertedStr, nil + e.logger.Debug( + "Successfully converted string to number", + "string", str, + "number", num, + ) + return num, nil } func (e *Exporter) bytesConverter(str string) (float64, error) { - var bytesValue float64 - level.Debug(e.logger).Log("msg", "Converting size to bytes") + e.logger.Debug("Converting size to bytes") num, err := e.removeCommas(str) if err != nil { return 0, err } - + var bytesValue float64 if strings.Contains(str, "bytes") { bytesValue = num } else if strings.Contains(str, "KB") { - bytesValue = num * 1024 + bytesValue = num * mulKB } else if strings.Contains(str, "MB") { - bytesValue = num * 1024 * 1024 + bytesValue = num * mulMB } else if strings.Contains(str, "GB") { - bytesValue = num * 1024 * 1024 * 1024 + bytesValue = num * mulGB } else if strings.Contains(str, "TB") { - bytesValue = num * 1024 * 1024 * 1024 * 1024 + bytesValue = num * mulTB } else { return 0, fmt.Errorf("Could not convert %s to bytes", str) } - level.Debug(e.logger).Log("msg", "Successfully converted string to bytes", "string", str, "value", bytesValue) + e.logger.Debug( + "Successfully converted string to bytes", + "string", str, + "value", bytesValue, + ) return bytesValue, nil } +// b2f is a very interesting appendix. +// Something needs it, but what and why? +// It would be quite nice if it was written here why such a thing is needed. func b2f(b bool) float64 { if b { return 1 diff --git a/config/config.go b/config/config.go index 101dcce..ec07f80 100644 --- a/config/config.go +++ b/config/config.go @@ -2,18 +2,20 @@ package config import ( "fmt" + "log/slog" "net/url" "time" - "github.com/go-kit/log" "github.com/kelseyhightower/envconfig" - "github.com/prometheus/common/promlog" - "github.com/prometheus/common/promlog/flag" "github.com/prometheus/common/version" "gopkg.in/alecthomas/kingpin.v2" + + l "github.com/peimanja/artifactory_exporter/logger" ) var ( + flagLogFormat = kingpin.Flag(l.FormatFlagName, l.FormatFlagHelp).Default(l.FormatDefault).Enum(l.FormatsAvailable...) + flagLogLevel = kingpin.Flag(l.LevelFlagName, l.LevelFlagHelp).Default(l.LevelFlagName).Enum(l.LevelsAvailable...) listenAddress = kingpin.Flag("web.listen-address", "Address to listen on for web interface and telemetry.").Envar("WEB_LISTEN_ADDR").Default(":9531").String() metricsPath = kingpin.Flag("web.telemetry-path", "Path under which to expose metrics.").Envar("WEB_TELEMETRY_PATH").Default("/metrics").String() artiScrapeURI = kingpin.Flag("artifactory.scrape-uri", "URI on which to scrape JFrog Artifactory.").Envar("ARTI_SCRAPE_URI").Default("http://localhost:8081/artifactory").String() @@ -37,7 +39,7 @@ type OptionalMetrics struct { Artifacts bool ReplicationStatus bool FederationStatus bool - OpenMetrics bool + OpenMetrics bool } // Config represents all configuration options for running the Exporter. @@ -49,18 +51,15 @@ type Config struct { ArtiSSLVerify bool ArtiTimeout time.Duration OptionalMetrics OptionalMetrics - Logger log.Logger + Logger *slog.Logger } -// NewConfig Creates new Artifactory exporter Config +// NewConfig Creates Config for Artifactory exporter func NewConfig() (*Config, error) { - promlogConfig := &promlog.Config{} - flag.AddFlags(kingpin.CommandLine, promlogConfig) kingpin.HelpFlag.Short('h') kingpin.Version(version.Info() + " " + version.BuildContext()) kingpin.Parse() - logger := promlog.New(promlogConfig) var credentials Credentials err := envconfig.Process("", &credentials) @@ -96,6 +95,12 @@ func NewConfig() (*Config, error) { } } + logger := l.New( + l.Config{ + Format: *flagLogFormat, + Level: *flagLogLevel, + }, + ) return &Config{ ListenAddress: *listenAddress, MetricsPath: *metricsPath, diff --git a/go.mod b/go.mod index 02b58bc..05ce591 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/peimanja/artifactory_exporter -go 1.18 +go 1.21 require ( github.com/go-kit/log v0.1.0 @@ -18,11 +18,9 @@ require ( github.com/cespare/xxhash/v2 v2.1.1 // indirect github.com/go-logfmt/logfmt v0.5.0 // indirect github.com/golang/protobuf v1.4.3 // indirect - github.com/konsorten/go-windows-terminal-sequences v1.0.3 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/procfs v0.6.0 // indirect - github.com/sirupsen/logrus v1.6.0 // indirect golang.org/x/sys v0.2.0 // indirect google.golang.org/protobuf v1.26.0-rc.1 // indirect ) diff --git a/go.sum b/go.sum index 37af929..ea519df 100644 --- a/go.sum +++ b/go.sum @@ -53,7 +53,6 @@ github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8 github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -93,7 +92,6 @@ github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3x github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/logger/config.go b/logger/config.go new file mode 100644 index 0000000..35eb8e4 --- /dev/null +++ b/logger/config.go @@ -0,0 +1,46 @@ +package logger + +import "log/slog" + +type Config struct { + Format string + Level string +} + +const ( + FormatDefault = fmtTXT + FormatFlagHelp = "Output format of log messages. One of: [logfmt, json]" + FormatFlagName = "log.format" + fmtJSON = "json" + fmtTXT = "logfmt" +) +const ( + lvlFNameDebug = "debug" + lvlFNameInfo = "info" + lvlFNameWarn = "warn" + lvlFNameError = "error" + LevelDefault = lvlFNameInfo + LevelFlagHelp = "Only log messages with the given severity or above. One of: [debug, info, warn, error]" + LevelFlagName = "log.level" +) + +var ( + EmptyConfig = Config{} + FormatsAvailable = []string{ + fmtTXT, + fmtJSON, + } + LevelsAvailable = []string{ + // Deliberately not in alphabetical order, but according to the significance of the levels. + lvlFNameDebug, + lvlFNameInfo, + lvlFNameWarn, + lvlFNameError, + } + levelsFlagToSlog = map[string]slog.Level{ + "debug": slog.LevelDebug, + "info": slog.LevelInfo, + "warn": slog.LevelWarn, + "error": slog.LevelError, + } +) diff --git a/logger/doc.go b/logger/doc.go new file mode 100644 index 0000000..c6831d4 --- /dev/null +++ b/logger/doc.go @@ -0,0 +1,9 @@ +// Package logger wraps configuration of `log/slog`. +// +// It is used by "github.com/peimanja/artifactory_exporter/config" +// and is not expected to be used outside the artifactory exporter. +// +// To maintain backward compatibility, log to os.Stderr +// as already used "github.com/prometheus/common/promlog". + +package logger diff --git a/logger/slog.go b/logger/slog.go new file mode 100644 index 0000000..94f3a6f --- /dev/null +++ b/logger/slog.go @@ -0,0 +1,68 @@ +package logger + +import ( + "log/slog" + "os" +) + +// New returns configured instance of `log/slog` +// +// Expects configuration in the Config type structure. +// In the absence of appropriate information in the provided configuration, +// values `FormatDefault` or `LevelDefault` will be assumed. +func New(c Config) *slog.Logger { + + lvl := lvlFromConfig(c) + + switch lf := fmtFromConfig(c); lf { + case fmtTXT: + return newTXTLogger(lvl) + case fmtJSON: + return newJSONLogger(lvl) + default: + l := newTXTLogger(lvl) + l.Error("We should never have ended up here! Plase report an issue") + os.Exit(1) + } + + /* + * The following should never happen and is only there + * to satisfy the compiler's formal requirements. + */ + return newTXTLogger(lvl) +} + +func fmtFromConfig(c Config) string { + if c.Format != "" { + return c.Format + } + return FormatDefault +} + +func lvlFromConfig(c Config) slog.Level { + lvlFromFlag := LevelDefault + if c.Level != "" { + lvlFromFlag = c.Level + } + return levelsFlagToSlog[lvlFromFlag] +} + +func newJSONLogger(l slog.Level) *slog.Logger { + h := slog.NewJSONHandler( + os.Stderr, // Please read the explanation in the `doc.go` file. + &slog.HandlerOptions{ + Level: l, + }, + ) + return slog.New(h) +} + +func newTXTLogger(l slog.Level) *slog.Logger { + h := slog.NewTextHandler( + os.Stderr, // Please read the explanation in the `doc.go` file. + &slog.HandlerOptions{ + Level: l, + }, + ) + return slog.New(h) +}