Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch to slog #841

Merged
merged 1 commit into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,6 @@ jobs:
with:
go-version: stable

- name: Determine GOPATH
id: go
run: |
echo "go_path=$(go env GOPATH)" >> $GITHUB_OUTPUT
- name: Setup QEMU
uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0
with:
Expand Down Expand Up @@ -183,7 +178,6 @@ jobs:
args: ${{ github.ref_type == 'tag' && 'release' || 'build --snapshot' }} ${{ github.event_name == 'pull_request' && '--single-target' || '' }} --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GOPATH: ${{ steps.go.outputs.go_path }}
NGINX_GITHUB_TOKEN: ${{ secrets.NGINX_PAT }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_COMMUNITY }}
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_LOGIN }}
Expand Down
9 changes: 5 additions & 4 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,24 @@ linters-settings:
- name: unused-parameter
- name: var-declaration
- name: var-naming
errcheck:
exclude-functions:
- (github.com/go-kit/log.Logger).Log
govet:
enable-all: true
sloglint:
static-msg: true
key-naming-case: snake
linters:
enable:
- asasalint
- asciicheck
- bidichk
- contextcheck
- copyloopvar
- dupword
- durationcheck
- errcheck
- errchkjson
- errname
- errorlint
- exportloopref
- fatcontext
- forcetypeassert
- gocheckcompilerdirectives
Expand Down Expand Up @@ -74,6 +74,7 @@ linters:
- promlinter
- reassign
- revive
- sloglint
- staticcheck
- stylecheck
- tagalign
Expand Down
4 changes: 0 additions & 4 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,6 @@ builds:
goarch: arm
flags:
- -trimpath
gcflags:
- all=-trimpath={{.Env.GOPATH}}
asmflags:
- all=-trimpath={{.Env.GOPATH}}
ldflags:
- "-s -w -X github.com/prometheus/common/version.Version={{.Version}} -X github.com/prometheus/common/version.BuildDate={{.Date}} -X github.com/prometheus/common/version.Branch={{.Branch}} -X github.com/prometheus/common/version.BuildUser=goreleaser"

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ nginx-prometheus-exporter: ## Build nginx-prometheus-exporter binary
.PHONY: build-goreleaser
build-goreleaser: ## Build all binaries using GoReleaser
@goreleaser -v || (code=$$?; printf "\033[0;31mError\033[0m: there was a problem with GoReleaser. Follow the docs to install it https://goreleaser.com/install\n"; exit $$code)
GOPATH=$(shell go env GOPATH) goreleaser build --clean --snapshot
goreleaser build --clean --snapshot

.PHONY: lint
lint: ## Run linter
Expand Down
1 change: 0 additions & 1 deletion collector/helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ func TestMergeLabels(t *testing.T) {
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
if got := MergeLabels(tt.mapA, tt.mapB); !reflect.DeepEqual(got, tt.want) {
Expand Down
9 changes: 4 additions & 5 deletions collector/nginx.go
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
package collector

import (
"log/slog"
"sync"

"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/nginxinc/nginx-prometheus-exporter/client"
"github.com/prometheus/client_golang/prometheus"
)

// NginxCollector collects NGINX metrics. It implements prometheus.Collector interface.
type NginxCollector struct {
upMetric prometheus.Gauge
logger log.Logger
logger *slog.Logger
nginxClient *client.NginxClient
metrics map[string]*prometheus.Desc
mutex sync.Mutex
}

// NewNginxCollector creates an NginxCollector.
func NewNginxCollector(nginxClient *client.NginxClient, namespace string, constLabels map[string]string, logger log.Logger) *NginxCollector {
func NewNginxCollector(nginxClient *client.NginxClient, namespace string, constLabels map[string]string, logger *slog.Logger) *NginxCollector {

Check warning on line 21 in collector/nginx.go

View check run for this annotation

Codecov / codecov/patch

collector/nginx.go#L21

Added line #L21 was not covered by tests
return &NginxCollector{
nginxClient: nginxClient,
logger: logger,
Expand Down Expand Up @@ -55,7 +54,7 @@
if err != nil {
c.upMetric.Set(nginxDown)
ch <- c.upMetric
level.Error(c.logger).Log("msg", "Error getting stats", "error", err.Error())
c.logger.Error("error getting stats", "error", err.Error())

Check warning on line 57 in collector/nginx.go

View check run for this annotation

Codecov / codecov/patch

collector/nginx.go#L57

Added line #L57 was not covered by tests
return
}

Expand Down
23 changes: 11 additions & 12 deletions collector/nginx_plus.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@

import (
"fmt"
"log/slog"
"strconv"
"sync"

"github.com/go-kit/log"
"github.com/go-kit/log/level"
plusclient "github.com/nginxinc/nginx-plus-go-client/client"
"github.com/prometheus/client_golang/prometheus"
)
Expand All @@ -32,7 +31,7 @@
// NginxPlusCollector collects NGINX Plus metrics. It implements prometheus.Collector interface.
type NginxPlusCollector struct {
upMetric prometheus.Gauge
logger log.Logger
logger *slog.Logger
cacheZoneMetrics map[string]*prometheus.Desc
workerMetrics map[string]*prometheus.Desc
nginxClient *plusclient.NginxClient
Expand Down Expand Up @@ -256,7 +255,7 @@
}

// NewNginxPlusCollector creates an NginxPlusCollector.
func NewNginxPlusCollector(nginxClient *plusclient.NginxClient, namespace string, variableLabelNames VariableLabelNames, constLabels map[string]string, logger log.Logger) *NginxPlusCollector {
func NewNginxPlusCollector(nginxClient *plusclient.NginxClient, namespace string, variableLabelNames VariableLabelNames, constLabels map[string]string, logger *slog.Logger) *NginxPlusCollector {

Check warning on line 258 in collector/nginx_plus.go

View check run for this annotation

Codecov / codecov/patch

collector/nginx_plus.go#L258

Added line #L258 was not covered by tests
upstreamServerVariableLabelNames := variableLabelNames.UpstreamServerVariableLabelNames
streamUpstreamServerVariableLabelNames := variableLabelNames.StreamUpstreamServerVariableLabelNames

Expand Down Expand Up @@ -627,7 +626,7 @@
if err != nil {
c.upMetric.Set(nginxDown)
ch <- c.upMetric
level.Warn(c.logger).Log("msg", "Error getting stats", "error", err.Error())
c.logger.Warn("error getting stats", "error", err.Error())

Check warning on line 629 in collector/nginx_plus.go

View check run for this annotation

Codecov / codecov/patch

collector/nginx_plus.go#L629

Added line #L629 was not covered by tests
return
}

Expand Down Expand Up @@ -658,7 +657,7 @@
varLabelValues := c.getServerZoneLabelValues(name)

if c.variableLabelNames.ServerZoneVariableLabelNames != nil && len(varLabelValues) != len(c.variableLabelNames.ServerZoneVariableLabelNames) {
level.Warn(c.logger).Log("msg", "wrong number of labels for http zone, empty labels will be used instead", "zone", name, "expected", len(c.variableLabelNames.ServerZoneVariableLabelNames), "got", len(varLabelValues))
c.logger.Warn("wrong number of labels for http zone, empty labels will be used instead", "zone", name, "expected", len(c.variableLabelNames.ServerZoneVariableLabelNames), "got", len(varLabelValues))

Check warning on line 660 in collector/nginx_plus.go

View check run for this annotation

Codecov / codecov/patch

collector/nginx_plus.go#L660

Added line #L660 was not covered by tests
for range c.variableLabelNames.ServerZoneVariableLabelNames {
labelValues = append(labelValues, "")
}
Expand Down Expand Up @@ -779,7 +778,7 @@
varLabelValues := c.getStreamServerZoneLabelValues(name)

if c.variableLabelNames.StreamServerZoneVariableLabelNames != nil && len(varLabelValues) != len(c.variableLabelNames.StreamServerZoneVariableLabelNames) {
level.Warn(c.logger).Log("msg", "wrong number of labels for stream server zone, empty labels will be used instead", "zone", name, "expected", len(c.variableLabelNames.StreamServerZoneVariableLabelNames), "got", len(varLabelValues))
c.logger.Warn("wrong number of labels for stream server zone, empty labels will be used instead", "zone", name, "expected", len(c.variableLabelNames.StreamServerZoneVariableLabelNames), "got", len(varLabelValues))

Check warning on line 781 in collector/nginx_plus.go

View check run for this annotation

Codecov / codecov/patch

collector/nginx_plus.go#L781

Added line #L781 was not covered by tests
for range c.variableLabelNames.StreamServerZoneVariableLabelNames {
labelValues = append(labelValues, "")
}
Expand Down Expand Up @@ -816,7 +815,7 @@
varLabelValues := c.getUpstreamServerLabelValues(name)

if c.variableLabelNames.UpstreamServerVariableLabelNames != nil && len(varLabelValues) != len(c.variableLabelNames.UpstreamServerVariableLabelNames) {
level.Warn(c.logger).Log("msg", "wrong number of labels for upstream, empty labels will be used instead", "upstream", name, "expected", len(c.variableLabelNames.UpstreamServerVariableLabelNames), "got", len(varLabelValues))
c.logger.Warn("wrong number of labels for upstream, empty labels will be used instead", "upstream", name, "expected", len(c.variableLabelNames.UpstreamServerVariableLabelNames), "got", len(varLabelValues))

Check warning on line 818 in collector/nginx_plus.go

View check run for this annotation

Codecov / codecov/patch

collector/nginx_plus.go#L818

Added line #L818 was not covered by tests
for range c.variableLabelNames.UpstreamServerVariableLabelNames {
labelValues = append(labelValues, "")
}
Expand All @@ -827,7 +826,7 @@
upstreamServer := fmt.Sprintf("%v/%v", name, peer.Server)
varPeerLabelValues := c.getUpstreamServerPeerLabelValues(upstreamServer)
if c.variableLabelNames.UpstreamServerPeerVariableLabelNames != nil && len(varPeerLabelValues) != len(c.variableLabelNames.UpstreamServerPeerVariableLabelNames) {
level.Warn(c.logger).Log("msg", "wrong number of labels for upstream peer, empty labels will be used instead", "upstream", name, "peer", peer.Server, "expected", len(c.variableLabelNames.UpstreamServerPeerVariableLabelNames), "got", len(varPeerLabelValues))
c.logger.Warn("wrong number of labels for upstream peer, empty labels will be used instead", "upstream", name, "peer", peer.Server, "expected", len(c.variableLabelNames.UpstreamServerPeerVariableLabelNames), "got", len(varPeerLabelValues))

Check warning on line 829 in collector/nginx_plus.go

View check run for this annotation

Codecov / codecov/patch

collector/nginx_plus.go#L829

Added line #L829 was not covered by tests
for range c.variableLabelNames.UpstreamServerPeerVariableLabelNames {
labelValues = append(labelValues, "")
}
Expand Down Expand Up @@ -973,7 +972,7 @@
varLabelValues := c.getStreamUpstreamServerLabelValues(name)

if c.variableLabelNames.StreamUpstreamServerVariableLabelNames != nil && len(varLabelValues) != len(c.variableLabelNames.StreamUpstreamServerVariableLabelNames) {
level.Warn(c.logger).Log("msg", "wrong number of labels for stream server, empty labels will be used instead", "server", name, "labels", c.variableLabelNames.StreamUpstreamServerVariableLabelNames, "values", varLabelValues)
c.logger.Warn("wrong number of labels for stream server, empty labels will be used instead", "server", name, "labels", c.variableLabelNames.StreamUpstreamServerVariableLabelNames, "values", varLabelValues)

Check warning on line 975 in collector/nginx_plus.go

View check run for this annotation

Codecov / codecov/patch

collector/nginx_plus.go#L975

Added line #L975 was not covered by tests
for range c.variableLabelNames.StreamUpstreamServerVariableLabelNames {
labelValues = append(labelValues, "")
}
Expand All @@ -984,7 +983,7 @@
upstreamServer := fmt.Sprintf("%v/%v", name, peer.Server)
varPeerLabelValues := c.getStreamUpstreamServerPeerLabelValues(upstreamServer)
if c.variableLabelNames.StreamUpstreamServerPeerVariableLabelNames != nil && len(varPeerLabelValues) != len(c.variableLabelNames.StreamUpstreamServerPeerVariableLabelNames) {
level.Warn(c.logger).Log("msg", "wrong number of labels for stream upstream peer, empty labels will be used instead", "server", upstreamServer, "labels", c.variableLabelNames.StreamUpstreamServerPeerVariableLabelNames, "values", varPeerLabelValues)
c.logger.Warn("wrong number of labels for stream upstream peer, empty labels will be used instead", "server", upstreamServer, "labels", c.variableLabelNames.StreamUpstreamServerPeerVariableLabelNames, "values", varPeerLabelValues)

Check warning on line 986 in collector/nginx_plus.go

View check run for this annotation

Codecov / codecov/patch

collector/nginx_plus.go#L986

Added line #L986 was not covered by tests
for range c.variableLabelNames.StreamUpstreamServerPeerVariableLabelNames {
labelValues = append(labelValues, "")
}
Expand Down Expand Up @@ -1204,7 +1203,7 @@
varLabelValues := c.getCacheZoneLabelValues(name)

if c.variableLabelNames.CacheZoneVariableLabelNames != nil && len(varLabelValues) != len(c.variableLabelNames.CacheZoneVariableLabelNames) {
level.Warn(c.logger).Log("msg", "wrong number of labels for cache zone, empty labels will be used instead", "zone", name, "labels", c.variableLabelNames.CacheZoneVariableLabelNames, "values", varLabelValues)
c.logger.Warn("wrong number of labels for cache zone, empty labels will be used instead", "zone", name, "labels", c.variableLabelNames.CacheZoneVariableLabelNames, "values", varLabelValues)

Check warning on line 1206 in collector/nginx_plus.go

View check run for this annotation

Codecov / codecov/patch

collector/nginx_plus.go#L1206

Added line #L1206 was not covered by tests
for range c.variableLabelNames.CacheZoneVariableLabelNames {
labelValues = append(labelValues, "")
}
Expand Down
39 changes: 19 additions & 20 deletions exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"crypto/x509"
"errors"
"fmt"
"log/slog"
"maps"
"net"
"net/http"
Expand All @@ -20,13 +21,11 @@
"github.com/nginxinc/nginx-prometheus-exporter/collector"

"github.com/alecthomas/kingpin/v2"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/collectors/version"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/prometheus/common/promlog"
"github.com/prometheus/common/promlog/flag"
"github.com/prometheus/common/promslog"
"github.com/prometheus/common/promslog/flag"
common_version "github.com/prometheus/common/version"

"github.com/prometheus/exporter-toolkit/web"
Expand Down Expand Up @@ -110,24 +109,24 @@
}
}

promlogConfig := &promlog.Config{}
config := &promslog.Config{}

Check warning on line 112 in exporter.go

View check run for this annotation

Codecov / codecov/patch

exporter.go#L112

Added line #L112 was not covered by tests

flag.AddFlags(kingpin.CommandLine, promlogConfig)
flag.AddFlags(kingpin.CommandLine, config)

Check warning on line 114 in exporter.go

View check run for this annotation

Codecov / codecov/patch

exporter.go#L114

Added line #L114 was not covered by tests
kingpin.Version(common_version.Print(exporterName))
kingpin.HelpFlag.Short('h')

addMissingEnvironmentFlags(kingpin.CommandLine)

kingpin.Parse()
logger := promlog.New(promlogConfig)
logger := promslog.New(config)

Check warning on line 121 in exporter.go

View check run for this annotation

Codecov / codecov/patch

exporter.go#L121

Added line #L121 was not covered by tests

level.Info(logger).Log("msg", "Starting nginx-prometheus-exporter", "version", common_version.Info())
level.Info(logger).Log("msg", "Build context", "build_context", common_version.BuildContext())
logger.Info("nginx-prometheus-exporter", "version", common_version.Info())
logger.Info("build context", "build_context", common_version.BuildContext())

Check warning on line 124 in exporter.go

View check run for this annotation

Codecov / codecov/patch

exporter.go#L123-L124

Added lines #L123 - L124 were not covered by tests

prometheus.MustRegister(version.NewCollector(exporterName))

if len(*scrapeURIs) == 0 {
level.Error(logger).Log("msg", "No scrape addresses provided")
logger.Error("no scrape addresses provided")

Check warning on line 129 in exporter.go

View check run for this annotation

Codecov / codecov/patch

exporter.go#L129

Added line #L129 was not covered by tests
os.Exit(1)
}

Expand All @@ -136,13 +135,13 @@
if *sslCaCert != "" {
caCert, err := os.ReadFile(*sslCaCert)
if err != nil {
level.Error(logger).Log("msg", "Loading CA cert failed", "err", err.Error())
logger.Error("loading CA cert failed", "err", err.Error())

Check warning on line 138 in exporter.go

View check run for this annotation

Codecov / codecov/patch

exporter.go#L138

Added line #L138 was not covered by tests
os.Exit(1)
}
sslCaCertPool := x509.NewCertPool()
ok := sslCaCertPool.AppendCertsFromPEM(caCert)
if !ok {
level.Error(logger).Log("msg", "Parsing CA cert file failed.")
logger.Error("parsing CA cert file failed.")

Check warning on line 144 in exporter.go

View check run for this annotation

Codecov / codecov/patch

exporter.go#L144

Added line #L144 was not covered by tests
os.Exit(1)
}
sslConfig.RootCAs = sslCaCertPool
Expand All @@ -151,7 +150,7 @@
if *sslClientCert != "" && *sslClientKey != "" {
clientCert, err := tls.LoadX509KeyPair(*sslClientCert, *sslClientKey)
if err != nil {
level.Error(logger).Log("msg", "Loading client certificate failed", "error", err.Error())
logger.Error("loading client certificate failed", "error", err.Error())

Check warning on line 153 in exporter.go

View check run for this annotation

Codecov / codecov/patch

exporter.go#L153

Added line #L153 was not covered by tests
os.Exit(1)
}
sslConfig.Certificates = []tls.Certificate{clientCert}
Expand Down Expand Up @@ -190,7 +189,7 @@
}
landingPage, err := web.NewLandingPage(landingConfig)
if err != nil {
level.Error(logger).Log("err", err)
logger.Error("failed to create landing page", "error", err.Error())

Check warning on line 192 in exporter.go

View check run for this annotation

Codecov / codecov/patch

exporter.go#L192

Added line #L192 was not covered by tests
os.Exit(1)
}
http.Handle("/", landingPage)
Expand All @@ -206,28 +205,28 @@
go func() {
if err := web.ListenAndServe(srv, webConfig, logger); err != nil {
if errors.Is(err, http.ErrServerClosed) {
level.Info(logger).Log("msg", "HTTP server closed")
logger.Info("HTTP server closed", "error", err.Error())

Check warning on line 208 in exporter.go

View check run for this annotation

Codecov / codecov/patch

exporter.go#L208

Added line #L208 was not covered by tests
os.Exit(0)
}
level.Error(logger).Log("err", err)
logger.Error("HTTP server failed", "error", err.Error())

Check warning on line 211 in exporter.go

View check run for this annotation

Codecov / codecov/patch

exporter.go#L211

Added line #L211 was not covered by tests
os.Exit(1)
}
}()

<-ctx.Done()
level.Info(logger).Log("msg", "Shutting down")
logger.Info("shutting down")

Check warning on line 217 in exporter.go

View check run for this annotation

Codecov / codecov/patch

exporter.go#L217

Added line #L217 was not covered by tests
srvCtx, srvCancel := context.WithTimeout(context.Background(), 5*time.Second)
defer srvCancel()
_ = srv.Shutdown(srvCtx)
}

func registerCollector(logger log.Logger, transport *http.Transport,
func registerCollector(logger *slog.Logger, transport *http.Transport,
addr string, labels map[string]string,
) {
if strings.HasPrefix(addr, "unix:") {
socketPath, requestPath, err := parseUnixSocketAddress(addr)
if err != nil {
level.Error(logger).Log("msg", "Parsing unix domain socket scrape address failed", "uri", addr, "error", err.Error())
logger.Error("parsing unix domain socket scrape address failed", "uri", addr, "error", err.Error())

Check warning on line 229 in exporter.go

View check run for this annotation

Codecov / codecov/patch

exporter.go#L229

Added line #L229 was not covered by tests
os.Exit(1)
}

Expand All @@ -250,7 +249,7 @@
if *nginxPlus {
plusClient, err := plusclient.NewNginxClient(addr, plusclient.WithHTTPClient(httpClient))
if err != nil {
level.Error(logger).Log("msg", "Could not create Nginx Plus Client", "error", err.Error())
logger.Error("could not create Nginx Plus Client", "error", err.Error())

Check warning on line 252 in exporter.go

View check run for this annotation

Codecov / codecov/patch

exporter.go#L252

Added line #L252 was not covered by tests
os.Exit(1)
}
variableLabelNames := collector.NewVariableLabelNames(nil, nil, nil, nil, nil, nil, nil)
Expand Down
2 changes: 0 additions & 2 deletions exporter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ func TestParsePositiveDuration(t *testing.T) {
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got, err := parsePositiveDuration(tt.testInput)
Expand Down Expand Up @@ -93,7 +92,6 @@ func TestParseUnixSocketAddress(t *testing.T) {
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
socketPath, requestPath, err := parseUnixSocketAddress(tt.testInput)
Expand Down
4 changes: 1 addition & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,17 @@ go 1.22.6

require (
github.com/alecthomas/kingpin/v2 v2.4.0
github.com/go-kit/log v0.2.1
github.com/nginxinc/nginx-plus-go-client v1.3.0
github.com/prometheus/client_golang v1.20.3
github.com/prometheus/common v0.59.1
github.com/prometheus/exporter-toolkit v0.12.0
github.com/prometheus/exporter-toolkit v0.13.0
)

require (
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/go-logfmt/logfmt v0.5.1 // indirect
github.com/jpillora/backoff v1.0.0 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
Expand Down
Loading
Loading